SSブログ

USBUART の RX にも FIFO 機能を装備する [PSoC]このエントリーを含むはてなブックマーク#

回路図

PSoC Advent Calendar 2016の19日目の記事です。

前回の記事では、 USBUART の TX 側に FIFO を実装しました。 今回は、 RX 側にも FIFO を実装します。

RX 側 FIFO の考え方

RX 側の FIFO も TX 側と同じ考え方で実装します。

  1. 周期的にエンドポイントを監視して、データが届いていたらバッファが空である事を確認してバッファにデータを取り込む。
  2. 一文字取り出し関数でバッファから文字を取り出す。

実は、この動作そのものは 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();
        }
    }
}

メインループにこのアプリケーションの処理が記述されています。 このアプリケーションでは、受信したデータの行ごとに文字数を数えて、行番号と文字数を送信します。 文字数を見る事で受信データに抜けや重複が無いかを確認し、行番号を見る事で受信したデータ量を求めることができます。

実行してみた

FIFO有り出力

プロジェクトが出来たので実行してみました。 TeraTerm から一行59文字の巨大なテキストファイルを送り込んでみました。 行末が CR+LF になっているため、出力に表示される一行当たりの文字数は 58 バイトになっています。

10万行のデータを送って所要時間を測定したところ、98秒かかりました。 実行スループットは 59kiB/s と計算できます。

FIFO を使わなかったら

FIFO無し出力

前回と同様に FIFO を使わない設定も試してみましたが、やはりボロボロになってしまいました。 送信側がうまく働いていないのだから、あたりまえと言えばあたりまえですが。

プロジェクトアーカイブ

この記事で作成したプロジェクトは、このファイルの拡張子を "zip" に変更すると再現できます。

関連商品

CY8CKIT-059 PSoC 5LP Prototyping Kit

CY8CKIT-059 PSoC 5LP Prototyping Kit

  • 出版社/メーカー: スイッチサイエンス
  • メディア: エレクトロニクス
SparkFun FreeSoC2 開発ボード - PSoC5LP

SparkFun FreeSoC2 開発ボード - PSoC5LP

  • 出版社/メーカー: Sparkfun
  • メディア: エレクトロニクス

nice!(1)  コメント(0)  トラックバック(0)  このエントリーを含むはてなブックマーク#

nice! 1

コメント 0

コメントを書く

お名前:
URL:
コメント:
画像認証:
下の画像に表示されている文字を入力してください。

トラックバック 0

トラックバックの受付は締め切りました