電王戦出場記念! | 書籍化されたで! | 監修したで!(`ω´) | 絶版なってしもた | Kindle版で復活!! | 記事書いたで! | |
|
|
|
YaneuLabs / やねうら王公式 / やねうらおにメール / twitter / プロフィール |
昨日の記事の続き。
コメント欄で、送信もリングバッファにしたほうが使いやすいですよと言われたので、ソースを修正してみた。ボーレート設定の時の丸め処理を追加して、変数名を整理したので、ご自由にコピペしてお使いください。(何かあっても責任は負いませんが)
私が記事を掲載して10分足らずで「ここがおかしい」「ここが間違っている」など貴重なご指摘を多数いただき(インターネットって凄いところやね…)、私も10分以内にソースを修正して掲載せねば!と思って頑張ってみた。ソースの修正は10分ぐらいで体感的には終わった気がする。(←計測してない。たぶん10分で終わってない。)
// USART.H #ifndef __USART__H__ #define __USART__H__ /* sio設定 */ void sio_init(unsigned int baud,int bit) { unsigned int ubrr = (((F_CPU>>4)+(baud>>1))/baud-1); // UBRRを設定するときに丸め処理をしておく。 UBRR0H = (unsigned char)(ubrr>>8); // ボーレート上位8bit UBRR0L = (unsigned char)ubrr; // ボーレート下位8bit UCSR0B = (1<<RXEN0) | (1<<TXEN0) | (1<<RXCIE0) | (1<<TXCIE0); // 送受信許可,送受信割り込み許可 switch(bit) { case 8: UCSR0C = (3<<UCSZ00) ; // stopbit 1bit , 8bit送信 break; case 5: UCSR0C = 0; // stopbit 1bit , 5bit送信 } } // byteを定義しておく。 typedef unsigned char byte; // フロー制御をしないので256 bytesの送受信bufferを自前で用意する volatile char usart_recvData[256]; // USARTで受信したデータ。ring buffer volatile byte usart_recv_write; // 現在のwrite位置(usart_recvDataのindex) byte usart_recv_read; // 現在のread位置(usart_recvDataのindex) volatile char usart_sendData[256]; // USARTで送信するデータ。ring buffer byte usart_send_write; // 現在のwrite位置(usart_sendDataのindex) volatile byte usart_send_read; // 現在のread位置(usart_sendDataのindex) // データを受信しているかのチェック。受信しているなら非0。 int is_received() { return (usart_recv_write != usart_recv_read) ? 1 : 0; // read位置とwrite位置が異なるならば受信データがあるはず } // データを受信するまで待機する void wait_for_receiving() { while(!is_received()) ; } // 受信したデータを返す。受信したデータがない場合は受信するまで待機。 int getReceivedData() { wait_for_receiving(); return usart_recvData[usart_recv_read++]; } // 割り込みによる受信 ISR(USART_RX_vect) { usart_recvData[usart_recv_write++] = UDR0; // 受信データを受信バッファに格納 } // 送信バッファにデータがあれば、そこから1バイト送信するルーチン。 // 内部的に使用しているだけなのでユーザーは呼び出さないで。 void private_send_char() { if (usart_send_write != usart_send_read) UDR0 = usart_sendData[usart_send_read++];// 送信バッファのデータを送信 } // 割り込みによる送信 ISR(USART_TX_vect) { private_send_char(); } // 1バイト送信 void sendChar(int c) { // 送信バッファがいっぱいなら待つ while(((usart_send_write + 1) & 0xff) == usart_send_read) ; // 何はともあれ送信バッファにデータを積む。 usart_sendData[usart_send_write++] = c; // 送信レジスタがセットされている == 送信できる状態 ならば、 // 一度だけ送信しておく。 if (UCSR0A & (1<<UDRE0)) private_send_char(); // 例えば次のように送信バッファにデータを積まずにUDR0に直接アクセスするコードは // よくない。 // if (UCSR0A & (1<<UDRE0)) // UDR0 = c; // else // usart_sendData[usart_send_write++] = c; // これは、else句が実行される瞬間にUSART_TX_vectによる割り込みがかかり、 // usart_send_write == usart_send_readであった場合、次にsendCharが呼び出されて // その送信が完了するまでここで積んだデータが送信されないからである。 } // 文字列の送信 void sendString(char *p) { while(*p) sendChar(*p++); } #endif
使用方法は、こんな感じ。
#include <avr/io.h> #include <avr/interrupt.h> #include <util/delay.h> #include "usart.h" int main(void) { sio_init(57600,8); // SIO設定 sei(); // 全割り込み許可 sendString("Hello! UART world!!"); for(;;) { int d = getReceivedData(); sendChar(d); // echo back } }
自分も仕事用でおんなじ様なもの作ってたますが
(関数の種類はともかく実装はほぼ一緒です)
ISR(USART_TX_vect)
{
private_send_char();
}
ここのところ最適化-Osでは
インライン化してくれなくて残念なコードをはいたりします。
もしボーレートギリギリまで上げたい時とかは、
割り込みがpushとpopだらけになって気持ち悪いんで
中身コピペか-O3でビルドが良いかもです。
まあ、なんか蛇足ですね。
タイムアタックで作ってあるコードですし。
あと、どうでも良いですけど"Helloが2箇所変ですよ。
まあ、誰が書いても(正しく動作する無駄のない作りにすれば)こうなりますっとことですかね(´ー`)b
> 中身コピペか-O3でビルドが良いかもです。
ああ、なるほろ…。
> あと、どうでも良いですけど”Helloが2箇所変ですよ。
あれ?どこでしょう…?
なんかダブルクオーテーションなハズのところが
アンパサントquotって見えます。
#include ”usart.h”
sendString(”Hello! UART world!!”);
のとこですかね。
表示されない方が正常な動作と言えますが。
http://www.w3.org/TR/html401/charset.html#entities
限られた状況以外では送信がまともに動かないはずですよ。
実CPUでも再現しました。
じゃああんたはどうしたのって話ですが、UDR0が空いたときに発生するISR(USART_UDRE_vect)を使いました。sendChar内は割込禁止で実行し、空のリングバッファへのプッシュならUDRIE0もセット。ISR(USART_UDRE_vect)内で(唯一の)UDR0への書き込みを行い、既にリングバッファが空だったらUDRIE0をリセットをする、ってな案配です。