2009/05/06

ATmega88p+AT24C256引き続き: マクロとコードの断片、気になったコーディング

基本的にAVR側がMasterとしてI2Cバスのクロックを制御しているためか、"TWINT"をセットする事でAVRの内部回路をスタートさせ、"TWINT"が再度セットされるまで待つというコードを良く書きます。

使っているWinAVRは20090313版ですが、"avr/io.h"の中でincludeされている"avr/sfr_defs.h"の中にloop_until_bit_is_setマクロがあり、よりプリミティブな書き方をするデータシートのコードよりも、これを使った方が見通しが良いと思います。

int twi_start() {
  TWCR = _BV(TWEN) | _BV(TWINT) | _BV(TWSTA);
  loop_until_bit_is_set(TWCR, TWINT);
  return TW_STATUS;
}
...
void usart_write_string(char *str) {
  for(int i=0; str[i] != '\0'; i++) {
    loop_until_bit_is_set(UCSR0A, UDRE0);
    UDR0 = send_char;
  }
}
...
r = twi_start();
if(r != TW_START && r != TW_REP_START) {
  usart_write_string("error at twi_start()\r\n");
  continue;
}
"loop_until_bit_is_clear"というのもありますが、どちらも中身をみるとおもしろいですね。
#define loop_until_bit_is_set(sfr, bit) do { } while (bit_is_clear(sfr, bit))
もちろん"bit_is_clear"の部分もマクロで、データシートのコーディングをまとめだだけなのかなと思いきや、ずっとさかのぼっていくと"sfr"部分は、
#define _MMIO_BYTE(mem_addr) (*(volatile uint8_t *)(mem_addr))
の"mem_addr"部分に還元されます。
考えてみたらコンパイラが意図しないタイミングで変更されるから、"volatile"を付けるべきなんですよね。
でもこの状況だとvolatile付けcastしても意味ないかな。 どっちにしても、グローバル変数とかは気をつけないとだなぁ…。

そうそう、twi.hの中で#defineされている"TW_STATUS"はデータシート中では"TWSR & 0xf8"と書かれている部分と同じですね。

あとは"_BV()"という書き方もよくみますが、実際には"#define _BV(bit) (1 << (bit))"というマクロとして定義されています。
AVR Studioではgccのオプションとしてデフォルトで"-std=gnu99"が定義されているので、変数宣言を関数の先頭で行なう必要がなくなって、"for(int i=0;..."のような書き方ができるようになっています。

プリプロセッサのマクロは便利ですが、使い過ぎは初心者の読もうという意欲を失なってしまう可能性があります。
AVRのプログラミングからC言語を始めるような方にはコードを読み易くするための工夫が、逆に作用する事があるかもしれません。
意味もわからず本に書かれたコードを単純にコピーして動かそうとして、動かずに呆然とした懐しい記憶が呼び起こされます。
いまの時点から過去を振り返ってみると、大学時代の時間を無駄に使って興味を追求していた贅沢な時間が必要だったのだなぁと思います。

0 件のコメント: