/*
 *	cliprudc.c
 *
 *	パルスモーターの台形制御(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
 *	- 停止中からの動作において、目標分周比が起動分周比よりも大きい(=遅い)場合は、目標分周比で起動するように修正しました。
 *	  停止中からの動作において、目標分周比が停止分周比よりも大きい(=遅い)場合は、目標分周比で停止するように修正しました。
 *	  動作中からの停止において、現在分周比が停止分周比よりも大きい(=遅い)場合は、現在分周比で停止するように修正しました。
 *	* Wed Jun 11 21:00:52 JST 2014 Naoyuki Sawa
 *	- RudCtlCh_Exec()を修正しました。修正点は、以下の通りです:
 *	  「センサエッジ付近で停止した際、停止励磁中に振動して不正な方向のセンサエッジを複数回検出し、2回目を本物と見なして採用してしてしまう」という問題が生じた。
 *	  問題を回避するために、停止中,又は,停止励磁中は、SpuriousSnsEdgeDetectedのビットをセットしないことにした。
 */
#include "clip.h"

#define PRODUCT_SPECIFIC_WORKAROUND /* このシンボルを定義すると、製品固有の調整を行う。 */

/****************************************************************************
 *	パルスモーターの台形制御(Ramp-up/Down control) - 計算
 ****************************************************************************/
