AVRのUSARTを用いた3線シリアル通信

本文書の取り扱い

クリエイティブ・コモンズ・ライセンスこの文書は、クリエイティブ・コモンズ・ライセンスの下でライセンスされています。

概要

  • フロー制御を用いない三線シリアル通信をUSART割り込みとリングバッファで実装

実装方針

  • 受信可能割り込みと送信可能割り込みに対応して受信と送信を行う
  • 送受信データはリングバッファ上に格納
    • 受信可能割り込み発生時は受信データのリングバッファに受信を行う
    • 送信可能割り込み発生時は送信データのリングバッファから送信を行う
  • 割り込み禁止時間を出来る限り小さくする。
  • C言語のみで実装
  • 文字列以外のデータの送信にも対応できるように read(), write() 相当の関数として実装

開発環境

  • WindowsXP SP3
  • AVR Studio 4.18 SP2
  • TeraTermPro
  • ATmega644P

評価

  • ATmega644P (20Mhz) のUSARTを2チャンネル使用して38400bpsでPC同士で通信。
    • 送受信に用いたデータはThe Book of Genesis(聖書の創世記)
    • 送信と受信を独立テスト
      • TeraTermを用い、一方からファイル送信を実行し、反対側でファイルを受信。その後diffで比較
    • 送信と受信の混合テスト
      • TeraTermを用い、両方からほぼ同時にファイル送信と受信を実行。その後diffで比較
  • 送受信での欠落や多重受信の発生は確認されなかった。

ソースコード

  • USART0のみを利用
usart.h
/**
 * @file  usart.h
 * @brief USART通信のヘルパー関数や設定処理
 */
#ifndef __USART__H__
#define __USART__H__

/**
 * @def USART0_BAUD_RATE
 * @brief USART通信のポーレート
 */
#define USART0_BAUD_RATE 19200


/**
 * @def BUF_SIZ 
 * @brief 送受信バッファサイズ(2の倍数でUINT8で表現可能な範囲とする)
 */
#define BUF_SIZ (64)

/**
 * @typedef UINT8
 * 8bit無符号整数型
 */
typedef unsigned char UINT8;

/**
 * @brief USART通信初期化
 */
extern void usart_open(void);

/**
 * @brief USART通信終了
 */
extern void usart_close(void);

/**
 * @brief USART通信の入出力バッファをクリアする
 */
extern void usart_clear( void );

/**
 * @brief Cのread相当の動作
 * @param [in] buf 受信したバイト列を格納するバッファ
 * @param [in] count 受信したバイト列を格納するバッファのデータ長
 * @return 実際に受信したバイト長
 */
extern int usart_read(void *buf, int count);

/**
 * @brief Cのwrite相当の動作
 * @param [in] buf 送信するバイト列
 * @param [in] count 送信するバイト列のデータ長
 * @return 送信したバイト長
 */
extern int usart_write(void *buf, int count);

#endif
usart.c
/**
 * @file  usart.c
 * @brief USARTを用いた三線シリアル通信
 */

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

#include "usart.h"

/**
 * @typedef usart_t 
 * @brief USART通信が用いるワークエリアの型
 */
typedef struct {
	UINT8 recv_data[BUF_SIZ];	/** 受信データを格納するリングバッファ */
	UINT8 recv_size;		/** 受信データのサイズ */
	UINT8 recv_read;		/** 受信データの読み込み位置 */
	UINT8 send_data[BUF_SIZ];	/** 送信データを格納するリングバッファ */
	UINT8 send_size;		/** 送信データのサイズ */
	UINT8 send_read;		/** 送信データの読み込み位置 */
} usart_t;

/**
 * @var usart
 * @brief USART通信が用いるワークエリア
 */
static volatile usart_t usart;

/**
 * @brief USART通信初期化
 */
void usart_open(void) {
	/* 割り込み禁止 */
	cli();

	/* UBRR0の算出 */
	#define UBRR_USART0 (((F_CPU>>4)+(USART0_BAUD_RATE>>1))/USART0_BAUD_RATE-1)

	/* USART0の送受信と割り込みを全て禁止 */
	UCSR0B = 0;

	/* ボーレート(16bit値)を8bitづつ設定 */
	UBRR0H = (UINT8)(UBRR_USART0>>8);	/* 上位8bit */
	UBRR0L = (UINT8)(UBRR_USART0   );	/* 下位8bit */

	/* ストップ:1ビット, データ:8bit, ノンパリティを設定 */
	UCSR0C = 3 << UCSZ00;

	/* 入出力バッファをクリア */
	usart_clear();

	/* USART0の送受信と割り込みを許可 */
	UCSR0B = (1 << RXEN0) | (1 << TXEN0) | (1 << RXCIE0) | (1 << TXCIE0);

	#undef UBRR_USART0

	/* 割り込み禁止解除 */
	sei();
}

