開発ツールとしての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。
ということで、クラスは使うのだけれど、参照型ではなくちょっと汚いポインタ型(組み込みなんだし、自分でテストできるのだからポインタのチェックなどしないよ!)があれよ一番サイズが小さいということになりました。オールドスタイルと参照型のスタイルがほぼ同着という結果。それで良いのか?