この記事はeeic Advent Calendar 2016 20日目の記事 その2 です。
はじめに
その1では,環境構築について説明したので,その2では,マイコンプログラミングの話と,プログラミングの実践を書いていきたいと思います.
マイコンプログラミングとは
この世には,様々なプログラミング言語があり,また様々な応用先があります.
Webサイトを作ったり,スマートフォンアプリを作ったり.
広い用途の中でも,大きな割合を占めるのが,組み込みプログラミングです.
組み込みプログラミングとは,実際のモノに組み込まれたマイクロコンピュータ(マイコン)で動くアプリケーションのプログラミングです.
エアコン,電子レンジ,冷蔵庫,車…あらゆるところにマイコンが組み込まれ,また誰かがプログラミングしています.
さて,今回はマイコンプログラミング入門として,主にプログラムを習ったことがあるEEICの学科の方を対象に,マイコンプログラミングの特徴について話していきたいと思います.
なお,ここで話す内容は, 伝統的な マイコンプログラミングです.最近は敷居が下がり,よりプログラムしやすくなっていますが,あえて伝統的な方式について述べたいと思います.
マイコンプログラミングの特徴
言語はC or C++
PC・スマホ向けアプリケーションと異なり,マイコンのスペックは低いため,モダンな言語を用いることができません.
組み込み向けRubyなどの例はありますが,実用的かと言われると微妙なところだと思っています.
また,そもそもマイコン向けのコンパイラは,各マイコンベンダーが作っていることが多いため,多くの言語のサポートが難しいというのも一因だと思います.
そのため,伝統的なCまたはC++が用いられることが多いです.
APIリファレンスではなく,データシートを読む
Arduinoやラズパイでプログラムを行う場合,IOポートから出力するための関数が用意されており,それをAPIリファレンスから検索して実装します.Arduinoならこれですね.
Ruby on RailsでWebアプリケーションを作ったり,Pythonで機械学習のプログラムを作る際も,APIリファレンスを読みながら(または使い方をググりながら)実装することが多いと思います.
マイコンプログラミングでは, データシート を参照しながらプログラミングを行います.
データシートとは次のようなものです.(正確にはReferenceManualと書かれていますが,便宜上データシートと呼ぶことにします)
データシート(今回はReferenceManual)とは,そのマイコンをプログラムする上で,各機能を使うための設定方法が書かれているものです.(他にも,特徴や電気的性質が書かれたものもあります.こちらを一般的にデータシートと呼びます)
先ほどのpdfを開いていただいた方にはわかると思いますが,1000ページ以上あります.このマイコンの全機能を把握するためには,これらを全部読む必要があります.これがマイコンプログラミングの最も大きな障壁だと思います.
ただ,趣味レベルでこれらを全て把握する必要はなく,使いたい機能のページをさらって読んでいくだけで十分だと思います.(業務レベルでどうなっているかは知らないですが…)
データシートを使ってどうプログラムするかが,次の項目です.
レジスタをいじる
マイコンプログラミングは,主に レジスタ を操作することによって行います.
レジスタとは,記憶素子のことですが,ここでは設定を保存する場所というイメージで良いと思います.
例えば,出力ポートから1を出して,LEDを光らせたいとします.
その時は,次のレジスタを参照します.(先ほどの Reference Manual より引用)
これは入出力ポートの用途を設定するレジスタで,32ビット幅となっています.
例えば,PORTA(と分類されている入出力ポートがあります)の5番を,プッシュプル出力(LEDなどを駆動するための普通の出力です)にするためには, GPIOA_CRLの21, 20番目のビットを"11"に,23, 22番目のビットを"00"にする 必要があります.リセット直後は(電源を入れた直後の状態では),CNF7は"10",すなわち,オープンドレインという違った出力方式で初期化されています.そのため,この設定をしないと,LEDが光らない!といった問題が発生する可能性があります.
同様に,ポートAの5番ピンから1を出力する場合には,次のレジスタを参照します.前述と同じデータシートからの引用です.
データシート曰く,GPIOA_BSRRの,5番目のビットを1にすれば出力が1(セット)に,BSRRの21番目のビットを0にすれば,出力が0(リセット)されるということがわかります.
このように,常にデータシートを参照しながら,目的の動作をさせるためのレジスタを操作していくのが,伝統的なマイコンプログラミングです.
実践
さて,いよいよ,「C/C++を用いて,データシートを読みながら,使いたい機能のレジスタをいじっていく」流れを実践してみたいと思います.
ここでは,先ほどサンプルプログラムで試した,Lチカ(LEDチカチカ)を行いたいと思います.
その1で説明したように,新しいプロジェクトを作ります.
ただし,OpenOCDの接続テストは行う必要はなく,Debuggerの設定のみ,次の画像のように,左上のDuplicateを押して複製した後,Name, Project, C/C++ Applicationを書き換えます.
まずは,main.cppに最初に書いてあったよくわからない部分を全て消します.
また,ソースファイルも,main.cppのみ残して削除します.src内にmain.cppのみが残るようにします.
main.cppの初めに,#include "stm32f10x.h"
をインクルードします.これが,レジスタ情報などが入ったヘッダです.
試行錯誤の結果が,次のソースコードです.
#include <stdio.h>
#include <stdlib.h>
#include "stm32f10x.h"
int main() {
RCC->APB2ENR |= (1 << 2); // PORTAにクロック供給
// PORTAの5番ピンを設定
GPIOA->CRL &= ~(1 << 23);
GPIOA->CRL &= ~(1 << 22);
GPIOA->CRL |= (1 << 21);
GPIOA->CRL |= (1 << 20);
GPIOA->BSRR |= (1 << 5); // PA5をセット
// Infinite loop
while (1) {
GPIOA->BSRR |= (1 << (5 + 16)); // PA5をリセット
for (long int i=0; i<2000000; i++); // 0.5秒くらい待つ
GPIOA->BSRR |= (1 << 5); // PA5をセット
for (long int i=0; i<2000000; i++); // 0.5秒くらい待つ
}
}
それでは,各行を説明していきます.
クロック供給
RCC->APB2ENR |= (1 << 2);
の部分です.
これは何をしているかというと,PORTAへのクロック供給です.このマイコンでは,低消費電力化を図るため,電源を入れた状態ではペリフェラル(コア以外の部分)のクロック供給を止めています.
したがって,PORTAを使うためには,それにクロックを供給する必要があります.
データシートを見ると,次のようなレジスタが見つかります.
データシートを読むとわかるのですが,このIOPAEN
というビットが,PORTAへのクロック供給を管理しています.
さて,レジスタの2ビット目をセットしたい,という時にどう書くかですが,ここではシフト演算とOR演算を用います.
セットしたい場合,まず1を用意して,それをセットしたいビット番号分左シフトします.その時に空いたビットには0が入ります.
この値とレジスタの値のORをとることで,セットしたいビットだけが1になり,他のビットとの干渉を防ぐことができます.(0とのORになるため."x or 0 = x"ですね)
逆にリセットしたい場合ですが,例えばRCC->APB2ENR &= ~(1 << 2);
と書きます.
リセットしたいビットだけが0,という値とのANDとなるので,他のビットとの干渉なしでリセットが行えます.
PORTA,5番ピンの設定
GPIOA->CRL &= ~(1 << 23);
からの4行です.その1を見ていただいた方にはわかると思いますが,NucleoのLED(ボード上にLD2と書いてあるやつです)は,PORTAの5番ピンに繋がっています.レジスタをいじるで説明しましたが,GPIOA_CRLの21, 20番目のビットを"11"に,23, 22番目のビットを"00"にする必要がありました.これを,前述の記法を使って書きます.
セット,リセット
GPIOA->BSRR |= (1 << 5);
とGPIOA->BSRR |= (1 << (5 + 16));
です.
LEDのピンをセット,リセットを行なっています.
Delay
for (long int i=0; i<2000000; i++);
の部分です.
無駄なループを行うことで,時間を稼ぎます.この2000000という値は,色々な値を試した結果です.
実行!!!
さて,BuildAllの後,新しく作ったプロジェクトに対応するデバッガを起動し,書き込みます.
だいたい0.5秒ごとにLEDが点滅しましたでしょうか?
というように,LEDを点滅させるだけでも,非常に大きな労力がかかります.
上で作ったソースも,ほぼBlink.hの中身と関数の名前を見ながら,操作していると思われるレジスタを探しました.
しかし,その大変さの代わりに,LEDが点滅しただけでも,大きな達成感が味わえます.
伝統的なマイコンプログラミングの大変さが伝わりましたでしょうか?
(ちなみに,今回はそのままでうまくいったので省きましたが,大抵の場合は,クロックの設定を行う必要があります.クロックのソースは内部発振器か外部か,プリスケーラ,PLLの倍率などを設定します)
終わりに
これまで,Nucleoボードを利用して,開発環境,プログラミングの特徴について書いてみました.
レジスタを叩くプログラミングはいかがでしたでしょうか?
実際は,この面倒さを防ぐために,マイコンプログラミングでもAPIプログラミングに近いものが出てきています.
今回用いたSTMマイコンでも,メーカーからHAL(Hardware Abstract Layer)が提供されており,それを用いればレジスタを意識することなく,APIを叩くようにプログラミングを行うことができます.
ただ,そういうライブラリは,使用者が少なく,情報が少ないことが多いと思います.
本当は割り込みまで書きたかったのですが,何故かうまくいかなかったので,Lチカまでとしました.
その場合,わかり次第追記したいと思います.~~~
(※12/20 1:10 追記:その1の方に追記しましたが,原因がわかりました.C Projectを選ばなければならないところを,C++ Projectとしていたのが原因でした.特殊な記法```__attribute__ ((weak, alias ("Default_Handler"))```があり,それを扱えるのがCコンパイラのみなのだと思われます)
将来マイコンを触る人がどのくらいいるかわかりませんが,そのような人の助けになれたら幸いです.
ありがとうございました.
# 参考文献
* [1] [RM0008: STM32F101xx、STM32F102xx、STM32F103xx、STM32F105xx、および STM32F107xx 高度 ARM ベース 32 ビット MCU Reference Manual](http://www.st.com/content/ccc/resource/technical/document/reference_manual/59/b9/ba/7f/11/af/43/d5/CD00171190.pdf/files/CD00171190.pdf/jcr:content/translations/ja.CD00171190.pdf)