/*
 *	cliprudc.h
 *
 *	パルスモーターの台形制御(Ramp-up/Down control)
 *
 *	CLiP - Common Library for P/ECE
 *	Copyright (C) 2001-2014 Naoyuki Sawa
 *
 *	* Wed Apr 23 21:55:05 JST 2014 Naoyuki Sawa
 *	- 1st リリース。
 *	* Mon May 05 14:59:40 JST 2014 Naoyuki Sawa
 *	- RudCtlCh_Move()を修正し、現在停止中,かつ,全体移動量が0ならば、何もしないようにしました。
 *	  この判断を行わないと、「move 0」「move 0」と実行した時、停止励磁のみのデバイス動作を都度実行してしまうからです。
 *	  見た目上はそれでも構いませんが、電力の無駄になります。
 *	* Mon May 05 19:19:03 JST 2014 Naoyuki Sawa
 *	- 停止励磁時間を追加しました。
 *	- これまでは、停止後,即,停止後動作要求を実行していたため、停止励磁が充分に行われない問題がありました。
 *	  停止励磁時間を追加したことによって、停止後、停止励磁終了を待ってから、停止後動作要求を実行するようになりました。
 *	- ただし、RudCtlCh_Move()とRudCtlCh_Stop()は、動作の判断条件に、停止励磁中か否かを考慮しません。
 *	  停止励磁中にRudCtlCh_Move()を実行した場合、停止励磁の終了を待たずに、直ちに新しい動作を開始します。(これまでと同じ)
 *	  停止励磁中にRudCtlCh_Stop()を実行した場合、新しい停止動作を開始せずに、現在の停止励磁を継続します。(これまでと同じ)
 *	* Tue May 06 15:46:46 JST 2014 Naoyuki Sawa
 *	- 直線移動のセンサエッジを無視しない方法を変更し、RudCtlCh_Exec()で判断するようにしました。
 *	  この変更によって、初期動作時の特別処理は不要になりました。
 *	* Thu May 29 00:28:30 JST 2014 Naoyuki Sawa
 *	- 停止中からの動作において、目標分周比が起動分周比よりも大きい(=遅い)場合は、目標分周比で起動するように修正しました。
 *	  停止中からの動作において、目標分周比が停止分周比よりも大きい(=遅い)場合は、目標分周比で停止するように修正しました。
 *	  動作中からの停止において、現在分周比が停止分周比よりも大きい(=遅い)場合は、現在分周比で停止するように修正しました。
 */
#ifndef __CLIP_RUDC_H__
#define __CLIP_RUDC_H__

/****************************************************************************
 *	パルスモーターの台形制御(Ramp-up/Down control) - 管理
 ****************************************************************************/
