USBUART の RX にも FIFO 機能を装備する
PSoC Advent Calendar 2016の19日目の記事です。
前回の記事では、 USBUART の TX 側に FIFO を実装しました。 今回は、 RX 側にも FIFO を実装します。
RX 側 FIFO の考え方
RX 側の FIFO も TX 側と同じ考え方で実装します。
- 周期的にエンドポイントを監視して、データが届いていたらバッファが空である事を確認してバッファにデータを取り込む。
- 一文字取り出し関数でバッファから文字を取り出す。
実は、この動作そのものは FIFO を使わない場合でも同じです。 これは、エンドポイントに貯まったデータを一文字ずつ取り出す方法がなく、すべて取り出さなくてはならないためです。
エンドポイントの監視周期は、 TX 側と同じ 2kHz の割り込みを使います。
ファームウェア
回路図およびコンポーネントの設定は、前回と同様です。 ファームウェアは、以下のようになりました。
#include "project.h" // FIFO 機能のON/OFF //#define NOFIFO // USBUARTのパケットサイズ #define UART_TX_QUEUE_SIZE (64) #define UART_RX_QUEUE_SIZE (64)
冒頭の部分には、受信で使うパケットのサイズ定義が追加されています。 送信の場合と同じように、 USBUART が使用する BULK パケットのサイズをそのまま FIFO バッファのサイズとしています。
// USBUARTのTXキューバッファ uint8 uartTxQueue[UART_TX_QUEUE_SIZE]; // TXキュー uint8 uartTxCount = 0; // TXキューに存在するデータ数 CYBIT uartZlpRequired = 0; // 要ZLPフラグ uint8 uartTxReject = 0; // 送信不可回数 // USBUARTのRXキューバッファ uint8 uartRxQueue[UART_RX_QUEUE_SIZE]; // RXキュー uint8 uartRxCount = 0; // RXキューに存在するデータ数 uint8 uartRxIndex = 0; // RXキューからの取り出し位置 CYBIT uartRxCRDetect = 0; // CR検出フラグ
送信の時に定義したキューバッファと同様の定義が続きます。 受信の場合だけに宣言されている uartRxCRDetect は、行末記号として CR を受信した場合にセットされます。 行末記号として CR + LF を受信した場合、このフラグがセットされた状態で LF を受信する事になります。 このようなときには、 CR が送られてきた時に行末符号を返して、次の LF を無視しています。
この後、以前の記事と同じく送信に関する記述が続きます。 今回は、省略します。
#ifdef NOFIFO
// 1バイト受信する関数
int16 getch_sub(void) {
int16 ch = -1;
uint8 state = CyEnterCriticalSection();
if (uartRxIndex >= uartRxCount) {
// 受信キューが空かつ
if (USBUART_DataIsReady()) {
// データが到着していたら
uartRxCount = USBUART_GetAll(uartRxQueue); // バッファに取り込む
uartRxIndex = 0;
}
}
if (uartRxIndex < uartRxCount) {
// 受信キューに文字が残っていたら
ch = uartRxQueue[uartRxIndex++]; // 受信キューから一文字取り出す
}
CyExitCriticalSection(state);
return ch;
}
FIFO を使わない場合、この関数が1バイトの受信に使用されます。 前半では、受信キューに確実にデータを準備しています。 具体的には、受信バッファが空の場合にはエンドポイントからデータをバッファに取り出しています。
後半では、受信キューから1バイトのデータを取り出して、関数の返り値としています。
この関数を使用した場合、この関数の中で次のパケットを受け取るため、文字が受信されるまで処理が止まってしまう可能性が有ります。
#else // define(NOFIFO)
// 受信側割り込みサービス制御
void uartRxIsr(void) {
uint8 state = CyEnterCriticalSection();
if (uartRxIndex >= uartRxCount) {
// 入力バッファが空かつ
if (USBUART_DataIsReady()) {
// データが到着していたらバッファに取り込む
uartRxCount = USBUART_GetAll(uartRxQueue);
uartRxIndex = 0;
}
}
CyExitCriticalSection(state);
}
これに対して、 FIFO を使う場合には、エンドポイントからデータを取り出す前半部分を周期割り込みで処理して、後半部分をメインループ内で処理しています。 この割り込みサービスルーチンも、 Critical Section をつくって、他の割り込みの介入を排除しています。
// 1バイト受信する関数
int16 getch_sub(void) {
int16 ch = -1;
uint8 state = CyEnterCriticalSection();
if (uartRxIndex < uartRxCount) {
// 受信キューに文字が残っていたら
ch = uartRxQueue[uartRxIndex++]; // 受信キューから一文字取り出す
}
CyExitCriticalSection(state);
return ch;
}
#endif // define(NOFIFO)
後半部分は、1バイト受信関数に記述されています。 受信キューからデータを取り出すだけの簡単な構成です。
// USBUARTから一文字受け取る
int16 getch(void) {
int16 ch = getch_sub();
if (uartRxCRDetect && ch == '\n') {
uartRxCRDetect = 0;
ch = getch_sub();
} else if (ch == '\r') {
ch = '\n';
uartRxCRDetect = 1;
}
return ch;
}
実際に一文字を返す関数では、行末記号の処理を行っています。 この処理により、行末が CR、 LF、 CR+LF のいずれであっても、 '\n' を返す事ができます。
#ifndef NOFIFO
// 周期的にUSBUARTの送受信を監視する
CY_ISR(int_uartQueue_isr) {
uartTxIsr();
uartRxIsr();
}
#endif // !define(NOFIFO)
割り込み処理ルーチンには、送信に使われていた関数に加えて受信に使われる関数が追加されました。
int main(void) {
uint32 nLine = 0; // 行番号
uint32 nChars = 0; // 文字数
CyGlobalIntEnable; // 割り込みの有効化
USBUART_Start(0, USBUART_5V_OPERATION); // 動作電圧5VにてUSBFSコンポーネントを初期化
#ifndef NOFIFO
int_uartQueue_StartEx(int_uartQueue_isr); // 周期タイマを起動する
#endif // !define(NOFIFO)
for(;;) {
// 初期化終了まで待機
while (USBUART_GetConfiguration() == 0);
USBUART_IsConfigurationChanged(); // CHANGEフラグを確実にクリアする
USBUART_CDC_Init(); // CDC機能を起動する
for (;;) {
// 設定が変更されたら、再初期化をおこなう
if (USBUART_IsConfigurationChanged()) {
break;
}
// CDC-OUT : 行ごとに受信文字数を表示する
{
int16 ch = getch();
if (ch >= 0) {
nChars++;
if (ch == '\n') {
putdec32(nLine, 7);
putstr(" - ");
putdec32(nChars, 7);
putstr("\n");
nLine++;
nChars = 0;
}
}
}
// CDC-Control : 制御コマンドは無視する
(void)USBUART_IsLineChanged();
}
}
}
メインループにこのアプリケーションの処理が記述されています。 このアプリケーションでは、受信したデータの行ごとに文字数を数えて、行番号と文字数を送信します。 文字数を見る事で受信データに抜けや重複が無いかを確認し、行番号を見る事で受信したデータ量を求めることができます。
実行してみた
プロジェクトが出来たので実行してみました。 TeraTerm から一行59文字の巨大なテキストファイルを送り込んでみました。 行末が CR+LF になっているため、出力に表示される一行当たりの文字数は 58 バイトになっています。
10万行のデータを送って所要時間を測定したところ、98秒かかりました。 実行スループットは 59kiB/s と計算できます。
FIFO を使わなかったら
前回と同様に FIFO を使わない設定も試してみましたが、やはりボロボロになってしまいました。 送信側がうまく働いていないのだから、あたりまえと言えばあたりまえですが。
プロジェクトアーカイブ
この記事で作成したプロジェクトは、このファイルの拡張子を "zip" に変更すると再現できます。
関連商品

CY8CKIT-059 PSoC 5LP Prototyping Kit
- 出版社/メーカー: スイッチサイエンス
- メディア: エレクトロニクス

SparkFun FreeSoC2 開発ボード - PSoC5LP
- 出版社/メーカー: Sparkfun
- メディア: エレクトロニクス
この記事へのコメント