UMEHOSHI ITA TOP PAGE    COMPUTER SHIEN LAB

EEPROM化のためのシンプルなモータ制御コード

このページは、UMEHOSHI ITA のCN6とCN7のPWD制御の一部をROM化して、 しかもRAM領域に「UME専用Hexコマンド」文字列を送信することで、プログラム送信や実行もでききる状態を維持する変更を 紹介しています。

前提知識と、目標

まず、以前に紹介したPWM操作用のハードのリンクがここにあり、 ここで示したハードを制御するROM化が目標です。
また、このページで紹介した[UMEHOSHI ITA]のプログラムを利用しています。
この制御で必要な[UMEHOSHI ITA]のAPIは、このリンクの呼び出しを使っています。

コードと、その概要

以下で示すROMコードは、RAM領域で動作するプログラムと似ている処理ですが、
このROMコードの利用中でも別途に通常のRAM領領域へのコードを、配置・実行できるようにグローバル変数を使わない作り方をしています。
それは、Cコンパイラが管理するスタック上の記憶領域のローカル変数だけを使って作っています。

umehoshiEditの開発ツールでC言語を使う場合、ソースを入れるフォルダ内に、 コードやデータのメモリ割り当て情報をリンカに与えるためのファイルのmylinkerscript.ldが入っていており 2つのプログラムをコンパイルした場合、同じ変数の記憶領域(ORIGIN = 0x80008000, LENGTH = 0x1000)を使うことになるので、単純には同時実行ができません。)
そこで、ROM化で使うコードではRAMで使う静的な変数の記憶域を使わない細工が必要にになります。

EEPROMのプログラムでは、ROM化後でもRAM領域へのプログラミングを行い場合、 スタックを使うauto変数は使えますが、グローバル、ヒープ、static変数が使えないということです。
それは、リンカが割り当てしない絶対アドレスを指定して記憶領域を変数として扱う方法で、利用者が自分でメモリの割り当てを管理する方法です。
この記憶域は、ここで示している ram_area3領域(先頭アドレスが0xA0009000からの3840byte)です。

下記プログラムでは、変数群をまとめた構造体のMortorCtrlを定義して、そのポインタを次のように、MTの名前で定義しています。
#define MT ((MortorCtrl *)0xA0009000) // ram_area3領域の絶対アドレスで、グローバル変数を定義しています。
これにより、例えば「MT->cn6PWM」ような表現で記憶域を扱う方法を使っています。

なお、全ての関数はリンカを使わないで、0x9D020000から始まるBASE_FUNCの番地から指定したアドレスに関数を割り当てています。

// app_pwm_esp32.c
// _RB15のLED制御はstartで点灯し、beepで点灯状態を反転しています。他の制御ではコメントにしています。
#include <xc.h> 
#include "common.h"

#define BASE_FUNC   0x9D020000 // EEPROM領域配置用(パワーONでないリセット操作の実行アドレス)
//#define BASE_FUNC 0x80005000 // RAM領域配置用

#define AdrStart     (BASE_FUNC+0x0010) 	// アプリの起動時の初期ルーチンのアドレス
#define AdrForward   (BASE_FUNC+0x0200)	// モータ前進ルーチンのアドレス
#define AdrGoBack    (BASE_FUNC+0x0300)	// モータ後進ルーチンのアドレス
#define AdrRight     (BASE_FUNC+0x0400)	// モータ右回転ルーチンのアドレス
#define AdrLeft      (BASE_FUNC+0x0500)	// モータ左回転ルーチンのアドレス
#define AdrUpLeft    (BASE_FUNC+0x0600)	// モータ左デューティ幅アップ
#define AdrDownLeft  (BASE_FUNC+0x0700)	// モータ左デューティ幅ダウン
#define AdrUpRight   (BASE_FUNC+0x0800)	// モータ右デューティ幅アップ
#define AdrDownRight (BASE_FUNC+0x0900)	// モータ右デューティ幅ダウン
#define AdrStop      (BASE_FUNC+0x0A00)	// モータ停止
#define AdrTimer2    (BASE_FUNC+0x0B00)	// 指定時間後にモータ停止のTimer2用アドレス
#define AdrBeep      (BASE_FUNC+0x0D00)	// ビープ音
#define AdrSetCN6    (BASE_FUNC+0x0E00)	// CN6(左側) コネクタの制御 
#define AdrSetCN7    (BASE_FUNC+0x0F00)	// CN7(右側) コネクタの制御 