//□参考にした資料:
//│Panasonic>法人向けトップ>電子デバイス・産業用機器>制御機器トップ>サービス・サポート>FA技術サポート>技術資料>位置決めNo.1位置決め移動時間の算出
//│(http://www3.panasonic.biz/ac/j/service/tech_support/fasys/technical_data/technical_data_pos_001/)
//
//□設計の概要(実装上の都合のため、厳密にはこのとおりでない点もあります)
//
//	・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・┌───────────────┐
//		↑								│				│
//		│								│				│
//		│								│				│
//		│	・・・・・・・・・・・・・・・・・・・・・・・・┌───┘				└───┐・・・・・・・・・・・・・・・・・・・・・・・・・・・
//		│				↑			│	・				・	│			↑
//		│		　　　加速速度３│			│	・				・	│			│減速速度３
//		│				│			│	・				・	│			│
//		│	・・・・・・・・・・・・│・・・・・・・┌───┘	・				・	└───┐・・・・・・・│・・・・・・・・・・・・・・・
//		│			↑	│		│		・				・		│		│	↑
//	目標速度│	　　　加速速度２│	│		│		・				・		│		│	│減速速度２
//		│			│	│		│		・				・		│		│	│
//		│	・・・・・・・・│・・・│・・・┌───┘		・				・		└───┐・・・│・・・│・・・・・・・・・・・
//		│		↑	│	│	│			・				・			│	│	│	↑
//		│		│	│	│	│			・				・			│	│	│	│
//		│		│	│	│	│			・				・			│	│	│	│
//		│　　加速速度１│	│	│	│　　加速区間移動量	・	　目標速度移動量	・　　減速区間移動量	│	│	│	│減速速度１
//		│		│	│	│	│			・				・			│	│	│	│
//		│		│	│	│	│			・				・			│	│	│	│
//		↓		↓	↓	↓	│			・				・			│	↓	↓	↓
//	━━━━━━━━━━━━━━━━━━━━━━━━┷━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┷━━━━━━━━━━━━━━━━━━━
//							・			・				・			・
//							・←─────────→・←─────────────→・←─────────→・
//							・	 加速時間	・	 目標速度移動時間	・	 減速時間	・
//							・										・
//							・←─────────────────────────────────────→・
//							・				 　全体移動時間					・
//							・										・
//
//[in]										実際には速度f[pps]を分周比nで与える。
//
//	全体移動量		P[pulse]
//	目標速度		f[pps]						目標分周比	n =fCLK/f	(f =fCLK/n )
//	起動速度		f1[pps]			(=加速速度1)		起動分周比	n1=fCLK/f1	(f1=fCLK/n1)
//	停止速度		f2[pps]			(=減速速度1)		停止分周比	n2=fCLK/f2	(f2=fCLK/n2)
//	加速度			r1/s1[pps/clk]		(Rate/Scale)
//	減速度			r1/s1[pps/clk]		(Rate/Scale)
//	基本クロック		fCLK[pps]
//
//[calc]
//
//	--- 加速区間 ---
//
//	加速時間		t1[clk]			=(f-f1)*s1/r1				=(n1-n)*s1/(n1*n*r1)
//
//	□三段階加速
//
//	加速速度1		f11[pps]		=(f-f1)*0/3+f1		加速分周比1	n11=(n1*n*3)/(n1*0+n*3)=n1
//	加速速度2		f12[pps]		=(f-f1)*1/3+f1		加速分周比2	n12=(n1*n*3)/(n1*1+n*2)		ceil
//	加速速度3		f13[pps]		=(f-f1)*2/3+f1		加速分周比3	n13=(n1*n*3)/(n1*2+n*1)		ceil
//
//	加速区間移動量1		p11[pulse]		=f11*t1/3				=(fCLK/n11)*t1/3=abs(n1-n)*s1*fCLK/(n1*n*r1*3*n11)	ceil	目標速度よりも起動速度が速い場合に対応できるよう、abs()を使う。たとえば、既に高速移動中(=起動速度と見なす)であり、低速(=目標速度)へ移行したい場合など。
//	加速区間移動量2		p12[pulse]		=f12*t1/3				=(fCLK/n12)*t1/3=abs(n1-n)*s1*fCLK/(n1*n*r1*3*n12)	ceil	目標速度よりも起動速度が速い場合に対応できるよう、abs()を使う。たとえば、既に高速移動中(=起動速度と見なす)であり、低速(=目標速度)へ移行したい場合など。
//	加速区間移動量3		p13[pulse]		=f13*t1/3				=(fCLK/n13)*t1/3=abs(n1-n)*s1*fCLK/(n1*n*r1*3*n13)	ceil	目標速度よりも起動速度が速い場合に対応できるよう、abs()を使う。たとえば、既に高速移動中(=起動速度と見なす)であり、低速(=目標速度)へ移行したい場合など。
//
//	⇒n11とn12,又は,n12とn13,又は,n13とnが同じ値であるか、或いは、p11,p12,p13の何れかが0だったら、二段階加速へ切り替える。
//
//	□二段階加速
//
//	加速速度1		f11[pps]		=(f-f1)*0/2+f1		加速分周比1	n11=(n1*n*2)/(n1*0+n*2)=n1	ceil
//	加速速度2		f12[pps]		=(f-f1)*1/2+f1		加速分周比2	n12=(n1*n*2)/(n1*1+n*1)		ceil
//	加速速度3		f13[pps]		=f			加速分周比3	n13=n								「目標速度移動区間」処理で左右の平均を取るため、使用しない加(減)速速度は目標速度としておく。
//
//	加速区間移動量1		p11[pulse]		=f11*t1/2				=(fCLK/n11)*t1/2=abs(n1-n)*s1*fCLK/(n1*n*r1*2*n11)	ceil	目標速度よりも起動速度が速い場合に対応できるよう、abs()を使う。たとえば、既に高速移動中(=起動速度と見なす)であり、低速(=目標速度)へ移行したい場合など。
//	加速区間移動量2		p12[pulse]		=f12*t1/2				=(fCLK/n12)*t1/2=abs(n1-n)*s1*fCLK/(n1*n*r1*2*n12)	ceil	目標速度よりも起動速度が速い場合に対応できるよう、abs()を使う。たとえば、既に高速移動中(=起動速度と見なす)であり、低速(=目標速度)へ移行したい場合など。
//	加速区間移動量3		p13[pulse]		=0					=0
//
//	⇒n11とn12,又は,n12とnが同じ値であるか、或いは、p11,p12の何れかが0だったら、一段階加速へ切り替える。
//
//	□一段階加速
//
//	加速速度1		f11[pps]		=(f-f1)*0/1+f1		加速分周比1	n11=(n1*n*1)/(n1*0+n*1)=n1
//	加速速度2		f12[pps]		=f			加速分周比2	n12=n								「目標速度移動区間」処理で左右の平均を取るため、使用しない加(減)速速度は目標速度としておく。
//	加速速度3		f13[pps]		=f			加速分周比3	n13=n								「目標速度移動区間」処理で左右の平均を取るため、使用しない加(減)速速度は目標速度としておく。
//
//	加速区間移動量1		p11[pulse]		=f11*t1/1				=(fCLK/n11)*t1/1=abs(n1-n)*s1*fCLK/(n1*n*r1*1*n11)	ceil	目標速度よりも起動速度が速い場合に対応できるよう、abs()を使う。たとえば、既に高速移動中(=起動速度と見なす)であり、低速(=目標速度)へ移行したい場合など。
//	加速区間移動量2		p12[pulse]		=0					=0
//	加速区間移動量3		p13[pulse]		=0					=0
//
//	⇒n11とnが同じ値であるか、或いは、p11が0だったら、加速無しへ切り替える。
//
//	□加速無し
//
//	加速速度1		f11[pps]		=f			加速分周比1	n11=n								「目標速度移動区間」処理で左右の平均を取るため、使用しない加(減)速速度は目標速度としておく。
//	加速速度2		f12[pps]		=f			加速分周比2	n12=n								「目標速度移動区間」処理で左右の平均を取るため、使用しない加(減)速速度は目標速度としておく。
//	加速速度3		f13[pps]		=f			加速分周比3	n13=n								「目標速度移動区間」処理で左右の平均を取るため、使用しない加(減)速速度は目標速度としておく。
//
//	加速区間移動量1		p11[pulse]		=0					=0
//	加速区間移動量2		p12[pulse]		=0					=0
//	加速区間移動量3		p13[pulse]		=0					=0
//
//	--- 減速区間 ---
//
//	(加速と左右対称で計算方法は同じなので、説明省略する)
//
//	--- 目標速度移動区間 ---
//
//	目標速度移動量		p[pulse]		=P-(p11+p12+p13)-(p21+p22+p23)
//
//												if(p<0){p+=p13+p23;p13=p23=0;n=(n13+n23+1)/2;}			//┐
//												if(p<0){p+=p12+p22;p12=p22=0;n=(n12+n22+1)/2;}			//├※TODO:厳密ではない。本当は、マイナス分だけ加(減)速を減らして、目標速度移動量は0のままにするのが正しい。修正を検討せよ。
//												if(p<0){p+=p11+p21;p11=p21=0;n=(n11+n21+1)/2;}			//┘
//
//	--- 全体移動時間 ---
//
//	(呼出側で容易に計算できるので、関数側からは返さない)
//
//	目標速度移動時間	t[clk]								=n*p
//
//	加速時間1		t11[clk]							=n11*p11
//	加速時間2		t12[clk]							=n12*p12
//	加速時間3		t13[clk]							=n13*p13
//
//	減速時間1		t21[clk]							=n21*p21
//	減速時間2		t22[clk]							=n22*p22
//	減速時間3		t23[clk]							=n23*p23
//
//	全体移動時間		T[clk]								=t+(t11+12+t13)+(t21+t22+t23)
//
//[note]
//
//	計算処理としては、加速速度1(=起動分周比),減速速度1(=停止分周比),又は,その両方に、目標速度よりも速い速度(=小さい分周比)を指定することもできる。
//	既に高速で移動中に、それよりも遅い目標速度へ移行する場合や、目標速度移動区間終了後、それよりも速い速度へ移行する場合の計算などに、利用できる。
//
//	・加速速度1<目標速度>減速速度1の場合:	　＿　
//	　　　　　　　　　　　　　　　　　　	／　＼
//
//	・加速速度1=目標速度>減速速度1の場合	＿＿　
//	　　　　　　　　　　　　　　　　　　	　　＼
//
//	・加速速度1>目標速度>減速速度1の場合:	＼＿　
//	　　　　　　　　　　　　　　　　　　	　　＼
//
//	・加速速度1<目標速度=減速速度1の場合:	　＿＿
//	　　　　　　　　　　　　　　　　　　	／　　
//
//	・加速速度1=目標速度=減速速度1の場合:	＿＿＿
//	　　　　　　　　　　　　　　　　　　	　　　
//
//	・加速速度1>目標速度=減速速度1の場合:	　＿＿
//	　　　　　　　　　　　　　　　　　　	／　　
//
//	・加速速度1<目標速度<減速速度1の場合:	　＿／
//	　　　　　　　　　　　　　　　　　　	／　　
//
//	・加速速度1=目標速度<減速速度1の場合:	＿＿／
//	　　　　　　　　　　　　　　　　　　	　　　
//
//	・加速速度1>目標速度<減速速度1の場合:	　＿／
//	　　　　　　　　　　　　　　　　　　	／　　
//
/*--------------------------------------------------------------------------*/
/* 除算して切り上げる処理 */
static unsigned div_ceil(unsigned n, unsigned d) { return (n + (d - 1)) / d; }
/*--------------------------------------------------------------------------*/
/* 加速区間,又は,減速区間の共通計算処理 */
static void RudCtl_CalcX(
    //[in]
    unsigned fCLK,		/* 基本クロック[pps] */
    unsigned n,			/* 目標分周比 */
    unsigned nX,		/* 起動(停止)分周比 */
    unsigned rX, unsigned sX,	/* 加(減)速度[pps/clk] */
    //[out]
    unsigned ret_nX[3],		/* 加(減)速分周比1,2,3 */
    unsigned ret_pX[3]) {	/* 加(減)速区間移動量1,2,3[pulse] */
	unsigned a, b, c;				/* 予め、共通式を計算するための変数 */
	if(!fCLK || !n || !nX || !rX || !sX) { DIE(); }
	a = abs(nX - n) * sX * fCLK;			/* 全段階加(減)速の共通式を計算する */	//目標速度よりも起動速度が速い場合に対応できるよう、abs()を使う。たとえば、既に高速移動中(=起動速度と見なす)であり、低速(=目標速度)へ移行したい場合など。
	/* 三段階加(減)速 */
#ifdef  WIN32
	printf("三段階加(減)速\n");
#endif//WIN32
	b = nX * n * 3;					/* 三段階加(減)速の共通式を計算する */
	c = rX * b;					/* 三段階加(減)速の共通式を計算する */
	ret_nX[0] = nX;
	ret_nX[1] = div_ceil(b, nX * 1 + n * 2);
	ret_nX[2] = div_ceil(b, nX * 2 + n * 1);
	ret_pX[0] = div_ceil(a, c * ret_nX[0]);
	ret_pX[1] = div_ceil(a, c * ret_nX[1]);
	ret_pX[2] = div_ceil(a, c * ret_nX[2]);
	if((ret_nX[0] != ret_nX[1]) &&
	   (ret_nX[1] != ret_nX[2]) &&
	   (ret_nX[2] != n) &&
	    ret_pX[0] &&
	    ret_pX[1] &&
	    ret_pX[2]) { return; } /* ここまで */
	/* 二段階加(減)速 */
#ifdef  WIN32
	printf("二段階加(減)速\n");
#endif//WIN32
	b = nX * n * 2;					/* 二段階加(減)速の共通式を計算する */
	c = rX * b;					/* 二段階加(減)速の共通式を計算する */
//不要	ret_nX[0] = nX;
	ret_nX[1] = div_ceil(b, nX * 1 + n * 1);
	ret_nX[2] = n;										//「目標速度移動区間」処理で左右の平均を取るため、使用しない加(減)速速度は目標速度としておく。
	ret_pX[0] = div_ceil(a, c * ret_nX[0]);
	ret_pX[1] = div_ceil(a, c * ret_nX[1]);
	ret_pX[2] = 0;
	if((ret_nX[0] != ret_nX[1]) &&
	   (ret_nX[1] != n) &&
	    ret_pX[0] &&
	    ret_pX[1]) { return; } /* ここまで */
	/* 一段階加(減)速 */
#ifdef  WIN32
	printf("一段階加(減)速\n");
#endif//WIN32
	b = nX * n * 1;					/* 一段階加(減)速の共通式を計算する */
	c = rX * b;					/* 一段階加(減)速の共通式を計算する */
//不要	ret_nX[0] = nX;
	ret_nX[1] = n;										//「目標速度移動区間」処理で左右の平均を取るため、使用しない加(減)速速度は目標速度としておく。
//不要	ret_nX[2] = n;
	ret_pX[0] = div_ceil(a, c * ret_nX[0]);
	ret_pX[1] = 0;
//不要	ret_pX[2] = 0;
	if((ret_nX[0] != n) &&
	    ret_pX[0]) { return; } /* ここまで */
	/* 加(減)速無し */
#ifdef  WIN32
	printf("加(減)速無し\n");
#endif//WIN32
	ret_nX[0] = n;										//「目標速度移動区間」処理で左右の平均を取るため、使用しない加(減)速速度は目標速度としておく。
//不要	ret_nX[1] = n;
//不要	ret_nX[2] = n;
	ret_pX[0] = 0;
//不要	ret_pX[1] = 0;
//不要	ret_pX[2] = 0;
}
/*--------------------------------------------------------------------------*/
/* 三段階加(減)速の台形制御 */
static void RudCtl_Calc(
    //[in]
    unsigned fCLK,		/* 基本クロック[pps] */
    unsigned P,			/* 全体移動量[pulse] */
    unsigned n,			/* 目標分周比 */		//補正前の値
    unsigned n1,		/* 起動分周比 */
    unsigned r1, unsigned s1,	/* 加速度[pps/clk] */
    unsigned n2,		/* 停止分周比 */
    unsigned r2, unsigned s2,	/* 減速度[pps/clk] */
    //[out]
    unsigned* ret_n,		/* 目標分周比 */		//補正後の値
    unsigned* ret_p,		/* 目標速度移動量[pulse] */
    unsigned  ret_n1[3],	/* 加速分周比1,2,3 */
    unsigned  ret_p1[3],	/* 加速区間移動量1,2,3[pulse] */
    unsigned  ret_n2[3],	/* 減速分周比1,2,3 */
    unsigned  ret_p2[3]) {	/* 減速区間移動量1,2,3[pulse] */
	int i, p;
	if(!fCLK || /*!P ||*/ !n || !n1 || !r1 || !s1 || !n2 || !r2 || !s2) { DIE(); }	//全体移動量(P)のみ0でも可
	/* 加速区間 */
	RudCtl_CalcX(fCLK, n, n1, r1, s1, ret_n1, ret_p1);
	/* 減速区間 */
	RudCtl_CalcX(fCLK, n, n2, r2, s2, ret_n2, ret_p2);
	/* 目標速度移動区間 */
	p = P - ((ret_p1[0] + ret_p1[1] + ret_p1[2]) +
	         (ret_p2[0] + ret_p2[1] + ret_p2[2]));
	for(i = 2; p < 0; i--) {
#ifdef  WIN32
		printf("%d段階目省略\n", i + 1);
#endif//WIN32
		p += (ret_p1[i] + ret_p2[i]);			//┐
		      ret_p1[i] = ret_p2[i] = 0;		//├※TODO:厳密ではない。本当は、マイナス分だけ加(減)速を減らして、目標速度移動量は0のままにするのが正しい。修正を検討せよ。
		n  = (ret_n1[i] + ret_n2[i] + 1) / 2;		//┘
	}
	/* 結果格納 */
	*ret_n = n;
	*ret_p = p;
}
/*--------------------------------------------------------------------------*/
#ifdef  WIN32
/* テストスイート */
void RudCtl_Calc_TestSuite() {
	//[in]
	unsigned fCLK;		/* 基本クロック[pps] */
	unsigned P;		/* 全体移動量[pulse] */
	unsigned n;		/* 目標分周比 */		//補正前の値
	unsigned n1;		/* 起動分周比 */
	unsigned r1, s1;	/* 加速度[pps/clk] */
	unsigned n2;		/* 停止分周比 */
	unsigned r2, s2;	/* 減速度[pps/clk] */
	//[out]
	unsigned ret_n;		/* 目標分周比 */		//補正後の値
	unsigned ret_p;		/* 目標速度移動量[pulse] */
	unsigned ret_n1[3];	/* 加速分周比1,2,3 */
	unsigned ret_p1[3];	/* 加速区間移動量1,2,3[pulse] */
	unsigned ret_n2[3];	/* 減速分周比1,2,3 */
	unsigned ret_p2[3];	//減速区間移動量1,2,3[pulse]
	/* 初回プロンプト */
	printf(	"以下の要素を全て入力してください。\n");
	printf(	"省略する要素は0を入力してください。\n");
	printf(	"終了するときは^Zを入力してください。\n");
	for(;;) {
		/* 毎回プロンプト */
		printf(	"\n");
		printf(	"1. 基本クロック[pps]\n");
		printf(	"2. 全体移動量[pulse]\n");
		printf(	"3. 目標分周比\n");
		printf(	"4. 起動分周比\n");
		printf(	"5. 加速度[pps/clk] Rate\n");
		printf(	"6. 〃              Scale\n");
		printf(	"7. 停止分周比\n");
		printf(	"8. 減速度[pps/clk] Rate\n");
		printf(	"9. 〃              Scale\n");
		/* 入力 */
		printf(	"\n");
		if(scanf(
			"%d"		/* 基本クロック[pps] */
			"%d"		/* 全体移動量[pulse] */
			"%d"		/* 目標分周比 */
			"%d"		/* 起動分周比 */
			"%d"		/* 加速度[pps/clk] */	//Rate
			"%d"		/* 〃 */		//Scale
			"%d"		/* 停止分周比 */
			"%d"		/* 減速度[pps/clk] */	//Rate
			"%d"		/* 〃 */		//Scale
			//
			,&fCLK		/* 基本クロック[pps] */
			,&P		/* 全体移動量[pulse] */
			,&n		/* 目標分周比 */
			,&n1		/* 起動分周比 */
			,&r1		/* 加速度[pps/clk] */	//Rate
			,&s1		/* 〃 */		//Scale
			,&n2		/* 停止分周比 */
			,&r2		/* 減速度[pps/clk] */	//Rate
			,&s2		/* 〃 */		//Scale
		)==EOF){break;}
		/* 初回入力が省略された要素の数値例を格納する。二回目以降は前回値のままとなる。 */
		if(!fCLK){fCLK=1000;}	/* 基本クロック[pps] */
		if(!P   ){P   = 100;}	/* 全体移動量[pulse] */
		if(!n   ){n   =   2;}	/* 目標分周比 */
		if(!n1  ){n1  =  10;}	/* 起動分周比 */
		if(!r1  ){r1  =   5;}	/* 加速度[pps/clk] */	//Rate
		if(!s1  ){s1  =   2;}				//Scale
		if(!n2  ){n2  =  n1;}	/* 停止分周比 */
		if(!r2  ){r2  =  r1;}	/* 減速度[pps/clk] */	//Rate
		if(!s2  ){s2  =  s1;}				//Scale
		/* 入力値を表示 */
		printf(	"\n"
			"基本クロック[pps]          %4d\n"
			"全体移動量[pulse]          %4d\n"
			"目標分周比                 %4d\n"
			"起動分周比                 %4d\n"
			"加速度[pps/clk] Rate       %4d\n"
			"〃              Scale      %4d\n"
			"停止分周比                 %4d\n"
			"減速度[pps/clk] Rate       %4d\n"
			"〃              Scale      %4d\n"
			//
			,fCLK		//基本クロック[pps]
			,P		//全体移動量[pulse]
			,n		//目標分周比
			,n1		//起動分周比
			,r1		//加速度[pps/clk]	//Rate
			,s1					//Scale
			,n2		//停止分周比
			,r2		//減速度[pps/clk]	//Rate
			,s2);					//Scale
		/* 計算 */
		printf(	"\n"
			"計算中…\n");
		RudCtl_Calc(
			fCLK,P,n,n1,r1,s1,n2,r2,s2,
			&ret_n,&ret_p,ret_n1,ret_p1,ret_n2,ret_p2);
		/* 結果を表示 */
		printf(	"\n");
		printf(	"目標分周比                 %4d\n",ret_n);
		printf(	"目標速度移動量[pulse]      %4d\n",ret_p);
		printf(	"加速分周比1,2,3            %4d %4d %4d\n",ret_n1[0],ret_n1[1],ret_n1[2]);
		printf(	"加速区間移動量1,2,3[pulse] %4d %4d %4d\n",ret_p1[0],ret_p1[1],ret_p1[2]);
		printf(	"減速分周比1,2,3            %4d %4d %4d\n",ret_n2[0],ret_n2[1],ret_n2[2]);
		printf(	"減速区間移動量1,2,3[pulse] %4d %4d %4d\n",ret_p2[0],ret_p2[1],ret_p2[2]);
		printf(	"全体移動量[pulse]          %4d\n",          ret_p1[0]+          ret_p1[1]+          ret_p1[2]+          ret_p2[0]+          ret_p2[1]+          ret_p2[2]+      ret_p);
		printf(	"全体移動時間[clk]          %4d\n",ret_n1[0]*ret_p1[0]+ret_n1[1]*ret_p1[1]+ret_n1[2]*ret_p1[2]+ret_n2[0]*ret_p2[0]+ret_n2[1]*ret_p2[1]+ret_n2[2]*ret_p2[2]+ret_n*ret_p);
	}
}
#endif//WIN32

