開発ツールとしてのARM Mbedを調べてみるシリーズから「スピンオフ(フォークというべきか?)」する感じで、ターゲットボードにいろいろデバイスをつなげてみるシリーズを始めました。そのときに、真っ先に困ったのが、デバイスを繋げるための自分のコードを書くときに「どんなスタイルで書くのがいいの?」という点。組み込み用のマイコンのプログラミングというとCで書くのが「未だに」一般的じゃないかと思います。しかし、Mbedは基本C++で、結構カッコよくclassを使ってハードウエアの下の方を隠蔽している感じです。
※「鳥なき里のマイコン屋」投稿順Indexはこちら
「未だに」組み込みじゃCが優勢に見える理由にはいろいろあると思うのですが、実際は「結構まぜて使っている」んじゃないかと思います。どこの会社もいろいろ考え方があり、コーディング規約に反映されてしていることでしょう。基本的なスタイルをいくつか列挙しましょう。
-
- Cで「伝統的な手続き型のスタイル」で書く
- Cの範囲で書くけれど、心はOOPスタイル。
- C++コンパイラは使うのだけれど、ごくごく一部の機能のみ使う
- C++コンパイラで制限なく書く
1の場合、流石にK&Rは無いでしょうが、C11どころかANSI-Cくらい限定で、伝統的なスタイル、品質保証やら何やら長年の蓄積もあり、生成されるオブジェクトが「想像つき」、小さくて速いに違いない、という考え方。2は、今時のプログラマなら、OOPに慣れているので使いたいのだけれど、諸般の事情があってCコンパイラ限定せざるを得ない。そのため、Cの範囲で構造体とか関数ポインタ駆使してC++風に仕上げるというもの。3は、C++コンパイラの便利なところは使うのだけれど(ネームスペースとか)、基本メモリを食うような、あるいは動的なところには手を出さず、ごくごく控えめにC同様なオブジェクトが得られる範囲で書く。4は、newあり、templateあり、オーバライドにオーバロード何でもありですが、流石にリソースがタイトな組み込みではこれはまずありますまい。
今回、MbedでLCDパネルに出力するルーチンを書こうとしたときに、まず迷ったのがこんなことでした。そこで、いくつか書いて比べてみることにいたしました。
まずは手続き型、伝統Cスタイル
int lcdDisplaySTR(I2C *lcd, char Lin, char* message) { char wdata[2]; int status =0; int idx = 0; wdata[0] = 0x0; wdata[1] = Lin; status = lcd->write(LCD_ADDR, wdata, 2, 0); wdata[0] = 0x40; while ((status == 0) && (idx < LCD_MAXLEN) && (*message != 0)) { wdata[1] = *message++; status = lcd->write(LCD_ADDR, wdata, 2, 0); idx++; } return status; }
引数としてMbed OSのオブジェクトであるI2Cのインスタンスへのポインタをとって(ポインタならCにもあるものね)、それに向けて出力するという形。これを呼び出す側はこんな感じ。
#include "mbed.h" #include "AQM1602XA.h" I2C i2c(I2C_SDA, I2C_SCL); DigitalOut myled(LED2); Serial pc(USBTX, USBRX); int main() { int i=0; char buffer[2]; int status; pc.printf("test AQM1602XA LCD\r\n"); lcdInit(&i2c); lcdDisplaySTR(&i2c, LCD_HLINE, "Hello World!"); while (1) { buffer[0]=0x20 + i; buffer[1]=0; status = lcdDisplaySTR(&i2c, LCD_LLINE, buffer); if (buffer[0] >= 0x7F) i = 0; pc.printf("%d --- %d \r\n",i, status); wait(0.5); i++; myled = !myled; } }
Flashの消費は33.5kB、RAMは2.1kB。ま、いいんだけれど、ちょっとMbedっぽくはないね。
次にちょっとMbedぽくして、C++らしくクラスを導入
class LCDAQM1602 { private: I2C* i2cRef; char wdata[2]; int status; public: LCDAQM1602(I2C* ch); int writeCommand(char cmd); int writeData(char dat); int clear(); int displaySTR(char Lin, char* message); int init(); };
すると先ほどの関数は、
int LCDAQM1602::displaySTR(char Lin, char* message) { int idx = 0; status =0; wdata[0] = 0x0; wdata[1] = Lin; status = i2cRef->write(LCD_ADDR, wdata, 2, 0); wdata[0] = 0x40; while ((status == 0) && (idx < LCD_MAXLEN) && (*message != 0)) { wdata[1] = *message++; status = i2cRef->write(LCD_ADDR, wdata, 2, 0); idx++; } return status; }
そしてメインプログラムは、
#include "mbed.h" #include "lcdAQM1602.h" DigitalOut myled(LED2); I2C i2c(I2C_SDA, I2C_SCL); Serial pc(USBTX, USBRX); int main() { LCDAQM1602 lcd(&i2c); int i=0; char buffer[2]; int status; pc.printf("AQM1602 LCD CLASS\r\n"); lcd.init(); lcd.displaySTR(LCD_HLINE, "Hello World!"); while (1) { buffer[0]=0x20 + i; buffer[1]=0; status = lcd.displaySTR(LCD_LLINE, buffer); if (buffer[0] >= 0x7F) i = 0; pc.printf("IDX:%d --- STAT:%d \r\n",i, status); wait(0.5); i++; myled = !myled; } }
こちらは、Flash 28kB、RAM1.8kB。先ほどの手続き型より小さくなっている。でもまだ、ポインタ使っているところがなんか汚い。
C++らしく参照型に変更
class AQM1602 { private: I2C& i2cRef; char wdata[2]; int status; public: AQM1602(I2C& ch); int writeCommand(char cmd); int writeData(char dat); int clear(); int displaySTR(char Lin, char* message); int init(); };
すると関数は、気持ちわずかにスッキリした?
int AQM1602::displaySTR(char Lin, char* message) { int idx = 0; status =0; wdata[0] = 0x0; wdata[1] = Lin; status = i2cRef.write(LCD_ADDR, wdata, 2, 0); wdata[0] = 0x40; while ((status == 0) && (idx < LCD_MAXLEN) && (*message != 0)) { wdata[1] = *message++; status = i2cRef.write(LCD_ADDR, wdata, 2, 0); idx++; } return status; }
そしてmain
#include "mbed.h" #include "AQM1602.h" DigitalOut myled(LED2); I2C i2c(I2C_SDA, I2C_SCL); Serial pc(USBTX, USBRX); int main() { AQM1602 lcd(i2c); int i=0; char buffer[2]; int status; pc.printf("AQM1602 LCD TEST\r\n"); lcd.init(); lcd.displaySTR(LCD_HLINE, "Hello World!"); while (1) { buffer[0]=0x20 + i; buffer[1]=0; status = lcd.displaySTR(LCD_LLINE, buffer); if (buffer[0] >= 0x7F) i = 0; pc.printf("IDX:%d --- STAT:%d \r\n",i, status); wait(0.5); i++; myled = !myled; } }
Flash 33.6kB, RAM2.1kB。
ということで、クラスは使うのだけれど、参照型ではなくちょっと汚いポインタ型(組み込みなんだし、自分でテストできるのだからポインタのチェックなどしないよ!)があれよ一番サイズが小さいということになりました。オールドスタイルと参照型のスタイルがほぼ同着という結果。それで良いのか?