#define UPDAOWN 0x1000	// デューティ幅の変更で使う値(この値を加算又は減算)

typedef struct {
	int cn6PWM;	// 現在の左デューティ幅で、初期設定
	int cn7PWM;	// 現在の右デューティ幅で、初期設定
	int time2Count;//Timer2割り込みでカウントアップ(下記stopCountまでカウント)
	int stopCount; // time2CountがstopCount以上で、time2CountがをゼロにしてモータをOFF
} MortorCtrl;

#define MT ((MortorCtrl *)0xA0009000;) // ram_area3領域の絶対アドレスで、グローバル変数を定義

__attribute__((address( AdrStart ))) void start (void);
void start()
{	// 左右のモータで、進行にバラツキがある場合、下記初期設定を調整して合わせる。
	MT->cn6PWM = 0x04FFF;	// 現在の左デューティ幅で、初期設定
	MT->cn7PWM = 0x04FFF;	// 現在の右デューティ幅で、初期設定
	MT->time2Count = 0;//Timer2割り込みでカウントアップ(下記stopCountまでカウント)
	MT->stopCount = 0x500;// time2CountがstopCount以上で、time2CountがをゼロにしてモータをOFF

	PORTBCLR = 0b0110000000001100;//モータ関連ポート(14,13,3,2)をゼロにする
	IEC0CLR = 0x00000200;//T2IE Timer2 Enable(割込み不可)");

	// 初期化ルーチン(リセット時の処理)を設定
	( (uint32_t *)_HANDLES)[_IDX_INIT_SUB_FUNC] =  (uint32_t ) AdrStart;
	__asm__ ("NOP");

	// Timer2の割り込み処理を置き換える
	( (uint32_t *)_HANDLES)[_IDX_TIMER_2_FUNC] =  (uint32_t ) AdrTimer2;
	PR2=0x09c3F;	// Timer2の周期設定
	
	_set_pwd_mode(1); // PWM モードへ変更
	T2CONbits.ON = 1; //Timer2の機能を有効

	MT->time2Count =0;
	_RB15 = 1; // LED D1点灯

	_clear_beep_code();
	_set_beep_code((uint8_t)0b00000100);//音パターン[・・・・]の登録
	_UM_PTR_GOTO_BEEP = NULL;//ループ生成OFF
}

// モータを停止する。(割り込み不可も行う)
__attribute__((address( AdrStop))) void stop(void);
void stop()
{
	IEC0CLR = 0x00000200;//T2IE Timer2 Enable(割込み不可)");
	OC5RS = OC1RS = OC4RS = OC3RS = 0;// モータすべてをOFF
	MT->time2Count = 0;
}

// タイマーで(stopCount 値に達するまで)の時間ごとに、モータを停止する。
// モータがオンで、移動し続けないようにする考慮で、stop内で割り込みを不可にしている。
__attribute__((address( AdrTimer2))) void timer2(void);
void timer2()
{
	IFS0CLR = 0x00000200; // Clear the timer interrupt status flag
	if( ++MT->time2Count >= MT->stopCount ){
		stop();// PWM パルス幅を0
		//_RB15 = 0; // LED1消灯
	}
}

// CN6(左側) コネクタの制御 dの値 0停止, 1:正転, -1:逆転
__attribute__((address( AdrSetCN6 ))) void setCN6(int);
void setCN6( int d){
	OC5RS = 0x0;	// CN6[1-2]制御(RB2)
	OC1RS = 0x0;	// CN6[3-4]制御(RB3)
	if(d==1){
		OC5RS = MT->cn6PWM;	// CN6[1-2]制御(RB2)
		OC1RS = 0x0;	// CN6[3-4]制御(RB3)
	} else if(d==-1){
		OC5RS = 0x0;	// CN6[1-2]制御(RB2)
		OC1RS = MT->cn6PWM;	// CN6[3-4]制御(RB3)
	}
}

