もっと組込みプログラミング的なことをやりたい
前回はビュートローバーARMに対してサンプルコードの修正で簡単な改造をしました。
s51517765.hatenadiary.jp
実はサンプルコードはある程度 分かりやすい 関数で組まれていて、その本質のところに触れていません。
というのはプログラミングというのはCPUが持っているタイマーやGPIO、メモリといったものをbitの上げ下げによって制御しているのですが、これをより人にとってわかりやすい関数に丸めているので、直感的に操作することが出来るようになっています。
そこで、今回はC言語でCPUをより直接制御することろをやってみます。
それ以前にもマイコンボードはいろいろありました。ただ、「初めて触ったマイコンがArduino」という方には想像がつかないと思うのですが、それ以前のマイコンは、「その筋」の人でないと使おうとは思わないですし、使うこともできない、なかなかハードルが高いものでした。
<中略>
つまり一番奥の方では、機械語のプログラムが実行されていて、更にその奥には、そのプログラムを実行するための論理回路が動いているわけです。
ソースコードをハードウェア視点で解読する
ここでは題材としてモーターを駆動する関数を見ていきます。
モーターを駆動する関数は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であることが分かります。
ここで、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_X
とPIO0_8/XX
PIO1_9/XX
が接続されていることが分かります。
さらにデータシートからPIO0_8/XX
PIO1_9/XX
がtimerであることが分かります。
これをハードウェアAND演算してFETを制御しているということになります。
ちなみに、モーターCN3
CN4
に対してそれぞれ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; }