AVRによるシリアル通信の基本(1)


前回、ATmega168 + FT232RLでシリアル通信するプログラムを紹介した。(http://d.hatena.ne.jp/yaneurao/20080628)


「FT232RLのところを見るとTXD,RXDがAVRのRXD,TXDに結線」されているのだが、これだけではフロー制御されない。フロー制御されないということはどんどんデータを送られるととりこぼしてしまうと言うことだ。


フロー制御を行ないたいなら、RTS,CTSも接続して「これ以上データを送らないでくれ」と言えるようにしておくべきなのだが、そんな結線をするとAVRのI/Oが減ってもったいないので、普通しない。


では、どうするかと言うと、AVR側では割り込みを使って受信して、割り込み内ではring bufferに格納していく。これが基本中の基本…だと思うのだが、あまり解説しているサイトがないのでサンプルコードを書いて解説をしてみる。



#include <avr/io.h>
#include <avr/interrupt.h>
#include <util/delay.h>

#define FOSC 20000000 // 20MHz
#define BAUD 57600
#define MYUBRR FOSC/16/BAUD-1

/* sio設定 */
void sio_init(unsigned int ubrr)
{
UBRR0H = (unsigned char)(ubrr>>8); // ボーレート上位8bit
UBRR0L = (unsigned char)ubrr; // ボーレート下位8bit
UCSR0B = (1<<RXEN0) | (1<<TXEN0) | (1<<RXCIE0); // 送受信許可,割り込み許可
UCSR0C = (3<<UCSZ00) ; // stopbit 1bit , 8bit送信
}

// フロー制御をしないので256 bytesのbufferを自前で用意する
volatile char usart_recvData[256]; // USARTで受信したデータ。ring buffer
volatile int usart_counter; // 現在のwrite位置(usart_recvDataのindex)
int usart_counter2; // 現在のread位置(usart_recvDataのindex)

// 送信が完了するまで待機する
void wait_for_sending()
{
loop_until_bit_is_set(UCSR0A,UDRE0); // 送信データレジスタ空きまで待機
}

// データを受信しているかのチェック。受信しているなら非0。
int is_received()
{
return (usart_counter != usart_counter2) ? 1 : 0;
// read位置とwrite位置が異なるならば受信データがあるはず
}

// データを受信するまで待機する
void wait_for_receiving()
{
while(!is_received())
;
}

// 受信したデータを返す。受信したデータがない場合は受信するまで待機。
int getReceivedData()
{
wait_for_receiving();
return usart_recvData[usart_counter2++ & 0xff];
}

// 割り込みによる受信
ISR(USART_RX_vect)
{
// if(bit_is_clear(UCSR0A,FE0))
// データのbit長を8bit以外に設定していると
// 毎回フレーミングエラーになるっぽい(´ω`) ので外す。
{
usart_recvData[usart_counter++ & 0xff] = UDR0; // 受信データを受信バッファに格納
}
}

int main(void)
{
sio_init(MYUBRR); // SIO設定

sei(); // 全割り込み許可

for(;;)
{
int d = getReceivedData();

_delay_ms(10); // 動作テストのためにわざとwaitを入れておく

wait_for_sending();
UDR0 = d; // echo back
}
}


上のような実装だと、ring bufferでwrite markerがread markerを追い越した時には悲劇が起きるが、どうしようもないのでそれは目をつぶることにしてある。サウンドバッファなどでもマシン負荷がかかって書き込みが追いつかないと特定部分が音飛びしたり重複して再生されたりするが、それも同じ事情である。


■ ハマリどころ


・UCSR0Bの設定で受信割り込み(RXCIE0)を許可すること。
・初期化が終わったらsei()で割り込みを許可すること。これをしないといつまでもデータを受け取れない。
・データサイズが8bit長以外だと毎回フレーミングエラーになるっぽい。詳しく調べていないので、原因は、よくわからない。AVR側の仕様か?
・AVRの周波数の設定は、AVR Studioからprojectの設定で行なっておく必要がある。単位が「Hz」なので「MHz」のつもりで 20 とか入力すると _wait_ms() が誤動作する。20MHzなら 20000000 と入力しよう。
・割り込み中の関数で値を書き換える必要のある、割り込みの外の関数からもアクセスする変数/配列には必ずvolatileをつけること。
・UBRR0Hのように0がついているのは、USARTが複数搭載あるデバイス向けの拡張である。1つしか無いデバイスではこの0が省略され、UBRRH のようになっている。
・ATmega168は実際はUSARTは1つしかないが、このようにUSART関連のレジスタ名にすべて 0 がつく。(USARTが複数あるデバイスへのポータビリティを考慮したのか?)


(長くなってきたので明日の記事に続く)