#define RudCtlCh_Max 3 /* 台形制御チャネル番号の最大値(この値も含む) */		//調整可。(3+1)チャネルで足りない場合は、増やしてください。
/*--------------------------------------------------------------------------*/
/* 動作要求 */
typedef struct _ST_RudCtlReq {
	 uint8_t Cond;			/* 実行条件(RudCtlReq_If_*,又は,RudCtlReq_When_*) */
	 uint8_t PosType;		/* 位置指定方法(RudCtlReq_Abs,又は,RudCtlReq_Rel) */
	 int16_t Pos;			/* 位置 */
	uint16_t Spd;			/* 目標分周比 */			//最速で良ければ0を指定して構わない。目標分周比の下限値未満の値を指定すると、RudCtlCh_Move()が自動的に補正する。
//省略	 uint8_t s,t;			/* 加速度[pps/clk] */			//動作要求のパラメータで、加速度を指定できるようしても良いが、実用上ほぼ使わないので省略した。加速度は常に、ST_RudCtlChInfo.r1/s1,又は,r2/s2を使用することにする。もし今後必要になったら、動作要求のパラメータに追加して処理せよ。
} ST_RudCtlReq;
/* ST_RudCtlReq.Cond(即時実行要求の場合) */
#define RudCtlReq_If_Never	0	/* 実行しない */			//←┐
#define RudCtlReq_If_SnsOn	1	/* センサOnならば実行 */		//←─┐
#define RudCtlReq_If_SnsOff	2	/* センサOffならば実行 */		//←│─┐
#define RudCtlReq_If_Always	3	/* 無条件で実行 */			//←─│─┐
/* ST_RudCtlReq.Cond(イベント実行要求の場合) */					//　││││	イベント実行条件発生時、イベント実行要求をそのまま即時実行要求に渡せるよう、Condの値を設計した。
#define RudCtlReq_When_Never	0	/* 実行しない */			//─┘│││	例えば、センサOnエッジ(1)発生時、センサOn(1)実行条件に一致して、実行される。
#define RudCtlReq_When_SnsRise	1	/* センサOnエッジで実行 */		//──┘││	具体的には、RudCtlCh_Exec()の実装を参照せよ。
#define RudCtlReq_When_SnsFall	2	/* センサOffエッジで実行 */		//───┘│
#define RudCtlReq_When_SnsEdge	3	/* センサエッジで実行 */		//────┘
/* ST_RudCtlReq.PosType */
#define RudCtlReq_Rel		0	/* 相対位置指定 */
#define RudCtlReq_Abs		1	/* 絶対位置指定 */
/*--- グローバル定数 ---*/
//□空の実行要求
// - 空の実行要求を頻繁に使用するので、定数として定義しておくことにした。
//   使用例: RudCtlCh_Move(pRudCtlCh, ReqImm, stRudCtlReq_Never/*無し*/);
extern const ST_RudCtlReq stRudCtlReq_Never;
/*--------------------------------------------------------------------------*/
/* 動作パラメータ */
typedef struct _ST_RudCtlAct {
	 int16_t d;			/* 移動方向(0=順方向,1=逆方向) */	//{0,1}以外の値は不正であることに注意せよ。
	uint16_t n[3+1+3];		/* 加速分周比1,2,3、目標分周比、減速分周比3,2,1 */
	uint16_t p[3+1+3];		/* 加速区間移動量1,2,3[pulse]、目標速度移動量[pulse]、減速区間移動量3,2,1[pulse] */
} ST_RudCtlAct;
/* ST_RudCtlActのフィールドを直接アクセスせず、下記のマクロを使用せよ。 */
#define RudCtlAct_d(  pRudCtlAct) ((pRudCtlAct)->d   )	/* 移動方向 */
#define RudCtlAct_n11(pRudCtlAct) ((pRudCtlAct)->n[0])	/* 加速分周比1 */
#define RudCtlAct_n12(pRudCtlAct) ((pRudCtlAct)->n[1])	/* 加速分周比2 */
#define RudCtlAct_n13(pRudCtlAct) ((pRudCtlAct)->n[2])	/* 加速分周比3 */
#define RudCtlAct_n(  pRudCtlAct) ((pRudCtlAct)->n[3])	/* 目標分周比 */
#define RudCtlAct_n23(pRudCtlAct) ((pRudCtlAct)->n[4])	/* 減速分周比1 */
#define RudCtlAct_n22(pRudCtlAct) ((pRudCtlAct)->n[5])	/* 減速分周比2 */
#define RudCtlAct_n21(pRudCtlAct) ((pRudCtlAct)->n[6])	/* 減速分周比3 */
#define RudCtlAct_p11(pRudCtlAct) ((pRudCtlAct)->p[0])	/* 加速区間移動量3[pulse] */
#define RudCtlAct_p12(pRudCtlAct) ((pRudCtlAct)->p[1])	/* 加速区間移動量2[pulse] */
#define RudCtlAct_p13(pRudCtlAct) ((pRudCtlAct)->p[2])	/* 加速区間移動量1[pulse] */
#define RudCtlAct_p(  pRudCtlAct) ((pRudCtlAct)->p[3])	/* 目標速度移動量[pulse] */
#define RudCtlAct_p23(pRudCtlAct) ((pRudCtlAct)->p[4])	/* 減速区間移動量3[pulse] */
#define RudCtlAct_p22(pRudCtlAct) ((pRudCtlAct)->p[5])	/* 減速区間移動量2[pulse] */
#define RudCtlAct_p21(pRudCtlAct) ((pRudCtlAct)->p[6])	/* 減速区間移動量1[pulse] */
/*--------------------------------------------------------------------------*/
/* 台形制御チャネル情報 */
typedef struct _ST_RudCtlChInfo {
	uint16_t Sns;			/* センサマスク */			//センサのビット位置が16bit範囲内であると仮定した。もしそうでない場合はuint32_tに変更せよ。
	uint16_t Bac;			/* バックラッシ[pulse] */
	struct {
		/* 位置 */
		 int16_t PosLim;	/* 限界位置 */				//┬PosがPosLimを超えたら、PosFixに書き換える。
		 int16_t PosFix;	/* 修正位置 */				//└直線移動ならば、Dir[0].PosLim=Dir[0].PosFix、Dir[1].PosLim=Dir[1].PosFixとするのが良い。回転移動ならば、Dir[0].PosLim=Dir[1].PosFix、Dir[1].PosLim=Dir[0].PosFixとするのが良い。
		 int16_t PosSns;	/* センサOnエッジ位置 */		//┬順方向センサOnエッジ検出時、 Dir[0].PosSns  に書き換える。逆方向センサOnエッジ検出時、 Dir[1].PosSns  に書き換える。	//直線移動の場合(厳密には移動範囲の端にセンサ検出範囲が有る直線移動の場合)は、順方向センサOnエッジは無いので、ダミー値であるが、逆方向センサOnエッジと同じ値を設定せよ。順方向と逆方向のセンサOnエッジ位置が同じかどうかで、直線移動か回転移動かを判断している処理が有る。RudCtlCh_Exec()とRudCtlCh_MoveAbs()を参照せよ。
		/* 速度 */							//└逆方向センサOffエッジ検出時、Dir[0].PosSns-1に書き換える。順方向センサOffエッジ検出時、Dir[1].PosSns+1に書き換える。
		uint16_t n;		/* 目標分周比の下限値 */
		uint16_t n1;		/* 起動分周比の下限値 */
		uint16_t r1, s1;	/* 加速度[pps/clk]の上限値 */
		uint16_t n2;		/* 停止分周比の下限値 */
		uint16_t r2, s2;	/* 減速度[pps/clk]の上限値 */
		/* 停止励磁 */
		uint16_t StpTim;	/* 停止励磁時間[clk] */
	} Dir[2];			/* [0]=順方向,[1]=逆方向 */
} ST_RudCtlChInfo;
/*--------------------------------------------------------------------------*/
/* 台形制御情報 */
typedef struct _ST_RudCtlInfo {
	void (*fnMove)(int iRudCtlCh, const ST_RudCtlAct* pRudCtlAct);		/* デバイス動作開始関数(アプリケーション定義) */
//不要	void (*fnStop)(int iRudCtlCh);						/* デバイス動作停止関数(アプリケーション定義) */	//現在の実装では使用していない。停止時はfnMoveを使って減速し自然に停止しているから。今後もし強制即停止の機能等を追加するために必要になったら復活させよ。現在は未定である。
	uint16_t fCLK;								/* 基本クロック[pps] */
	ST_RudCtlChInfo TBL_RudCtlChInfo[RudCtlCh_Max+1];			/* 台形制御チャネル情報 */				//使用しない台形制御チャネルの台形制御チャネル情報は、全フィールドの値を0としてください。
} ST_RudCtlInfo;
/*--------------------------------------------------------------------------*/
/* 台形制御チャネル */
typedef struct _ST_RudCtlCh {
	uint16_t Sns;			/* センサ安定状態 */			//常に有効				センサ状態のビット並びに、センサマスクを適用した値。下記の方法で安定状態を判定し、安定状態と判定するまでは変化させない。
	uint16_t SnsCnt;		/* センサ現在状態カウンタ */		//常に有効				ノイズ除去用。センサ状態が安定状態と同じならば、次回のセンサ状態変化時に備えてSnsCntを0にする。センサ状態が安定状態と違うならばSnsCntを+1し、(SnsCnt>=Spd)になったら安定状態と判断する。要するに、おおよそ1[pulse]間センサ状態が変化しなければ安定状態と判定する。厳密には、Spdが変化するので正確に1[pulse]間ではないが、実用上問題ないだろう。尚、停止中はSpd=0なので常に(SnsCnt>=Spd)であり、即安定状態となる。
	 int16_t Pos;			/* 現在位置 */				//常に有効
	uint16_t Spd;			/* 現在分周比 */			//常に有効				┬(Spd=0以外,StpTim=不問)…動作中。(Spd=0,StpTim=0以外)…停止中,かつ,停止励磁中。(Spd=0,StpTim=0)…停止中,かつ,無励磁。
	uint16_t StpTim;		/* 残り停止励磁時間[clk] */		//常に有効				┘停止状態をSpdのみで判断するか、SpdとStpTimで判断するかは、場面による。RudCtlCh_Move()とRudCtlCh_Stop()では、Spdのみで判断する。RudCtlCh_Exec()で停止後動作要求を実行する条件は、SpdとStpTimで判断する。
	uint16_t Cnt;			/* 現在経過時間[clk] */			//停止中は無効
	uint16_t DivCnt;		/* 現在分周比の分周カウンタ */		//停止中は無効
	uint16_t BacCnt;		/* バックラッシカウンタ[pulse] */	//停止中は無効				逆回転開始時にセットする。BacCntが残っている間、Posを変化させない。
	ST_RudCtlAct Act;		/* 動作パラメータ */			//停止後も最後の動作パラメータを保持	停止後のST_RudCtlCh.Act.dは、前回移動方向を意味する(バックラッシ判定に使用)。起動直後は前回移動方向が不明であるが、前回移動方向=0(順方向)と見なすことにした。
//不要	ST_RudCtlReq ReqImm1;		/* 実行中動作要求(即時実行要求) */
	ST_RudCtlReq ReqEvt1;		/* 実行中動作要求(イベント実行要求) */
	ST_RudCtlReq ReqImm2;		/* 停止後動作要求(即時実行要求) */
	ST_RudCtlReq ReqEvt2;		/* 停止後動作要求(イベント実行要求) */
} ST_RudCtlCh;
/*--- グローバル関数 ---*/
void RudCtlCh_Move(int iRudCtlCh, ST_RudCtlReq ReqImm/*即時実行要求*/, ST_RudCtlReq ReqEvt/*イベント実行要求*/);
void RudCtlCh_Stop(int iRudCtlCh);
/*--------------------------------------------------------------------------*/
/* 台形制御 */
typedef struct _ST_RudCtl {
	const ST_RudCtlInfo* pRudCtlInfo;		/* 台形制御情報へのポインタ */
	ST_RudCtlCh TBL_RudCtlCh[RudCtlCh_Max+1];	/* 台形制御チャネル */		//【注意】厳密にはvolatileであるがコンパイラ最適化が阻害されるのでvolatileを省略した。割込外から書込んではいけない。割込外から読出す時は関数経由せよ。1wordの読出しに限り直接読出しても構わないが、ビジーループで監視してはいけない。(コンパイラが最適化して一回しか読出さないから)
} ST_RudCtl;
/* 割込外からST_RudCtl.TBL_RudCtlCh[]にアクセスする場合は、下記のマクロを使用せよ。 */
#define RudCtlCh_Get(iRudCtlCh)		(&((volatile ST_RudCtl*)&stRudCtl)->TBL_RudCtlCh[iRudCtlCh])
#define RudCtlCh_GetPos(iRudCtlCh)	(RudCtlCh_Get(iRudCtlCh)->Pos)		//┐
#define RudCtlCh_GetSpd(iRudCtlCh)	(RudCtlCh_Get(iRudCtlCh)->Spd)		//├頻繁に参照するフィールドのみマクロ定義した。これ以外のフィールドは、「RudCtlCh_Get()->フィールド」で参照せよ。
#define RudCtlCh_GetStpTim(iRudCtlCh)	(RudCtlCh_Get(iRudCtlCh)->StpTim)	//┘
/*--- グローバル変数 ---*/
extern ST_RudCtl stRudCtl;
/*--- グローバル関数 ---*/
void RudCtl_Init(const ST_RudCtlInfo* pRudCtlInfo);
void RudCtl_Exec(int Sns/*センサ状態のビット並び*/);