/**
 * @brief USART通信終了
 */
void usart_close(void) {
	/* 割り込み禁止 */
	cli();

	/* USART0の送受信と割り込みを全て禁止 */
	UCSR0B = 0;

	/* 割り込み禁止解除 */
	sei();
}


/**
 * @brief USART通信の入出力バッファをクリアする
 */
void usart_clear( void ) {
	cli();
	usart.recv_size = 0;
	usart.recv_read = 0;
	usart.send_size = 0;
	usart.send_read = 0;
	sei();
}

/**
 * @brief C言語のread()関数相当の動作
 * @param [in] buf 受信するバイト列
 * @param [in] count 受信するバイト列のデータ長
 * @return 受信したバイト長
 */
int usart_read(void *buf, int count) {
	UINT8 *p = (UINT8*)buf;
	while ( count-- > 0 ) {
		UINT8 s;
		cli();
		s = usart.recv_size;
		if (s == 0) {
			sei();
			break;
		} else {
			UINT8 r = usart.recv_read;
			UINT8 data = usart.recv_data[r];
			usart.recv_read = (r+1) & (BUF_SIZ-1);
			usart.recv_size = s-1;
			sei();
			*p++ = data;
		}
	}
	return p-((UINT8 *)buf);
}

/**
 * @brief Cのwrite相当の動作
 * @param [in] buf 送信するバイト列
 * @param [in] count 送信するバイト列のデータ長
 * @return 送信したバイト長
 */
int usart_write(void *buf, int count) {
	UINT8 *p = (UINT8 *)buf;
	UINT8 flag = 1;
	while ( count-- > 0 ) {
		UINT8 data = *p;
		UINT8 s;
		cli();
		s = usart.send_size;
		if ( s >= BUF_SIZ ) {
			sei();
			break;
		} else {
			UINT8 r = usart.send_read;
			UINT8 w = (s + r) & (BUF_SIZ-1);
			usart.send_data[w] = data;
			usart.send_size = (s + 1) & (BUF_SIZ-1);
			sei();
			p++;
			flag = 0;
		}
	}

	if (flag == 0) {
		cli();
		if (UCSR0A & (1<<UDRE0)) {
			/* 送信可能なら1文字送信する */
			UINT8 s = usart.send_size;
			if (s > 0) {
				UINT8 r = usart.send_read;
				UDR0 = usart.send_data[r];
				usart.send_read = (r + 1) & (BUF_SIZ-1);
				usart.send_size = s - 1;
			}
		}
		sei();
	}

	return p-((UINT8 *)buf);
}

/**
 * @brief USARTのデータ受信時に発生する割り込みの処理
 * @note USART割り込みベクタに登録されるルーチン
 */
ISR(USART0_RX_vect) {
	UINT8 s = usart.recv_size;
	if (s < BUF_SIZ) {
		UINT8 w = (s + usart.recv_read) & (BUF_SIZ-1);
		usart.recv_data[w] = UDR0;
		usart.recv_size = s+1;
	}
}

/**
 * @brief USARTが送信可能になった時に発生する割り込みの処理
 */
ISR(USART0_TX_vect) {
	UINT8 s = usart.send_size;
	if (s > 0) {
		UINT8 r = usart.send_read;
		UDR0 = usart.send_data[r];
		usart.send_read = (r + 1) & (BUF_SIZ-1);
		usart.send_size = s - 1;
	}
}
サンプルコード
#include <avr/io.h>
#include <avr/interrupt.h>
#include <util/delay.h>

#include "./usart.h"

__attribute__ ((naked)) 
int main(void) {
	usart_open();

	sei();

	for(;;) {
		UINT8 buf[BUF_SIZ];
		int n;
		n = usart_read(buf, sizeof(buf));
		usart_write(buf, n);
	}

	return 0;
}