// CN7(右側) コネクタの制御 dの値 0停止, 1:正転, -1:逆転
__attribute__((address( AdrSetCN7 ))) void setCN7(int);
void setCN7( int d){
	OC4RS = 0x0;	// CN7[3-4]制御(RB13)
	OC3RS = 0x0;	// CN7[1-2]制御(RB14)
	if(d==1){
		OC4RS = MT->cn7PWM;	// CN7[3-4]制御(RB13)
		OC3RS = 0x0;	// CN7[1-2]制御(RB14)
	} else if(d==-1){
		OC4RS = 0x0;	// CN7[3-4]制御(RB13)
		OC3RS = MT->cn7PWM;	// CN7[1-2]制御(RB14)
	}
}

__attribute__((address( AdrBeep ))) void beep (void);
void beep()
{
	_clear_beep_code();
	_set_beep_code((uint8_t)0b000000100);//音パターン[・・・・]の登録
	_UM_PTR_GOTO_BEEP = NULL;//ループ生成OFF
	_RB15 = ! _RB15;//確認用のLED1点灯反転
}

// モータ前進ルーチン
__attribute__((address( AdrForward  ))) void forward(void);
void forward()
{
	MT->time2Count = 0;
	MT->stopCount = 300;
	setCN6( 1 );// 左正転
	setCN7( 1 );// 右正転
	//_RB15 = ! _RB15;//確認用のLED1点灯反転
	IEC0SET = 0x00000200;//T2IE Timer2 Enable(割込み許可)");
}

// モータ後進ルーチン
__attribute__((address( AdrGoBack ))) void goback(void);
void goback()
{
	MT->time2Count = 0;
	MT->stopCount = 200;
	setCN6( -1 );// 左逆転
	setCN7( -1 );// 右逆転
	//_RB15 = ! _RB15;//確認用のLED1点灯反転
	IEC0SET = 0x00000200;//T2IE Timer2 Enable(割込み許可)");
}

// モータ右回転ルーチン
__attribute__((address( AdrRight ))) void right(void);
void right()
{
	MT->time2Count = 0;
	MT->stopCount = 100;
	setCN6( 1 );// 左正転
	setCN7( -1 );// 右逆転
	//_RB15 = ! _RB15;//確認用のLED1点灯反転
	IEC0SET = 0x00000200;//T2IE Timer2 Enable(割込み許可)");
}

// モータ左回転ルーチン
__attribute__((address( AdrLeft ))) void left(void);
void left()
{
	MT->time2Count = 0;
	MT->stopCount = 100;
	setCN6( -1 );// 左逆転
	setCN7( 1 );// 右正転
	//_RB15 = ! _RB15;//確認用のLED1点灯反転
	IEC0SET = 0x00000200;//T2IE Timer2 Enable(割込み許可)");
}

// モータ左デューティ幅アップルーチン
__attribute__((address( AdrUpLeft ))) void upleft(void);
void upleft()
{
	//_send_hex_low( cn6PWM );
	if( MT->cn6PWM +UPDAOWN <= 0x0ffff){
		MT->cn6PWM += UPDAOWN ;
		//_send_string(" Left UP\r\n");
	} else {
		beep();
		//_send_string(" Left FULL\r\n");
	}
	//_RB15 = ! _RB15;//確認用のLED1点灯反転
}

// モータ左デューティ幅ダウンルーチン
__attribute__((address( AdrDownLeft ))) void downleft(void);
void downleft()
{
	//_send_hex_low( MT->cn6PWM );
	if( MT->cn6PWM - UPDAOWN >= 0){
		MT->cn6PWM -= UPDAOWN ;
		//_send_string(" Left DOWN\r\n");
	} else {
		beep();
		//_send_string(" Left Minimum\r\n");
	}
	//_RB15 = ! _RB15;//確認用のLED1点灯反転
}

//モータ右デューティ幅アップ
__attribute__((address( AdrUpRight ))) void upright(void);
void upright()
{
	//_send_hex_low( MT->cn7PWM );
	if( MT->cn7PWM +UPDAOWN <= 0x0ffff){
		MT->cn7PWM += UPDAOWN ;
		//_send_string(" Right UP\r\n");
	} else {
		beep();
		//_send_string(" Right FULL\r\n");
	}
	//_RB15 = ! _RB15;//確認用のLED1点灯反転
}