/****************************************************************************
 *	アプリケーション用のユーティリティマクロ
 ****************************************************************************/
/* 指定した台形制御チャネル番号に対応する、台形制御チャネル情報にアクセスするマクロ。簡便のために定義したもので、使用しなくても構わない。ST_RudCtl.pRudCtlInfoは、アプリケーションがRudCtl_Init()に指定したポインタをそのまま格納しているだけである。アプリケーションはST_RudCtlInfoを保持しているはずだから、それを直接参照しても構わない。 */
#define RudCtlCh_GetInfo(iRudCtlCh)	(&stRudCtl.pRudCtlInfo->TBL_RudCtlChInfo[iRudCtlCh])
#define RudCtlCh_GetPosLim(iRudCtlCh,d)	(RudCtlCh_GetInfo(iRudCtlCh)->Dir[d].PosLim)						//┐
#define RudCtlCh_GetPosSns(iRudCtlCh,d)	(RudCtlCh_GetInfo(iRudCtlCh)->Dir[d].PosSns)						//├頻繁に参照するフィールドのみマクロ定義した。これ以外のフィールドは、「RudCtlCh_GetInfo()->フィールド」で参照せよ。
#define RudCtlCh_GetWidth(iRudCtlCh)	(RudCtlCh_GetPosLim(iRudCtlCh,0)-RudCtlCh_GetPosLim(iRudCtlCh,1)+1) //最大移動量	//┘

/****************************************************************************
 *	アプリケーション用のユーティリティ関数
 ****************************************************************************/
/* 単純な制御用途ならば、RudCtlCh_Move()やRudCtlCh_Stop()を直接使用しなくても、下記の三種類の関数だけで用が足りると思います。 */
void RudCtlCh_MoveInit(int iRudCtlCh);
void RudCtlCh_MoveAbs(int iRudCtlCh, int Pos/*絶対位置*/, int Spd/*0=最速*/);
void RudCtlCh_MoveRel(int iRudCtlCh, int Pos/*相対位置*/, int Spd/*0=最速*/);

#endif/*__CLIP_RUDC_H__*/
