Hatena::ブログ(Diary)

やねうらお-ノーゲーム・ノーライフ このページをアンテナに追加 RSSフィード

GT-Rの買取ならここですわ。どこよりも高く買取ってもらえるはず。お勧め!GT-R 買取
電王戦出場記念! 書籍化されたで! 監修したで!(`ω´) 絶版なってしもた Kindle版で復活!! 記事書いたで!
解析魔法少女美咲ちゃん マジカル・オープン!

YaneuLabs / やねうら王公式 / やねうらおにメール / twitter / プロフィール

 | 

2008-07-13 AVRによるシリアル通信の基本

[] AVRによるシリアル通信の基本(4)  AVRによるシリアル通信の基本(4)を含むブックマーク  AVRによるシリアル通信の基本(4)のブックマークコメントAdd Star

昨日の記事の続き。


コメント欄で、送信もリングバッファにしたほうが使いやすいですよと言われたので、ソースを修正してみた。ボーレート設定の時の丸め処理を追加して、変数名を整理したので、ご自由にコピペしてお使いください。(何かあっても責任は負いませんが)


私が記事を掲載して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
    }
}

hanahihanahi 2008/07/11 11:34 usart_sendData[256] のコメントがコピペしたままになってるみたいですよ。

yaneuraoyaneurao 2008/07/11 15:58 ほんとだ(´ω`) 修正しました。

kkkk 2008/07/11 19:13 提案を対応して頂いてしまった!

自分も仕事用でおんなじ様なもの作ってたますが
(関数の種類はともかく実装はほぼ一緒です)

ISR(USART_TX_vect)
{
private_send_char();
}
ここのところ最適化-Osでは
インライン化してくれなくて残念なコードをはいたりします。

もしボーレートギリギリまで上げたい時とかは、
割り込みがpushとpopだらけになって気持ち悪いんで
中身コピペか-O3でビルドが良いかもです。

まあ、なんか蛇足ですね。
タイムアタックで作ってあるコードですし。

あと、どうでも良いですけど"Helloが2箇所変ですよ。

yaneuraoyaneurao 2008/07/11 20:05 > (関数の種類はともかく実装はほぼ一緒です)

まあ、誰が書いても(正しく動作する無駄のない作りにすれば)こうなりますっとことですかね(´ー`)b

> 中身コピペか-O3でビルドが良いかもです。

ああ、なるほろ…。

> あと、どうでも良いですけど”Helloが2箇所変ですよ。

あれ?どこでしょう…?

kkkk 2008/07/11 20:17 あれ・・・
なんかダブルクオーテーションなハズのところが
アンパサントquotって見えます。

#include ”usart.h”
sendString(”Hello! UART world!!”);
のとこですかね。

yaneuraoyaneurao 2008/07/11 20:20 IEでは正常に見れます。ブラウザ依存なんですかね…。(´ω`)?

KAKAMIKAKAMI 2008/07/11 21:35 文字実体参照の末尾にセミコロンを記述しないで文字列を続けると、IE 以外では表示されないですね。
表示されない方が正常な動作と言えますが。
http://www.w3.org/TR/html401/charset.html#entities

yaneuraoyaneurao 2008/07/11 21:37 うお。quot;の’;’が抜けてたのか…。それは、気づかなかった。

kyky 2014/03/14 03:06 古いエントリーに突っ込んで申し訳ないのですが、このコードは
限られた状況以外では送信がまともに動かないはずですよ。
実CPUでも再現しました。

yaneuraoyaneurao 2014/03/14 03:15 ↑何が原因なんでしょうか? 一応、当時実際にAVRマイコンにて送受信の動作は確認しています。(デイジーチェーンにして実験しました) なんか当時、やっつけで書いたコードなのでいま見てもよく思い出せませんが。

kyky 2014/03/18 03:08 ISR(USART_TX_vect)は送信シフトレジスタが空になり、1フレームの送信が終わった時に発生する割込ですよね。UDR0(の書き込み)レジスタが空(送信シフトレジスタに渡し済み)で、UDRE0がセットされていても前のデータを送信中という状況があり得るので、非ISRのコードから呼び出されたprivate_send_char()の実行中にISR(USART_TX_vect)が呼び出され、再度private_send_char()が実行される…なんてことが起きるんだと思います。連続したデータを受信し、送受信が並行して行われるような状態にするとすぐデータが化けました。sendChar()にcli()、sei()を追加すればいいようにも、それだけじゃ駄目な気もしますw。

じゃああんたはどうしたのって話ですが、UDR0が空いたときに発生するISR(USART_UDRE_vect)を使いました。sendChar内は割込禁止で実行し、空のリングバッファへのプッシュならUDRIE0もセット。ISR(USART_UDRE_vect)内で(唯一の)UDR0への書き込みを行い、既にリングバッファが空だったらUDRIE0をリセットをする、ってな案配です。

yaneuraoyaneurao 2014/03/18 03:13 ↑あ、そうなんですね。コメント、ありがとうございます。私のほうは、AVRプログラミングのことはすっかり忘れていて、もう思い出せませんが、あとからこのページをご覧になる方にはとても参考になることでしょう。ありがとうございます。

 | 

1900 | 01 |
2004 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
2005 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
2006 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
2007 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
2008 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
2009 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
2010 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
2011 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
2012 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
2013 | 01 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
2014 | 01 | 02 | 03 | 04 | 06 | 08 | 10 | 11 | 12 |
2015 | 01 | 02 |


Microsoft MVP
Microsoft MVP Visual C# 2006.07-2011.06