皆さん、「マイコンプログラミングでは、プログラミングの基本となる printf
は使わない」なんて言われていません/信じていませんか?この記事ではじゃあ使えるようにしてやるよといった内容です。対象は RX220、RX631。他のマイコンでも似たようなことをやればできると思います。
なお STM32 では以下の記事に従えば楽にできます。
STM32でUARTをやってみる6(float型printfをUART経由で出力) - ガレスタさんのDIY日記
さて、マイコン好きな人は、UART で文字を出力するオレオレprintf
を愛用していることでしょう。僕もこれを結構愛用しています。
#include <stdio.h>
#include <stdarg.h>
__attribute__((format(printf, 1, 2))) void uart_printf(const char *format, ...) {
va_list arg;
va_start(arg, format);
char buf[64];
vsprintf(buf, format, arg);
char *p = buf;
while( *p != '\0' ) {
uart_send(*p);
p++;
}
va_end(arg);
}
ですが、今回はシステムコールを自分で定義して printf
そのものを実行できるようにしてしまおう、というわけです。
とりあえず出力先なんかを考えずに Hello World(ハロワ)を書いてみます。例えば下の RX631 用のコード。
#include <stdio.h>
int main(void) {
sysclk_init(); // 96MHz through PLL
cmt0_init(); // initialize CMT0 for ms_wait
while(1) {
ms_wait(1000);
printf("Hello\n");
}
return 0;
}
こんなの、誰がどう見ても組み込み用だと思いませんよね。とりあえずビルドしてみましょう。。。
/usr/local/rx-elf/rx-elf/lib/libg.a(lib_a-closer.o): In function `close_r':
../newlib/libc/reent/closer.c:53: warning: _close is not implemented and will always fail
/usr/local/rx-elf/rx-elf/lib/libg.a(lib_a-fstatr.o): In function `fstat_r':
../newlib/libc/reent/fstatr.c:62: warning: _fstat is not implemented and will always fail
/usr/local/rx-elf/rx-elf/lib/libg.a(lib_a-isattyr.o): In function `isatty_r':
../newlib/libc/reent/isattyr.c:58: warning: _isatty is not implemented and will always fail
/usr/local/rx-elf/rx-elf/lib/libg.a(lib_a-lseekr.o): In function `lseek_r':
../newlib/libc/reent/lseekr.c:58: warning: _lseek is not implemented and will always fail
/usr/local/rx-elf/rx-elf/lib/libg.a(lib_a-readr.o): In function `read_r':
../newlib/libc/reent/readr.c:58: warning: _read is not implemented and will always fail
/usr/local/rx-elf/rx-elf/lib/libg.a(lib_a-writer.o): In function `write_r':
../newlib/libc/reent/writer.c:58: warning: _write is not implemented and will always fail
いっぱい怒られました。そりゃそうだ。どこに出力するかも決めてないし。確か ARM だと怒られないんでしたっけ。いずれにしろ、以下の関数が足りないです。ビルド自体は通るのですが、実行時にコケます。
close
fstat
isatty
lseek
read
write
さて、実装しましょう。以下のサイトを参考にしてください。下の方に Definitions for OS interface という項目があります。ここではミニマルな実装がありますので、それぞれパクってどっかのファイルに定義しておいてください。というかそれだけで使えるようになります。
...にしてもすごいですね。close
に至っては常に失敗を返すという仕様にしてあるので。まあ特にすることがないから失敗なんでしょう。
さて、肝心なのは write
の中身です。以下のようにすれば printf
の出力を指定できます。ついでに '\n'
と打つだけで CR+LF になりますよ。
int write(int file, char *ptr, int len) {
int todo;
for (todo = 0; todo < len; todo++) {
if( *ptr == '\n' ) {
uart_send('\r');
uart_send('\n');
}else{
uart_send(*ptr);
}
ptr++;
}
return len;
}
続いてリンカスクリプトを多少修正します。というのもこの printf
、メモリを食うんです。なのでユーザースタック領域を拡張しておきましょう。
.ustack 0x400: AT(0x400)
最後に先程の関数に次を追加して、バッファリングを無効化します。
setbuf(stdout, NULL);
setbuf(stdin, NULL);
以上でできました。main関数の方はまとめてこんな感じです。
#include <stdio.h>
int main(void) {
sysclk_init(); // 96MHz through PLL
cmt0_init(); // initialize CMT0 for ms_wait
setbuf(stdout, NULL);
setbuf(stdin, NULL);
while(1) {
ms_wait(1000);
printf("Hello\n");
}
return 0;
}
それでもまだ怒られる人はリンク時に -lc -lg -lgcc
を追加すればいいと思います。
これで、1秒ごとにシリアルターミナルに Hello
と出力されますね。そう、マイコンでも printf
は使えるのだ!