/****************************************************************************
 *	パルスモーターの台形制御(Ramp-up/Down control) - 管理
 ****************************************************************************/
ST_RudCtl stRudCtl;			/* 台形制御 */
const ST_RudCtlReq stRudCtlReq_Never;	/* 空の実行要求 */
#ifdef  PRODUCT_SPECIFIC_WORKAROUND
//1動作につき最大1回まで、「+領域での正方向センサOnエッジ」「+領域での逆方向センサOffエッジ」「-領域での逆方向センサOnエッジ」「-領域での正方向センサOffエッジ」を無視する。
//こうした理由は、センサのキワで動作開始／停止した時に、機械のブレで誤ったセンサエッジを検出し、誤った位置補正を行ってしまう問題が生じたため、その問題を回避するためである。
//1動作につき最大1回までとした理由は、起動直後の初期動作中は位置未検出なので、上記の検出も正常で有り得るからである。	//┐
//初期動作中に上記の検出を1回無視しても、2回目の検出を有効とすれば実用上問題無い。初期動作が少し長くなるだけである。	//┴初期動作中の考慮について、左記の方法だけでは不充分であった。直線移動の場合に1回目を無視すると、2回目の検出ができないから。そこで、初期動作中は不正(と思われる)センサエッジも無視しないように変更した。RudCtlCh_MoveInit()の実装を参照せよ。		//⇒2014/5/6変更:直線移動のセンサエッジを無視しない方法を変更し、RudCtlCh_Exec()で判断するようにしました。この変更によって、初期動作時の特別処理は不要になりました。
#if     (RudCtlCh_Max>8)
#error  SpuriousSnsEdgeDetectedをuint8_t⇒uint16_tに変更してください。
#endif//(RudCtlCh_Max>8)
static uint8_t SpuriousSnsEdgeDetected; //現在の1動作中に不正(と思われる)センサエッジを検出し無視したことを示す(bit0=チャネル0,bit1=チャネル1,…)
#endif//PRODUCT_SPECIFIC_WORKAROUND
/*--------------------------------------------------------------------------*/
/* 台形制御初期化 */
void RudCtl_Init(const ST_RudCtlInfo* pRudCtlInfo) {
	/* 台形制御をクリアする。 */
	memset(&stRudCtl, 0, sizeof(ST_RudCtl));
	/* 台形制御情報へのポインタを格納する。 */
	stRudCtl.pRudCtlInfo = pRudCtlInfo;
}
/*--------------------------------------------------------------------------*/
/* 台形制御チャネルの、基本クロック実行 */					//割込禁止状態で呼出すこと。当関数は割込制御を行っていない。
static void RudCtlCh_Exec(int iRudCtlCh, int Sns/*センサ状態のビット並び*/) {
	ST_RudCtlCh* pRudCtlCh;
	ST_RudCtlAct* pRudCtlAct;
	const ST_RudCtlChInfo* pRudCtlChInfo;
	unsigned Sum, n, p;
	int SnsEdge, i;
	/* 台形制御チャネルを取得する。 */
	if((unsigned)iRudCtlCh > RudCtlCh_Max) { DIE(); }
	pRudCtlCh = &stRudCtl.TBL_RudCtlCh[iRudCtlCh];
	pRudCtlAct = &pRudCtlCh->Act;
	pRudCtlChInfo = &stRudCtl.pRudCtlInfo->TBL_RudCtlChInfo[iRudCtlCh];
	/* センサ状態を更新する。 */
	SnsEdge = 0;						/* ここにセンサエッジを求める。(0=センサエッジ無し,1=センサOnエッジ,-1=センサOffエッジ) */
	if((Sns &= pRudCtlChInfo->Sns) == pRudCtlCh->Sns) {	/* センサ状態のビット並びに、センサマスクを適用する。センサ状態が安定状態と同じならば… */
		pRudCtlCh->SnsCnt = 0;				/* 次回のセンサ状態変化時に備えてSnsCntを0にする。 */						//センサ状態が一瞬変化した後、元の状態に戻った場合のために、この処理が必要である。この処理を行わないと、一瞬変化による(SnsCnt+1)が蓄積して誤って安定状態と判定してしまう可能性がある。
	} else {						/* センサ状態が安定状態と違うならば… */
		if(++pRudCtlCh->SnsCnt >= pRudCtlCh->Spd) {	/* SnsCntを+1し、(SnsCnt>=Spd)になったら安定状態と判断する。 */
			pRudCtlCh->SnsCnt = 0;			/* 次回のセンサ状態変化時に備えてSnsCntを0にする。 */						//次回即センサ状態が変化した場合のために、この処理が必要である。この処理を行わないと、次回即センサ状態が変化した場合、(SnsCnt=0)に戻っておらず、安定状態と判定してしまう可能性がある。
			pRudCtlCh->Sns = Sns;			/* 安定状態を格納する。 */
			SnsEdge = Sns ? 1/*センサOnエッジ*/ : -1/*センサOffエッジ*/;
#ifdef  PRODUCT_SPECIFIC_WORKAROUND
			//1動作の初回RudCtlCh_Exec()はRudCtlCh_Move()から呼出され、必ずセンサエッジ無しでありこちらのブロックは通らない。SpuriousSnsEdgeDetectedをクリアする前にこちらのブロックを通る心配は無い。
			if(!(SpuriousSnsEdgeDetected & (1<<iRudCtlCh))) {
				//「+領域(+)での正方向(+)センサOnエッジ(+)」 3要素のXor⇒(+)─┐
				//「+領域(+)での逆方向(-)センサOffエッジ(-)」3要素のXor⇒(+)─┤
				//「-領域(-)での逆方向(-)センサOnエッジ(+)」 3要素のXor⇒(+)─┤
				//「-領域(-)での正方向(+)センサOffエッジ(-)」3要素のXor⇒(+)─┤
				if((pRudCtlCh->Pos ^ (-pRudCtlAct->d) ^ SnsEdge) >= 0) {//←─┘これで判断できる。
				    //{{2014/06/11変更:「センサエッジ付近で停止した際、停止励磁中に振動して不正な方向のセンサエッジを複数回検出し、2回目を本物と見なして採用してしてしまう」という問題が生じた。問題を回避するために、停止中,又は,停止励磁中は、SpuriousSnsEdgeDetectedのビットをセットしないことにした。
				    //	SpuriousSnsEdgeDetected |= (1<<iRudCtlCh);
				    //↓2014/06/11変更:「センサエッジ付近で停止した際、停止励磁中に振動して不正な方向のセンサエッジを複数回検出し、2回目を本物と見なして採用してしてしまう」という問題が生じた。問題を回避するために、停止中,又は,停止励磁中は、SpuriousSnsEdgeDetectedのビットをセットしないことにした。
					if(pRudCtlCh->Spd) { SpuriousSnsEdgeDetected |= (1<<iRudCtlCh); }
				    //}}2014/06/11変更:「センサエッジ付近で停止した際、停止励磁中に振動して不正な方向のセンサエッジを複数回検出し、2回目を本物と見なして採用してしてしまう」という問題が生じた。問題を回避するために、停止中,又は,停止励磁中は、SpuriousSnsEdgeDetectedのビットをセットしないことにした。
					//ただし、直線移動の場合は、この補正を行わない。位置がずれて誤って「-領域(-)での逆方向(-)センサOnエッジ(+)」と見なし無視してしまうと、原点を検出できなくなるから。
					//直線移動の場合(厳密には移動範囲の端にセンサ検出範囲が有る直線移動の場合)は、センサエッジ箇所が一か所しか無いので、元々補正が必要無いのであるが、処理の都合上、ここで判断することにした。
					//よって、下記のセンサエッジを無視する処理は、直線移動でない場合に対してのみ行う。
					//直線移動かどうかを判断する方法は、両方向のセンサOnエッジ位置が同じ値に設定されているかどうかで判断することにした。
					//直線移動の順方向センサOnエッジは無いので、ダミー値であるが、逆方向センサOnエッジと同じ値を設定せよ。
					if(pRudCtlChInfo->Dir[0].PosSns != pRudCtlChInfo->Dir[1].PosSns) { SnsEdge = 0; } //直線移動でない(=回転移動である)場合のみ、センサエッジを無視する。
				}
			}
#endif//PRODUCT_SPECIFIC_WORKAROUND
			//順方向センサOnエッジ検出時、 Dir[0].PosSns  に書き換える。逆方向センサOnエッジ検出時、 Dir[1].PosSns  に書き換える。			//┐
			//逆方向センサOffエッジ検出時、Dir[0].PosSns-1に書き換える。順方向センサOffエッジ検出時、Dir[1].PosSns+1に書き換える。			//│停止後のセンサ検出でも、位置補正を行うのが適切であると考えた。
			if(SnsEdge > 0) { pRudCtlCh->Pos = !pRudCtlAct->d ?  pRudCtlChInfo->Dir[0].PosSns    :  pRudCtlChInfo->Dir[1].PosSns   ; }		//│停止中ならば、前回移動方向によって順方向か順方向かを判断する。
			if(SnsEdge < 0) { pRudCtlCh->Pos =  pRudCtlAct->d ? (pRudCtlChInfo->Dir[0].PosSns-1) : (pRudCtlChInfo->Dir[1].PosSns+1); }		//┘
		}
	}
	/* 現在動作中ならば… */
	if(pRudCtlCh->Spd) {
#ifdef  PRODUCT_SPECIFIC_WORKAROUND
		//1動作の初回RudCtlCh_Exec()はRudCtlCh_Move()から呼出され、ここでSpuriousSnsEdgeDetectedをクリアする。
		if(!pRudCtlCh->Cnt) { SpuriousSnsEdgeDetected &= ~(1<<iRudCtlCh); }
#endif//PRODUCT_SPECIFIC_WORKAROUND
		/* センサイベント処理を行う。 */																			//┐
		if(((SnsEdge > 0) && (pRudCtlCh->ReqEvt1.Cond & RudCtlReq_When_SnsRise)) ||	/* RudCtlReq_When_SnsRise,又は,RudCtlReq_When_SnsEdge */						//│「センサエッジ検出時に位置を書き換える処理」と「センサイベント処理」の処理順序は、左記のとおり、「センサエッジ検出時に位置を書き換える処理」が先でなくてはならない。
		   ((SnsEdge < 0) && (pRudCtlCh->ReqEvt1.Cond & RudCtlReq_When_SnsFall))) {	/* RudCtlReq_When_SnsFall,又は,RudCtlReq_When_SnsEdge */						//│センサイベントが絶対位置指定された場合、センサエッジ検出時に補正された位置を基準として動作すべきだからである。
			/* 実行中動作要求(イベント実行要求)を実行する。 */																//│
			RudCtlCh_Move(iRudCtlCh, pRudCtlCh->ReqEvt1, stRudCtlReq_Never);	//RudCtlCh_Move()がReqEvt1,ReqImm2,ReqEvt2を確実にクリアするので、ここでの明示的なクリア処理は不要。	//│厳密には、「センサエッジ検出時に位置を書き換える処理」と「センサイベント処理」を、「位置トラッキング処理」よりも後で行うべきかも知れないが、実用上は問題無いだろう。
			return; /* ここまで */																				//│考え様によっては、RudCtlCh_Move()で開始直後明示的にRudCtlCh_Exec()を呼んでいる分、「位置トラッキング処理」が一回先へ進んでいるとも考えられるので、現在の処理順のままの方が位置トラッキング処理が一回分相殺されて、却って正しいかも知れない。
		}																							//┘
		/* 位置トラッキング処理を行う。 */
		Sum = 0;						/* 区間(i)までの合計移動時間 */
		for(i = 0; i < (3+1+3); i++) {				/* 加速区間1,2,3、目標速度移動区間、減速区間3,2,1 */
			n = pRudCtlAct->n[i];				/* 加速分周比1,2,3、目標分周比、減速分周比3,2,1 */
			p = pRudCtlAct->p[i];				/* 加速区間移動量1,2,3[pulse]、目標速度移動量[pulse]、減速区間移動量3,2,1[pulse] */
			if((Sum += n * p) > pRudCtlCh->Cnt) {		/* 区間(i)までの合計移動時間が、現在経過時間を超過したら… */
				pRudCtlCh->Spd = n;			/* 区間(i)の分周比を、現在分周比に格納する。 */
				if(++pRudCtlCh->DivCnt >= n) {		/* 現在分周比の分周カウンタを増やし、区間(i)の分周比に到達したら… */
					pRudCtlCh->DivCnt = 0;		/* 現在分周比の分周カウンタをリセットする。 */
					if(pRudCtlCh->BacCnt) {		/* バックラッシカウンタが残っていたら… */
						pRudCtlCh->BacCnt--;	/* バックラッシカウンタを減らす。 */			//BacCntが残っている間、Posを変化させない。
					} else {			/* バックラッシカウンタが残ってなければ… */
						if(!pRudCtlAct->d) {	/* 順方向の場合、位置が順方向限界位置未満ならば、増やす。位置が順方向限界位置以上ならば、順方向修正位置に書き換える。 */
							pRudCtlCh->Pos = (pRudCtlCh->Pos < pRudCtlChInfo->Dir[0].PosLim) ? (pRudCtlCh->Pos + 1) : pRudCtlChInfo->Dir[0].PosFix;
						} else {		/* 逆方向の場合、位置が逆方向限界位置超過ならば、減らす。位置が逆方向限界位置以下ならば、逆方向修正位置に書き換える。 */
							pRudCtlCh->Pos = (pRudCtlCh->Pos > pRudCtlChInfo->Dir[1].PosLim) ? (pRudCtlCh->Pos - 1) : pRudCtlChInfo->Dir[1].PosFix;
						}
					}
					//＜＜もし今後自分でモータードライバを実装する場合は、ここでパルスを出力すれば良い。＞＞
				}
				/* 現在経過時間を増やす。 */
				if(++pRudCtlCh->Cnt) { return; } /* ここまで */
				/* Cntがオーバーフローして0に戻ったら、停止と見なし、継続して以下の処理を行うことにした。 */	//極端に長時間,又は,極端に長距離の動作パラメータが指定された場合に起こる可能性が有るが、実用上はまず起こらないだろう。
				break; /* 停止処理へ */
			}
		}
		/* 現在分周比に、停止中を意味する0を格納する。 */
		pRudCtlCh->Spd = 0;
		/* 停止励磁時間が無ければ、直ちに停止後動作要求を実行する。 */
		if(!pRudCtlCh->StpTim) { goto L_STOP; }
		/* 停止励磁時間が有れば、次回からこの下の処理で残り停止励磁時間を減らして行き、停止励磁終了した時に停止後動作要求を実行する。 */
	/* 現在停止中,かつ,停止励磁中ならば… */
	} else if(pRudCtlCh->StpTim) {
		/* 残り停止励磁時間を減らし、0になったら(=停止励磁終了したら)… */
		if(!--pRudCtlCh->StpTim) {
L_STOP:			//＜＜もし今後自分でモータードライバを実装する場合は、ここで励磁を終了すれば良い。＞＞
//不要			/* 停止後動作要求が有れば… */
//不要			if(pRudCtlCh->ReqImm2.Cond || pRudCtlCh->ReqEvt2.Cond) {			//RudCtlCh_Move()がReqImm2.Condを判定するので、ここでの明示的な判定は不要。
				/* 停止後動作要求を実行する。 */
				RudCtlCh_Move(iRudCtlCh, pRudCtlCh->ReqImm2, pRudCtlCh->ReqEvt2);	//RudCtlCh_Move()がReqEvt1,ReqImm2,ReqEvt2を確実にクリアするので、ここでの明示的なクリア処理は不要。
				return; /* ここまで */
//不要			}
		}
	}
}
/*--------------------------------------------------------------------------*/
/* 基本クロック実行 */
void RudCtl_Exec(int Sns/*センサ状態のビット並び*/) {
	int iRudCtlCh;
ENTER_CS;//割込外から呼ばれた場合のために、確実に割込制御を行う。
	/* 各台形制御チャネルについて… */
	for(iRudCtlCh = 0; iRudCtlCh <= RudCtlCh_Max; iRudCtlCh++) {
		/* 台形制御チャネルの、基本クロック実行を呼出す。 */
		RudCtlCh_Exec(iRudCtlCh, Sns);
	}
LEAVE_CS;//割込外から呼ばれた場合のために、確実に割込制御を行う。
}
/*--------------------------------------------------------------------------*/
/* 動作開始 */
void RudCtlCh_Move(int iRudCtlCh, ST_RudCtlReq ReqImm/*即時実行要求*/, ST_RudCtlReq ReqEvt/*イベント実行要求*/) {
	ST_RudCtlCh* pRudCtlCh;
	ST_RudCtlAct* pRudCtlAct;
	const ST_RudCtlChInfo* pRudCtlChInfo;
	int p2sum;
	/* 動作パラメータ */
	int      d;	/* 移動方向 */
	unsigned n;	/* 目標分周比 */		//補正後の値
	unsigned p;	/* 目標速度移動量[pulse] */
	unsigned n1[3];	/* 加速分周比1,2,3 */
	unsigned p1[3];	/* 加速区間移動量1,2,3[pulse] */
	unsigned n2[3];	/* 減速分周比1,2,3 */
	unsigned p2[3];	/* 減速区間移動量1,2,3[pulse] */
	/* 台形制御チャネルを取得する。 */
	if((unsigned)iRudCtlCh > RudCtlCh_Max) { DIE(); }
	pRudCtlCh = &stRudCtl.TBL_RudCtlCh[iRudCtlCh];
	pRudCtlAct = &pRudCtlCh->Act;
	pRudCtlChInfo = &stRudCtl.pRudCtlInfo->TBL_RudCtlChInfo[iRudCtlCh];
ENTER_CS;
	/* 即時実行要求の実行条件を判定する。 */			//即時実行要求の実行条件を満たさなかった場合、イベント実行要求が指定されていても破棄することに注意せよ。
	if(( pRudCtlCh->Sns && (ReqImm.Cond & RudCtlReq_If_SnsOn )) ||	/* センサOn 、かつ、RudCtlReq_If_SnsOn ,又は,RudCtlReq_If_Alwaysならば、実行する。 */
	   (!pRudCtlCh->Sns && (ReqImm.Cond & RudCtlReq_If_SnsOff))) {	/* センサOff、かつ、RudCtlReq_If_SnsOff,又は,RudCtlReq_If_Alwaysならば、実行する。 */
		/* これ以降の判定で、即時実行要求が即時実行されず、停止後動作要求に格納された場合のために、実行条件を「無条件で実行」に書き換えておく。 */	//┬要検討。{ReqImm.Cond=If_SnsOff,ReqEvt.Cond=When_SnsRise}で呼出され、ここでReqImm.Cond=If_Alwaysに書き換えた後、もし停止後動作要求に格納され、	┬解決した。イベント実行要求を即時実行要求の移動方向と同方向の動作に付随させれば良い。
		ReqImm.Cond = RudCtlReq_If_Always;														//┘もし最短停止の動作中にSnsOnになると、不正な動作になるのではないか?停止後動作要求として実行された後、発生しないSnsRiseを待ち続けることになる。	┘「現在動作中⇒現在移動方向と同方向⇒全体移動量が最短停止距離未満」の処理を参照せよ。
		/* 実行中動作要求を確実にクリアする。 */
//不要		pRudCtlCh->ReqImm1 = stRudCtlReq_Never;	/* 実行中動作要求(即時実行要求) */
		pRudCtlCh->ReqEvt1 = stRudCtlReq_Never;	/* 実行中動作要求(イベント実行要求) */
		/* 停止後動作要求を確実にクリアする。 */
		pRudCtlCh->ReqImm2 = stRudCtlReq_Never;	/* 停止後動作要求(即時実行要求) */
		pRudCtlCh->ReqEvt2 = stRudCtlReq_Never;	/* 停止後動作要求(イベント実行要求) */
		/* 絶対位置ならば、相対位置に変換する。 */
		if(ReqImm.PosType) {/*(==RudCtlReq_Abs)*/
			ReqImm.PosType = RudCtlReq_Rel;
			ReqImm.Pos   -= pRudCtlCh->Pos;
		}
		/* 移動方向を判定する。 */
		if(ReqImm.Pos > 0) {			/* 全体移動量が正ならば… */
			d = 0;				/* 順方向 */
		} else if(ReqImm.Pos < 0) {		/* 全体移動量が負ならば… */
			ReqImm.Pos = -ReqImm.Pos;	/* 全体移動量を正にする。 */
			d = 1;				/* 逆方向 */
		} else {/*(ReqImm.Pos==0)*/		/* 全体移動量が0ならば… */
			d = pRudCtlAct->d;		/* 前回移動方向と同方向と見なす。 */
		}
		/* 現在動作中ならば…(停止励磁中は含まない) */
		if(pRudCtlCh->Spd) {
			/* 現在移動方向の、最短停止の動作パラメータを計算する。 */
			RudCtl_CalcX(
				//[in]
				stRudCtl.pRudCtlInfo->fCLK,										/* 基本クロック[pps] */
				pRudCtlCh->Spd,												/* 目標分周比 */		//=現在分周比
				(pRudCtlCh->Spd >= pRudCtlChInfo->Dir[pRudCtlAct->d].n2) ? pRudCtlCh->Spd				/* ┬停止分周比 */		//現在分周比が停止分周比よりも大きい(=遅い)場合は、現在分周比で停止する。
				                                                         : pRudCtlChInfo->Dir[pRudCtlAct->d].n2,	/* ┘           */
				pRudCtlChInfo->Dir[pRudCtlAct->d].r2, pRudCtlChInfo->Dir[pRudCtlAct->d].s2,				/* 減速度[pps/clk] */
				//[out]
				n2,													/* 減速分周比1,2,3 */
				p2);													/* 減速区間移動量1,2,3[pulse] */
			/* 最短停止距離を求める。 */
			p2sum = (p2[0] + p2[1] + p2[2]);
			/* 最短停止距離のうち、バックラッシカウンタで相殺される分は位置移動にならないので、引く。 */	//┬必要。これを行わないと、加速途中に反対方向への移動に切替えた場合に、戻りすぎてしまうバグになる。
			if((int)(p2sum -= pRudCtlCh->BacCnt) < 0) { p2sum = 0; }					//┘
			/* 現在移動方向と同方向ならば… */
			if(d == pRudCtlAct->d) {
				/* 全体移動量が最短停止距離以上ならば、停止中からの動作と同様とする。ただし、起動分周比を現在分周比とする。(ジャンプ先の処理中で判定している) */
				if(ReqImm.Pos >= p2sum) { goto L_FORE; }
				/* 全体移動量が最短停止距離未満ならば、行き過ぎた停止位置から戻る動作とする。 */
				ReqImm.Pos -= p2sum;			//┬停止後動作要求を、行き過ぎた停止位置からの相対位置指定に書き換える。
				if(d) { ReqImm.Pos = -ReqImm.Pos; }	//┘
				ReqImm.Spd = 0/*最速*/;			//最短停止後の移動方向が指定された即時移動要求と異方向なので、指定された速度に関係無く最速で戻ることにする。下記の「現在移動方向と異方向ならば…」のブロックは、最短停止後の移動方向が指定された即時移動要求と同方向なので、指定された速度をそのまま使用する。違いに注意せよ。
				/* 停止後動作要求を格納する。 */
				pRudCtlCh->ReqImm2 = ReqImm;	/* 停止後動作要求(即時実行要求) */
				pRudCtlCh->ReqEvt1 = ReqEvt;	/* 実行中動作要求(イベント実行要求) */	//←───────────────────────┐実行中動作要求(=最短停止動作)が、即時実行要求の移動方向と同方向の動作であるので、イベント実行要求を、実行中動作要求(=最短停止動作)に付随させる。
			/* 現在移動方向と異方向ならば… */												//│
			} else {															//│
				/* 即時実行要求の全体移動量に、行き過ぎた停止位置から戻る分の距離を加算する。 */					//│
				ReqImm.Pos += p2sum;			//┬停止後動作要求を、行き過ぎた停止位置からの相対位置指定に書き換える。	//├違いに注意せよ
				if(d) { ReqImm.Pos = -ReqImm.Pos; }	//┘										//│
				/* 停止後動作要求を格納する。 */											//│
				pRudCtlCh->ReqImm2 = ReqImm;	/* 停止後動作要求(即時実行要求) */							//│
				pRudCtlCh->ReqEvt2 = ReqEvt;	/* 停止後動作要求(イベント実行要求) */	//←───────────────────────┘停止後動作要求が、即時実行要求の移動方向と同方向の動作であるので、イベント実行要求を、停止後動作要求に付随させる。
			}
			/* 動作パラメータを格納する。 */
//不要			RudCtlAct_d(  pRudCtlAct)	= pRudCtlAct->d;	/* 移動方向 */			//=現在移動方向
			RudCtlAct_n11(pRudCtlAct)	= pRudCtlCh->Spd;	/* 加速分周比1 */		//=現在分周比
			RudCtlAct_n12(pRudCtlAct)	= pRudCtlCh->Spd;	/* 加速分周比2 */		//=現在分周比
			RudCtlAct_n13(pRudCtlAct)	= pRudCtlCh->Spd;	/* 加速分周比3 */		//=現在分周比
			RudCtlAct_n(  pRudCtlAct)	= pRudCtlCh->Spd;	/* 目標分周比 */		//=現在分周比
			RudCtlAct_n23(pRudCtlAct)	= n2[2];		/* 減速分周比1 */
			RudCtlAct_n22(pRudCtlAct)	= n2[1];		/* 減速分周比2 */
			RudCtlAct_n21(pRudCtlAct)	= n2[0];		/* 減速分周比3 */
			RudCtlAct_p11(pRudCtlAct)	= 0;			/* 加速区間移動量3[pulse] */	//=0
			RudCtlAct_p12(pRudCtlAct)	= 0;			/* 加速区間移動量2[pulse] */	//=0
			RudCtlAct_p13(pRudCtlAct)	= 0;			/* 加速区間移動量1[pulse] */	//=0
			RudCtlAct_p(  pRudCtlAct)	= 0;			/* 目標速度移動量[pulse] */	//=0
			RudCtlAct_p23(pRudCtlAct)	= p2[2];		/* 減速区間移動量3[pulse] */
			RudCtlAct_p22(pRudCtlAct)	= p2[1];		/* 減速区間移動量2[pulse] */
			RudCtlAct_p21(pRudCtlAct)	= p2[0];		/* 減速区間移動量1[pulse] */
			/* 動作を開始する。 */
			pRudCtlCh->Cnt    =  0;
//不要			pRudCtlCh->Spd    = -1;		/* 動作中であることを示すための仮の値。この後すぐ、RudCtlCh_Exec()で正しい値が設定される。 */	//RudCtlCh_Exec()の「センサ状態を更新する」処理で、即安定状態と判定してしまわないよう、大きな値とする。1は不可。2以上ならば何でも同じだが、確実のため最大値とすることにした。
			pRudCtlCh->DivCnt =  0;
			pRudCtlCh->StpTim = pRudCtlChInfo->Dir[pRudCtlAct->d].StpTim;
			(*stRudCtl.pRudCtlInfo->fnMove)(iRudCtlCh, pRudCtlAct);		//┬この順序は必須である。RudCtlCh_Exec()からRudCtlCh_Move()が呼ばれる可能性が有り、先に即時実行要求のfnMove()を実行しておく必要が有る。
			RudCtlCh_Exec(iRudCtlCh, pRudCtlCh->Sns);			//┘もし順序を逆にすると、次のfnMove()を実行した後に、即時実行要求のfnMove()を実行してしまうバグとなる。
		/* 現在停止中(停止励磁中も含む),かつ,全体移動量が0でなければ… */					//現在停止中,かつ,全体移動量が0ならば、何もしない。この判断を行わないと、「move 0」「move 0」…と実行した時、停止励磁のみのデバイス動作を都度実行してしまう。見た目上はそれでも構わないが、電力の無駄である。
		} else if(ReqImm.Pos) {
			/* 前回移動方向と異方向ならば… */
			if(d != pRudCtlAct->d) {
				/* バックラッシカウンタを設定する。 */
				pRudCtlCh->BacCnt = pRudCtlChInfo->Bac - pRudCtlCh->BacCnt;	/* BacCntを消費し切る前に再逆転した場合のために、BacCntに残っている値を引く。 */
				/* 全体移動量に加算する。 */
				ReqImm.Pos += pRudCtlCh->BacCnt;
			}
L_FORE://「現在動作中⇒現在移動方向と同方向⇒全体移動量が最短停止距離以上」だった場合のジャンプ位置
			/* 指定された目標分周比が、目標分周比の下限値未満ならば、補正する。 */
			if(ReqImm.Spd < pRudCtlChInfo->Dir[d].n) { ReqImm.Spd = pRudCtlChInfo->Dir[d].n; }
			/* 指定された目標分周比が、基本クロック超過ならば、エラー停止する。 */				//これは安全対策である。理論上は目標分周比の上限は無いのであるが、実際には極端に遅いと一つの相に励磁したまま長時間動作中の扱いとなりモーターが焼損する危険がある。そこで1[pps]に制限することにした。目標分周比が極端に大きいケースは、おそらく引数ReqImm.Spd,又は,ReqEvt.Spdの初期化し忘れのバグであろう。
			if(ReqImm.Spd > stRudCtl.pRudCtlInfo->fCLK) { DIE(); }
			/* 動作パラメータを計算する。 */
			RudCtl_Calc(
				//[in]
				stRudCtl.pRudCtlInfo->fCLK,								/* 基本クロック[pps] */
				ReqImm.Pos,										/* 全体移動量[pulse] */
				ReqImm.Spd,										/* 目標分周比 */		//補正前の値
				pRudCtlCh->Spd ? pRudCtlCh->Spd								/* ┬起動分周比 */		//─現在動作中の処理から飛んで来た場合
				               : (ReqImm.Spd >= pRudCtlChInfo->Dir[d].n1) ? ReqImm.Spd			/* │           */		//┬現在停止中の処理から普通に来た場合		//目標分周比が起動分周比よりも大きい(=遅い)場合は、目標分周比で起動する。
				                                                          : pRudCtlChInfo->Dir[d].n1,	/* ┘           */		//┘
				pRudCtlChInfo->Dir[d].r1, pRudCtlChInfo->Dir[d].s1,					/* 加速度[pps/clk] */
				(ReqImm.Spd >= pRudCtlChInfo->Dir[d].n2) ? ReqImm.Spd					/* ┬停止分周比 */								//目標分周比が停止分周比よりも大きい(=遅い)場合は、目標分周比で停止する。
				                                         : pRudCtlChInfo->Dir[d].n2,			/* ┘           */
				pRudCtlChInfo->Dir[d].r2, pRudCtlChInfo->Dir[d].s2,					/* 減速度[pps/clk] */
				//[out]
				&n,											/* 目標分周比 */		//補正後の値
				&p,											/* 目標速度移動量[pulse] */
				n1,											/* 加速分周比1,2,3 */
				p1,											/* 加速区間移動量1,2,3[pulse] */
				n2,											/* 減速分周比1,2,3 */
				p2);											/* 減速区間移動量1,2,3[pulse] */
			/* 動作パラメータを格納する。 */
			RudCtlAct_d(  pRudCtlAct)	= d;		/* 移動方向 */
			RudCtlAct_n11(pRudCtlAct)	= n1[0];	/* 加速分周比1 */
			RudCtlAct_n12(pRudCtlAct)	= n1[1];	/* 加速分周比2 */
			RudCtlAct_n13(pRudCtlAct)	= n1[2];	/* 加速分周比3 */
			RudCtlAct_n(  pRudCtlAct)	= n;		/* 目標分周比 */
			RudCtlAct_n23(pRudCtlAct)	= n2[2];	/* 減速分周比1 */
			RudCtlAct_n22(pRudCtlAct)	= n2[1];	/* 減速分周比2 */
			RudCtlAct_n21(pRudCtlAct)	= n2[0];	/* 減速分周比3 */
			RudCtlAct_p11(pRudCtlAct)	= p1[0];	/* 加速区間移動量3[pulse] */
			RudCtlAct_p12(pRudCtlAct)	= p1[1];	/* 加速区間移動量2[pulse] */
			RudCtlAct_p13(pRudCtlAct)	= p1[2];	/* 加速区間移動量1[pulse] */
			RudCtlAct_p(  pRudCtlAct)	= p;		/* 目標速度移動量[pulse] */
			RudCtlAct_p23(pRudCtlAct)	= p2[2];	/* 減速区間移動量3[pulse] */
			RudCtlAct_p22(pRudCtlAct)	= p2[1];	/* 減速区間移動量2[pulse] */
			RudCtlAct_p21(pRudCtlAct)	= p2[0];	/* 減速区間移動量1[pulse] */
			/* 実行中動作要求を格納する。 */
//不要			pRudCtlCh->ReqImm1 = ReqImm;	/* 実行中動作要求(即時実行要求) */
			pRudCtlCh->ReqEvt1 = ReqEvt;	/* 実行中動作要求(イベント実行要求) */
			/* 動作を開始する。 */
			pRudCtlCh->Cnt    =  0;
			pRudCtlCh->Spd    = -1;		/* 動作中であることを示すための仮の値。この後すぐ、RudCtlCh_Exec()で正しい値が設定される。 */	//RudCtlCh_Exec()の「センサ状態を更新する」処理で、即安定状態と判定してしまわないよう、大きな値とする。1は不可。2以上ならば何でも同じだが、確実のため最大値とすることにした。
			pRudCtlCh->DivCnt =  0;
			pRudCtlCh->StpTim = pRudCtlChInfo->Dir[pRudCtlAct->d].StpTim;
			(*stRudCtl.pRudCtlInfo->fnMove)(iRudCtlCh, pRudCtlAct);		//┬この順序は必須である。RudCtlCh_Exec()からRudCtlCh_Move()が呼ばれる可能性が有り、先に即時実行要求のfnMove()を実行しておく必要が有る。
			RudCtlCh_Exec(iRudCtlCh, pRudCtlCh->Sns);			//┘もし順序を逆にすると、次のfnMove()を実行した後に、即時実行要求のfnMove()を実行してしまうバグとなる。
		}
	}
LEAVE_CS;
}
/*--------------------------------------------------------------------------*/
/* 動作停止 */
void RudCtlCh_Stop(int iRudCtlCh) {
	ST_RudCtlCh* pRudCtlCh;
	ST_RudCtlAct* pRudCtlAct;
	const ST_RudCtlChInfo* pRudCtlChInfo;
	/* 動作パラメータ */
	unsigned n2[3];	/* 減速分周比1,2,3 */
	unsigned p2[3];	/* 減速区間移動量1,2,3[pulse] */
	/* 台形制御チャネルを取得する。 */
	if((unsigned)iRudCtlCh > RudCtlCh_Max) { DIE(); }
	pRudCtlCh = &stRudCtl.TBL_RudCtlCh[iRudCtlCh];
	pRudCtlAct = &pRudCtlCh->Act;
	pRudCtlChInfo = &stRudCtl.pRudCtlInfo->TBL_RudCtlChInfo[iRudCtlCh];
ENTER_CS;
	/* 実行中動作要求を確実にクリアする。 */
//不要	pRudCtlCh->ReqImm1 = stRudCtlReq_Never;	/* 実行中動作要求(即時実行要求) */
	pRudCtlCh->ReqEvt1 = stRudCtlReq_Never;	/* 実行中動作要求(イベント実行要求) */
	/* 停止後動作要求を確実にクリアする。 */
	pRudCtlCh->ReqImm2 = stRudCtlReq_Never;	/* 停止後動作要求(即時実行要求) */
	pRudCtlCh->ReqEvt2 = stRudCtlReq_Never;	/* 停止後動作要求(イベント実行要求) */
	/* 現在動作中ならば…(停止励磁中は含まない) */
	if(pRudCtlCh->Spd) {
		/* 現在移動方向の、最短停止の動作パラメータを計算する。 */
		RudCtl_CalcX(
			//[in]
			stRudCtl.pRudCtlInfo->fCLK,										/* 基本クロック[pps] */
			pRudCtlCh->Spd,												/* 目標分周比 */		//=現在分周比
			(pRudCtlCh->Spd >= pRudCtlChInfo->Dir[pRudCtlAct->d].n2) ? pRudCtlCh->Spd				/* ┬停止分周比 */		//現在分周比が停止分周比よりも大きい(=遅い)場合は、現在分周比で停止する。
			                                                         : pRudCtlChInfo->Dir[pRudCtlAct->d].n2,	/* ┘           */
			pRudCtlChInfo->Dir[pRudCtlAct->d].r2, pRudCtlChInfo->Dir[pRudCtlAct->d].s2,				/* 減速度[pps/clk] */
			//[out]
			n2,													/* 減速分周比1,2,3 */
			p2);													/* 減速区間移動量1,2,3[pulse] */
		/* 動作パラメータを格納する。 */
//不要		RudCtlAct_d(  pRudCtlAct)	= pRudCtlAct->d;	/* 移動方向 */			//=現在移動方向
		RudCtlAct_n11(pRudCtlAct)	= pRudCtlCh->Spd;	/* 加速分周比1 */		//=現在分周比
		RudCtlAct_n12(pRudCtlAct)	= pRudCtlCh->Spd;	/* 加速分周比2 */		//=現在分周比
		RudCtlAct_n13(pRudCtlAct)	= pRudCtlCh->Spd;	/* 加速分周比3 */		//=現在分周比
		RudCtlAct_n(  pRudCtlAct)	= pRudCtlCh->Spd;	/* 目標分周比 */		//=現在分周比
		RudCtlAct_n23(pRudCtlAct)	= n2[2];		/* 減速分周比1 */
		RudCtlAct_n22(pRudCtlAct)	= n2[1];		/* 減速分周比2 */
		RudCtlAct_n21(pRudCtlAct)	= n2[0];		/* 減速分周比3 */
		RudCtlAct_p11(pRudCtlAct)	= 0;			/* 加速区間移動量3[pulse] */	//=0
		RudCtlAct_p12(pRudCtlAct)	= 0;			/* 加速区間移動量2[pulse] */	//=0
		RudCtlAct_p13(pRudCtlAct)	= 0;			/* 加速区間移動量1[pulse] */	//=0
		RudCtlAct_p(  pRudCtlAct)	= 0;			/* 目標速度移動量[pulse] */	//=0
		RudCtlAct_p23(pRudCtlAct)	= p2[2];		/* 減速区間移動量3[pulse] */
		RudCtlAct_p22(pRudCtlAct)	= p2[1];		/* 減速区間移動量2[pulse] */
		RudCtlAct_p21(pRudCtlAct)	= p2[0];		/* 減速区間移動量1[pulse] */
		/* 動作を開始する。 */
		pRudCtlCh->Cnt    =  0;
//不要		pRudCtlCh->Spd    = -1;		/* 動作中であることを示すための仮の値。この後すぐ、RudCtlCh_Exec()で正しい値が設定される。 */	//RudCtlCh_Exec()の「センサ状態を更新する」処理で、即安定状態と判定してしまわないよう、大きな値とする。1は不可。2以上ならば何でも同じだが、確実のため最大値とすることにした。
		pRudCtlCh->DivCnt =  0;
		pRudCtlCh->StpTim = pRudCtlChInfo->Dir[pRudCtlAct->d].StpTim;
		(*stRudCtl.pRudCtlInfo->fnMove)(iRudCtlCh, pRudCtlAct);		//┬この順序は必須である。RudCtlCh_Exec()からRudCtlCh_Move()が呼ばれる可能性が有り、先に即時実行要求のfnMove()を実行しておく必要が有る。
		RudCtlCh_Exec(iRudCtlCh, pRudCtlCh->Sns);			//┘もし順序を逆にすると、次のfnMove()を実行した後に、即時実行要求のfnMove()を実行してしまうバグとなる。
	}
LEAVE_CS;
}

