プログラミング素人のはてなブログ

プログラミングも電気回路も専門外の技術屋の末端が勉強したことや作品をアウトプットするブログ。コードに間違いなど見つけられたら、気軽にコメントください。 C#、Python3、ラズパイなど。

ビュートローバーARMを組込み視点で考える

もっと組込みプログラミング的なことをやりたい

前回はビュートローバーARMに対してサンプルコードの修正で簡単な改造をしました。
s51517765.hatenadiary.jp

実はサンプルコードはある程度 分かりやすい 関数で組まれていて、その本質のところに触れていません。
というのはプログラミングというのはCPUが持っているタイマーやGPIO、メモリといったものをbitの上げ下げによって制御しているのですが、これをより人にとってわかりやすい関数に丸めているので、直感的に操作することが出来るようになっています。
そこで、今回はC言語でCPUをより直接制御することろをやってみます。

それ以前にもマイコンボードはいろいろありました。ただ、「初めて触ったマイコンArduino」という方には想像がつかないと思うのですが、それ以前のマイコンは、「その筋」の人でないと使おうとは思わないですし、使うこともできない、なかなかハードルが高いものでした。
<中略>
つまり一番奥の方では、機械語のプログラムが実行されていて、更にその奥には、そのプログラムを実行するための論理回路が動いているわけです。

deviceplus.jp

ソースコードをハードウェア視点で解読する

ここでは題材としてモーターを駆動する関数を見ていきます。
モーターを駆動する関数はvs-wrc103.cにある、

void Mtr_Run_lv(short m1,short m2,short m3,short m4,short m5,short m6){}

でした。

まず、モーターにDutyを書き込んでいると思われるのが以下です。

LPC_TMR16B0->MR0 = t_Duty[0]&0x0000FFFF;

これは多段defineマクロされているので、展開してみます。

//左辺 =  ((LPC_TMR_TypeDef    *) LPC_CT16B0_BASE)->MR0
//左辺 =  ((LPC_TMR_TypeDef    *)(LPC_APB0_BASE + 0x0C000))->MR0
((LPC_TMR_TypeDef    *)((0x40000000UL) + 0x0C000))->MR0= t_Duty[0]&0x0000FFFF;

すると(0x40000000UL) + 0x0C000)のところにあるMR0にDutyを書き込んでいると分かります。
(LPC_TMR_TypeDef *)はポインタです(多分)。
t_Duty[]はモーターのモーターの速度パラメータとして渡された引数を途中、2倍して論理反転していますが、プラスマイナスとか何かの調整を行っているだけだとと思います。

if (mta[i] > 0)
{ 
//0のときは何もしない
t_Duty[i] = (unsigned int)(~(mta[i] * 2));
Gpio2Temp |= 1 << i * 2; //bitを立てる 12bitまでの範囲、Dutyが0でなければそのbitを立てる
}

これを右辺でBit演算して入力しています。
0x0000FFFFは16進数ですが、2進数でいうと0000 0000 0000 0000 1111 1111 1111 1111で下位16bitが1でそれ以外が0です。
これをAND演算しているので、16bit以上を切り捨てているということが分かります。
つまり16bit以上の桁の値は無視して、(0x40000000UL) + 0x0C000)のところにあるMR0に書き込んでいます。

LPC1343のデータシートP16をみると、ここはtimerであることが分かります。

f:id:s51517765:20211121112104p:plain
LPC1343データシートよりメモリマップ

ここで、timerは広義には時間を計測するものですが、ここではPWMを扱っていると推測されその上限が16bitつまり65,535分解能のPWMということになります。

次に、このような命令があります。

//読み込み
Gpio2Temp = LPC_GPIO2->DATA;
//書き込み
LPC_GPIO2->DATA = Gpio2Temp;

これも先ほどのように多段defineマクロを展開すると(同様なので書き込みは省略)、

//Gpio2Temp =   ((LPC_GPIO_TypeDef   *) LPC_GPIO2_BASE )->DATA;
//Gpio2Temp =   ((LPC_GPIO_TypeDef   *)(LPC_AHB_BASE  + 0x20000) )->DATA;
Gpio2Temp = ((LPC_GPIO_TypeDef *)((0x50000000UL) + 0x20000))->DATA;

((LPC_GPIO_TypeDef *)((0x50000000UL) + 0x20000))のところにあるDATAに読み書きしていることが分かります。
こちらも先ほどのメモリマップによると、GPIO PIO2というGPIOであることが分かります。

これらから、二つのレジスタへの書き込みでモーターを制御しようとしていることが分かります。
この2つのかかわり方ですが、GPIO PIO2_Xが回転方向を指示し、16bit timerがそのPWMを設定していると思われます。
回路図をみると、二つのモーターに対してPIO02_XPIO0_8/XXPIO1_9/XXが接続されていることが分かります。
f:id:s51517765:20211122111613p:plain
さらにデータシートからPIO0_8/XXPIO1_9/XXがtimerであることが分かります。
f:id:s51517765:20211122111505p:plain
これをハードウェアAND演算してFETを制御しているということになります。
ちなみに、モーターCN3CN4に対してそれぞれFETが4つついているのはHブリッジ回路というもので、正転・逆転の制御ができるものです。

まとめ

ビュートローバーARMのモーター駆動部分をハードウェア観点で解読してみました。
デフォルトではモーターは2つしか搭載されていないので、これらのdefineマクロ展開結果を入れて整理すると、以下のようなプログラムで同じ機能を持たせることが出来ます。

void Mtr_Run_lv2(short m1, short m2)
{
	int i = 0;
	short mta[2];
	unsigned int t_Duty[2];
	uint32_t Gpio2Temp;	
	Gpio2Temp = ((LPC_GPIO_TypeDef *)((0x50000000UL) + 0x20000))->DATA; //読み込み

	Gpio2Temp &= ~0x0FFF; //下位12bitを0

	mta[0] = m1;
	mta[1] = m2;

	for (i = 0; i <  sizeof(mta) / sizeof(mta[0]); i++)
	{
		if (mta[i] == 0x8000)
			mta[i] = 0;
	}
	mta[1] = -mta[1];

	for (i = 0; i <  sizeof(mta) / sizeof(mta[0]); i++)
	{
		if (mta[i] > 0)
		{ //0のときは何もしない
			t_Duty[i] = (unsigned int)(~(mta[i] * 2));
			Gpio2Temp |= 1 << i * 2; //bitを立てる 12bitまでの範囲、Dutyが0でなければそのbitを立てる
		}
		else if (mta[i] < 0)
		{
			t_Duty[i] = (unsigned int)(~(-mta[i] * 2));
			Gpio2Temp |= 2 << i * 2; //bitを立てる 12bitまでの範囲、モータ出力を有効にする?
		}
		else
		{
			t_Duty[i] = 0;
		}
	}
	((LPC_TMR_TypeDef *)((0x40000000UL) + 0x0C000))->MR0 = t_Duty[0] & 0x0000FFFF; //0xFFFF=16bit以上を切り捨て、TimerにDutyを割り当て、Timerが16bitだから
	((LPC_TMR_TypeDef *)((0x40000000UL) + 0x10000))->MR0 = t_Duty[1] & 0x0000FFFF;

        //デバッグ用
	char sentence[19]; //送る文字数より1文字多い配列を確保する。
	for (int i = 0; i < sizeof(mta) / sizeof(mta[0]); i++)
	{
		sprintf(sentence, "t_Duty%01d=%04d\r\n", i, t_Duty[i]);
		SciStrTx(sentence, 18);
	}
	LPC_GPIO2->DATA = Gpio2Temp;
}