クレア工房 | 電子工作 | AVR | AVRの USART

AVR の USART

ATtiny2313, ATmega88 の系統には、 Universal Synchronous/Asynchronous Serial Receiver/Transmitter (USART) が搭載されています。 PC のシリアルポートと接続してコマンドをやりとりする機能を実装する手段としては、 USART の利用が手軽です。

AVR の USART は、伝統的にポートDの一部を占有します。 品種によって USART のレジスタ名が少し違いますが、使い方は概ね同じです。

クロック

PC と通信する場合は、 AVR のクロックとしては 8MHz の水晶を選ぶと比較的に楽かも知れません。 個人的には 19200bps 程度を上限と割り切ってしまって、 CPU の処理速度を優先して 20MHz とか 10MHz を選ぶこともあります。

初期化

F_CPU から計算します。 コンパイラの桁あふれの問題を忘れがちなので注意したいところ。 AVR-GCC では int == 16bit ですから、"L" を忘れると溢れます。

次のコード例は ATmega88 向けです。 16MHz のクロックで19200bpsを指定しています。 非同期 (調歩同期)、データ8ビット、パリティなし、ストップ1ビット (俗に 8N1 と呼ばれるパラメータ) です。

#ifndef F_CPU
#  define F_CPU  16000000L
#endif

void
com_init()
{
  uint16_t  rate;

  rate = (F_CPU/19200/16) - 1;
  UBRR0H = rate >> 8;
  UBRR0L = rate;

  /* async, non-parity, 1-bit stop, 8-bit data */
  UCSR0C = _BV(UCSZ01)|_BV(UCSZ00);

  /* receiver enable, transmitter enable */
  UCSR0B = _BV(RXEN0)|_BV(TXEN0);
}

受信

次のコード例は ATmega88 向けで、 受信バッファが空の場合は受信されるまで待ちます。 何かしら受信されるまで永遠に待ちますので、 時間に制限のある割り込み処理の中から呼ぶのには適していません。

uint8_t
com_getch()
{
  /* wait for a byte received */
  while ((UCSR0A & _BV(RXC0)) == 0)
    ;
  return UDR0;
}

割り込みを使う場合は ISR(USART_RXC_vect) あたりで UDR を読みます。 (USARTが2つある場合、USART0がUDR0、USART1がUDR1です)

RAM を FIFO に割けるほど余裕がない場合が多いかと思います。 ATtiny2313, ATmega88 は USART に FIFO 機能が組み込まれていますので、 1〜2バイトまでであれば FIFO に頼ることができます。 プロトコル設計を工夫して、 受信割り込みの利用を避けるのもひとつの手段です。 受信したときの時刻が重要な意味をもつリアルタイムアプリケーションの場合、 データが少量でも積極的に受信割り込みを使うべきかも知れません。 割り込みを使うか否かは、実現すべき機能によって変わります。

送信

次のコード例は ATmega88 向けで、 バッファが空くのを待ってから送信します。 送信シフトレジスタの空きを示す TXC0 ではなく、 バッファレジスタの空きを示す UDRE0 を確認します。 バッファが空くまで限りなく待ちますので、 時間に制限のある割り込み処理の中から呼ぶのには適していません。

void
com_putch(uint8_t c)
{
  /* wait for buffer empty */
  while ((UCSR0A & _BV(UDRE0)) == 0)
    ;
  UDR0 = c;
}

文字列を送信したい場合は、 フラッシュメモリのアドレスを渡して送信できるようにしておくと便利かも知れません。 次のコード例はヌル終端された文字列を送信します。終端を表すヌルは送りません。

void
com_puts(uint8_t __flash const *ptr)
{
  uint8_t ch;

  while (ch=*ptr++, ch!=0)
    com_putch(ch);
}

フラッシュメモリに文字列定数を確保してから、 そのアドレスを渡して使います。 1文字ごとにレジスタの空きを待ちますので、実行に時間がかかります。 割り込みタスクから呼ぶのではなく、メインタスクから呼ぶようにスケジュールします。

static uint8_t const __flash hello_text[] =
  "hello, world\r\n";

com_puts(hello_text);

なお、ここで例にあげたフラッシュメモリを指す予約語 __flash は、 比較的最近の gcc でないと使えません。 AVR Studio などをお使いの場合は #include <avr/pgmspace.h> などを参照してください。

割り込みを使う場合は ISR(USART_UDRE_vect) あたりで UDR に書き込みます。 ハンドシェークするタイミングを得るために送信完了を知りたい場合は、 UDRE フラグではなく TXC フラグを参照します。

フロー制御

AVR自身にハードウェア的なフロー制御機構はありませんので、 必要であればGPIOとソフトウェアで実装します。 MIDI の場合はフロー制御ができませんので、 バルクダンプを受ける可能性があるときは ワイヤレートを捌けることを確認するか、 または十分大きな (512byte程度の) 受信バッファを用意します。

もっとも、AVRで実装するペリフェラルについてはXON/XOFFや CTS/RTSなどによるフロー制御は実装されないのが通常です。 高速通信を行う場合はフロー制御を実装したくなることと思いますが、 昨今のPC向けシリアルポートは 512byte程度の巨大なFIFOを搭載している場合があり、 FIFOの分だけオーバーランすることを考慮する必要があります。 現実的には、1KB 程度の巨大な受信バッファを確保するか、 あるいはワイヤレートを捌けるように (取りこぼしても必要に応じて再同期できるように) 待ち時間の規約を含めたプロトコル、 データフロー、 そしてファームウェアを設計する必要があるでしょう。

フロー制御を実装しなければならない場合は、 SRAM を外部実装するなどして RAM に十分余裕のあるマイコンを選定されることをお勧めします。 AVRは概してRAMが貧弱で容量が少ないものですが、 ごく一部にRAMを外部増設できるものがあります。


Copyright © 2012-2014 clare. All rights reserved. 本ページに掲げたサンプルプログラムは、 筆者の著作に帰すべき部分についてpublic domainに準じます。 筆者は運用結果にいかなる責任も負いません。