/****************************************************************************
 *	アプリケーション用のユーティリティ関数
 ****************************************************************************/
/* 単純な制御用途ならば、RudCtlCh_Move()やRudCtlCh_Stop()を直接使用しなくても、下記の三種類の関数だけで用が足りると思います。 */
/*--------------------------------------------------------------------------*/
/* 起動直後の初期動作。
 * センサエッジ方向へ移動して、位置を検出した後、原点へ移動する。
 * 回転移動の場合、センサエッジへの最短方向になるとは限らないが、一周してセンサエッジに到達するので問題無い。
 */
void RudCtlCh_MoveInit(int iRudCtlCh) {
	const ST_RudCtlChInfo* pRudCtlChInfo;
	ST_RudCtlReq ReqImm, ReqEvt;
	if((unsigned)iRudCtlCh > RudCtlCh_Max) { DIE(); }
	pRudCtlChInfo = &stRudCtl.pRudCtlInfo->TBL_RudCtlChInfo[iRudCtlCh];
	ReqImm.Cond    = RudCtlReq_If_SnsOn;
	ReqImm.PosType = RudCtlReq_Rel;
	ReqImm.Pos     = (pRudCtlChInfo->Dir[0].PosLim - pRudCtlChInfo->Dir[1].PosLim + 1) * 2/*ミスステップを考慮して移動距離に2倍のマージンを設けた*/;
	ReqImm.Spd     = 0/*最速*/;
	ReqEvt.Cond    = RudCtlReq_When_SnsEdge;
	ReqEvt.PosType = RudCtlReq_Abs;
	ReqEvt.Pos     = 0/*原点*/;
	ReqEvt.Spd     = 0/*最速*/;
ENTER_CS;
	RudCtlCh_Move(iRudCtlCh, ReqImm, ReqEvt);	//←┐
	ReqImm.Cond    = RudCtlReq_If_SnsOff;		//　│センサ状態はOn,又は,Offであるから、
	ReqImm.Pos     = -ReqImm.Pos;			//　│どちらか一方だけが実行され、もう一方は無視される。
	RudCtlCh_Move(iRudCtlCh, ReqImm, ReqEvt);	//←┘
//#ifdef  PRODUCT_SPECIFIC_WORKAROUND												//┐
//	SpuriousSnsEdgeDetected |= (1<<iRudCtlCh);	//初期動作中は不正(と思われる)センサエッジも無視しない。		//│⇒2014/5/6変更:直線移動のセンサエッジを無視しない方法を変更し、RudCtlCh_Exec()で判断するようにしました。この変更によって、初期動作時の特別処理は不要になりました。
//#endif//PRODUCT_SPECIFIC_WORKAROUND												//┘
LEAVE_CS;
}
/*--------------------------------------------------------------------------*/
#if 0//単純な実装
/* 通常の移動(絶対位置指定)。
 * 目標位置への移動中にセンサエッジを検出したら、補正後(又はそのまま)の位置を基準にして目標位置へ再始動する。
 * 目標位置への移動中にセンサエッジを検出しなくても、目標位置に到達したら停止する。(=センサOn区間内,又は,センサOff区間内での移動)
 */