// モータ右デューティ幅ダウン
__attribute__((address( AdrDownRight ))) void downright(void);
void downright()
{
	//_send_hex_low( MT->cn7PWM );
	if( MT->cn7PWM -UPDAOWN >= 0){
		MT->cn7PWM -= UPDAOWN ;
		//_send_string(" Right DOWN\r\n");
	} else {
		beep();
		//_send_string("Right Minimum\r\n");
	}
	//_RB15 = ! _RB15;//確認用のLED1点灯反転
}
以上のコードをumehoshiEditの開発ツールで作成し、 できた「UME専用Hexコマンド」を転送するだけで、EEPROM領域(0x9D020010〜)に書き込まれます。
(#define BASE_FUNC 0x80005000 // RAM領域配置用 を使うとRAM配置に変わる)
これだけでは何も動きません。起動時にstart()を実行させることで、その後にforward()など各制御関数を呼び出すことで動作します。
timer2が0.001秒ごとに割り込みで実行して、MT->time2Countをインクリメントして MT->stopCount以上で stop()によりtimer2の割り込みを止めてモータの動作を止めます。
forward()、goback()、right()、left()、ではモータを起動とともにtimer2の割り込みを許可しています。
これでtimer2により一定時間後に停止させる仕組みになっています。

補足情報:
なお、タイマー2の設定はプリスケーラ(1:1)により、(1/40e6)の周期で TMR2をカウントアップします。
そしてこの値はPR2の周期レジスタ値と比較されて、一致するとこの割り込み状態フラグをクリアしてTMR2を0x0000 にリセットした後にカウントアップを再開します。
割り込み状態フラグはセットされることで、割り込み処理が起動されます。この処理の中で、状態フラグをクリア(IFS0CLR利用)しないと次の和割り込みが起動はできないので、 割り込み用のtimer2()関数内で、状態フラグをクリア(IFS0CLR利用)しています。

また、割り込み許可制御レジスタが存在しており、上記start()では割り込みを不可にしています。
これを、許可するメソッドはforward()、goback()、right()、left()の中で行っており、呼び出すと割り込み処理が始まります。 そして割り込みでそれぞれのメソッド内でMT->time2Count がMT->stopCountの設定値数に達すると割り込みを不可することで、モータを停止しています。 なお、PR2の周期レジスタ値は、0x09c3Fになっており、これより割り込み周期は、(1/40e6)*(0x09c3F+1)=0.001秒になっています。
よって、モータ前進ルーチンのforward関数は、MT->stopCount=300の回数行っているので、0.3秒間前進して止まる処理になっています。


上記のEEPROM化後の利用 その1

上記のEEPROM化後でも、これまで通りにumehoshiEditの開発ツールでRAM領域をC言語でプログラミングして転送・実行ができます。
そして、各モーター制御のエントリーポイントを呼び出すだけで、簡単にモータを動かすことができるようになりました。
それを利用すると、RAM領域をC言語でプログラミングする場合も、簡単なコードでモータを制御することができます。
以下は、実行する順番でROMのモータ制御関数のエントリーポイントを登録することで、その順番のモータ制御を繰り返すプログラムの例です。
#include <xc.h>
#include "common.h"

#define BASE_FUNC   0x9D020000 // EEPROM領域配置用(パワーONでないリセット操作の実行アドレス)
//#define BASE_FUNC 0x80005000 // RAM領域配置用

#define AdrStart     (BASE_FUNC+0x0010) 	// アプリの起動時の初期ルーチンのアドレス
#define AdrForward   (BASE_FUNC+0x0200)	// モータ前進ルーチンのアドレス
#define AdrGoBack    (BASE_FUNC+0x0300)	// モータ後進ルーチンのアドレス
#define AdrRight     (BASE_FUNC+0x0400)	// モータ右回転ルーチンのアドレス
#define AdrLeft      (BASE_FUNC+0x0500)	// モータ左回転ルーチンのアドレス
#define AdrTimer2    (BASE_FUNC+0x0B00)	// 指定時間後にモータ停止のTimer2用アドレス
#define AdrBeep      (BASE_FUNC+0x0D00)	// ビープ音

typedef struct {
	int cn6PWM;	// 現在の左デューティ幅で、初期設定
	int cn7PWM;	// 現在の右デューティ幅で、初期設定
	int time2Count;//Timer2割り込みでカウントアップ(下記stopCountまでカウント)
	int stopCount; // time2CountがstopCount以上で、time2CountがをゼロにしてモータをOFF
} MortorCtrl;

#define MT ((MortorCtrl *)0xA0009000) // ram_area3領域の絶対アドレスで、グローバル変数を定義

void WaitBeep()	//音出して、1秒待つ
{
	( (void (*)(void) )AdrBeep)();//音パターン[−−−−]の登録、ループ無し
	MT->time2Count = 1000;
	IEC0SET = 0x00000200;//T2IE Timer2 Enable(割込み許可)
}

void timer2_interrupt()// 割り込み関数
{
	static int step=0;
	static void (*funcs[])(void)={ // 実行する順番で関数を登録(各関数内で、Timer2割り込みをイネーブルにする必要がある)
		WaitBeep,		// 音を出して一時停止
		(void *)AdrForward,	// 前進関数
		(void *)AdrRight,	// 右回転関数
		WaitBeep,		// 音を出して一時停止
		(void *)AdrLeft,	// 左回転関数
		(void *)AdrGoBack,	// 後進関数
	};

	( (void (*)(void) )AdrTimer2 )();
	// 内部でMT->time2Count++を行って、MT->stopCount以上なら0になり、Timer2割り込みがディセーブルになる。

	if( MT->time2Count != 0) return; // time2Countまだ動作中
	if( step >= 6 ) step = 0;
	funcs[step++]();// 上記で配列に登録した関数を、順番に実行する。
}

// RAM領域の起動アドレス
__attribute__((address( 0x80005000 ))) void start (void);
void start()
{
	( (void (*)(void) )AdrStart)();// Timer2のON、割り込み処理デフォルト関数登録、割り込み不可

	( (uint32_t *)_HANDLES)[_IDX_TIMER_2_FUNC] =  (uint32_t ) timer2_interrupt;// Timer2の割り込み処理を置き換える
	__asm__ ("NOP");
	MT->stopCount = 3000;// 最初の割り込み内のstop()タイミング (3秒後)
	IEC0SET = 0x00000200;//T2IE Timer2 Enable(割込み許可)
}
なお、USBケーブルを接続したままで実行すると雑音で誤動作しがちなので、実行スタートを3秒に設定しています。
これを利用して、通信用後に通信用USBを抜いて(電源の供給が継続する細工が必要)、その後にモードを動くようにすると良いでしょう。

上記のEEPROM化後の利用 その2

上記のEEPROM化後であれば、モーター制御のエントリーポイントを呼び出すだけで、簡単にモータを動かすことができるようになります。
このエントリーポイントをUME専用Hexコマンドとして実行させる文字列表現を以下にまとめます。
(例えば、「Tera Term」などのターミナルソフトを使ってシリアル接続して送信できます。)

ブザー音(−−−−)を鳴らす処理登録で、初期化無しで呼び出せます。
Beep
R009D020D00003B

タイマー割り込み初期設定(下記モータ制御命令を使う前にに一回だけ行う必要がある)
この初期化で、TIMER2の割り込みルーチンにTimer2関数を登録しています。
Timer2関数で、一定時間経過したらモータを停止してTIMER2の割り込みを不可にしている。
StartInit
R009D020010004E

モータ前進処理とタイマー2の割り込み許可で、それを割り込み内のstop実行まで保持する。
Forward
R009D020200004D

モータ後進処理とタイマー2の割り込み許可で、それを割り込み内のstop実行まで保持する。
Back
R009D020300004C

モータ右回転処理とタイマー2の割り込み許可で、それを割り込み内のstop実行まで保持する。
Right
R009D020400004B

モータ左回転処理とタイマー2の割り込み許可で、それを割り込み内のstop実行まで保持する。
Left
R009D020500004A