void RudCtlCh_MoveAbs(int iRudCtlCh, int Pos/*絶対位置*/, int Spd/*0=最速*/) {
	ST_RudCtlReq Req;
	Req.Cond    = RudCtlReq_If_Always/*=RudCtlReq_When_SnsEdge*/;
	Req.PosType = RudCtlReq_Abs;
	Req.Pos     = Pos;
	Req.Spd     = Spd;
	RudCtlCh_Move(iRudCtlCh, Req, Req);
}
#else//原点移動のみ特別扱いした実装
/* 通常の移動(絶対位置指定)。
 * 実用上、原点(0)移動時の停止位置のずれが大きいと目立つ事が多いので、ずれを低減する対策を行った。
 * 具体的には、目標位置が0(原点)である場合のみ特別扱いし、最悪ケースでもセンサOn区間内で停止する。
 * 要するに、RudCtlCh_MoveInit()と変更前のRudCtlCh_MoveAbs()の、折衷案である。
 */
void RudCtlCh_MoveAbs(int iRudCtlCh, int Pos/*絶対位置*/, int Spd/*0=最速*/) {
	ST_RudCtlCh* pRudCtlCh;
	const ST_RudCtlChInfo* pRudCtlChInfo;
	ST_RudCtlReq ReqImm, ReqEvt;
	if((unsigned)iRudCtlCh > RudCtlCh_Max) { DIE(); }
	pRudCtlCh = &stRudCtl.TBL_RudCtlCh[iRudCtlCh];
	pRudCtlChInfo = &stRudCtl.pRudCtlInfo->TBL_RudCtlChInfo[iRudCtlCh];
ENTER_CS;
	/* 原点移動以外であるか、又は、センサOn区間内での原点移動ならば… */
	if(Pos || pRudCtlCh->Sns) {
		/* 目標位置への移動中にセンサエッジを検出したら、補正後(又はそのまま)の位置を基準にして目標位置へ再始動する。 */
		/* 目標位置への移動中にセンサエッジを検出しなくても、目標位置に到達したら停止する。(=センサOn区間内,又は,センサOff区間内での移動) */
		ReqImm.Cond    = RudCtlReq_If_Always/*=RudCtlReq_When_SnsEdge*/;
		ReqImm.PosType = RudCtlReq_Abs;
		ReqImm.Pos     = Pos;
		ReqImm.Spd     = Spd;
		RudCtlCh_Move(iRudCtlCh, ReqImm, ReqImm);
	/* センサOff区間からの原点移動ならば… */
	} else {
		/* センサOn区間への進入時にセンサOnエッジを検出し、補正後(又はそのまま)の位置を基準にして原点へ再始動する。 */
		/* ミスステップを考慮して移動距離に2倍のマージンを設けたが、もしそれでもセンサOnエッジを検出できない場合は、あきらめて停止する。 */
		ReqImm.Cond    = RudCtlReq_If_SnsOff;
		ReqImm.PosType = RudCtlReq_Rel;
		ReqImm.Pos     = (pRudCtlChInfo->Dir[0].PosLim - pRudCtlChInfo->Dir[1].PosLim + 1) * 2/*ミスステップを考慮して移動距離に2倍のマージンを設けた*/;
		ReqImm.Spd     = Spd;
		ReqEvt.Cond    = RudCtlReq_When_SnsRise;
		ReqEvt.PosType = RudCtlReq_Abs;
		ReqEvt.Pos     = Pos;
		ReqEvt.Spd     = Spd;
		if((pRudCtlChInfo->Dir[0].PosSns == pRudCtlChInfo->Dir[1].PosSns) ||	//直線移動ならば、常に逆方向とする。現在位置を(+)と認識しているか(-)と認識しているかに関係無く、センサOff区間から見て原点は逆方向に有る。もし現在位置を(-)と認識しているならば、それは検出のズレであり、実際には(+)に居ると考えられる。尚、左記の条件式で直線移動と見なす理由は、RudCtlCh_Exec()内の、PRODUCT_SPECIFIC_WORKAROUND処理のコメントを参照せよ。
		   (pRudCtlCh->Pos > 0)) { ReqImm.Pos = -ReqImm.Pos; }			//回転移動ならば、現在位置を(+)と認識しているならば、逆方向とする。もし検出のズレで、実際の現在位置が(-)だったとしても、一周回って原点に到達することは可能である。尚、(0)であっても移動をRudCtlCh_Move()を省略してはいけない。移動に偶々(0)に居ただけかも知れないから。その場合、停止動作を開始しなければならない。本当に停止中の(0)ならば、RudCtlCh_Move()が判断して何も行わないので、ここで判断する必要は無い。
		RudCtlCh_Move(iRudCtlCh, ReqImm, ReqEvt);
	}
LEAVE_CS;
}
#endif
/*--------------------------------------------------------------------------*/
/* 通常の移動(相対位置指定)。
 * 目標位置への移動中にセンサエッジを検出しても、何もしない。
 * 動作開始時の位置を基準として、相対移動する目的であるから、絶対位置が補正されてもこの動作には関係無いから。
 */
void RudCtlCh_MoveRel(int iRudCtlCh, int Pos/*相対位置*/, int Spd/*0=最速*/) {
	ST_RudCtlReq Req;
	Req.Cond    = RudCtlReq_If_Always;
	Req.PosType = RudCtlReq_Rel;
	Req.Pos     = Pos;
	Req.Spd     = Spd;
	RudCtlCh_Move(iRudCtlCh, Req, stRudCtlReq_Never);
}

