UART割り込み受信の落とし穴と対策リングバッファ・OERR・FERRを実装する

通信(USART / RS-485 / DMX)

前回の記事では、UARTでの送受信とコマンド制御までを実装しました。

今回の記事では、
まず、割り込み制御によるコマンド制御を行います。

次に、リンクバッファを使って複数文字を同時に受信し、
コマンド制御を行います。

さらに、大量データが一気に流入して処理が追い付かない状態に発生する
OERRの処理を行い、
通信フォーマットが崩れて起きるFERR(フレーミングエラー)についての処理も
学びました。

回路図

以前までと同じ回路図です。


割り込み制御

前回の記事のメインに入れていたものを単純に
interrupt()に入れるだけのことです。

c

void __interrupt() isr(void) {
    if (PIR1bits.RCIF) {

        rx_data = RCREG;
        rx_flag = 1;
    }
}

変数はグローバルで設定済みです。

c

while (1) {
        if (rx_flag) {
            rx_flag = 0;
            while (PIR1bits.TXIF) {
                TXREG = rx_data;
            }
            if (rx_data == 'a') {
                LATAbits.LATA1 = 1;
            } else if (rx_data == 'b') {
                LATAbits.LATA1 = 0;
            } else if (rx_data == 't') {
                LATAbits.LATA1 = !LATAbits.LATA1;
            }
        }
    }

割り込み制御でのフラグが立ったら、
コマンド制御をさせるというだけのことです。
前回の記事と大きな違いはありません。
そのため特に動画などは撮りませんでした。

リングバッファ

通信は、1文字ずつ受信することばかりではなく、
むしろまとめて受信することばかりです。

これまでのようなやり方では、
1文字しか受信できないため、
まとめてもれなく受信する方法が

リングバッファ

になります。

下がイメージ図です。

c
//割り込み内での処理

if (PIR1bits.RCIF) {

        unsigned char next = (head + 1) % BUFFER_SIZE;

        if (next != tail) {
            buffer[head] = RCREG;
            head = next;
        } else {
            unsigned char dummy = RCREG;
        }
    }
c

//mainループ内での処理

if (head != tail) {
            unsigned char data = buffer[tail];
            tail = (tail + 1) % BUFFER_SIZE;

            while (!PIR1bits.TXIF);
            TXREG = data;


            if (data == 'a') {
                LATAbits.LATA1 = 1;
            } else if (data == 'b') {
                LATAbits.LATA1 = 0;
            } else if (data == 't') {
                LATAbits.LATA1 = !LATAbits.LATA1;
            }
        }

上の図と、コードの様に
headが書き込み位置を決めて、
受信したコードを順番に書き込みます。

それをmainのループ内で
tailが読み込み位置をなぞり、
読み込んでいきます。

OERR(オーバーランエラー)対策

リングバッファを使うことで、受信したデータを一時的に保存できるようになりました。

しかし、これだけではまだ完全ではありません。

UARTでは、受信データを読み出す前に次々とデータが入ってくると、
受信バッファがあふれてしまうことがあります。

この状態が OERR(Overrun Error)です。

OERR が発生すると、USARTの受信が停止してしまいます。
つまり、一度OERRになると、RCREGを読んだだけでは復旧できません。

そのため、CRENビットを一度クリアして、再度セットします。


if (RCSTAbits.OERR) {
    RCSTAbits.CREN = 0;
    RCSTAbits.CREN = 1;
}

CRENは連続受信を有効にするビットです。

OERRが発生した場合は、
一度受信機能をOFFにしてからONに戻すことで、
USARTの受信状態を復旧させます。

今回の割り込み処理では、最初にOERRを確認するようにしました。


void __interrupt() isr(void) {

    if (RCSTAbits.OERR) {
        RCSTAbits.CREN = 0;
        RCSTAbits.CREN = 1;
    }

    if (PIR1bits.RCIF) {

        unsigned char next = (head + 1) % BUFFER_SIZE;

        if (next != tail) {
            buffer[head] = RCREG;
            head = next;
        } else {
            unsigned char dummy = RCREG;
        }
    }
}

OERRは、リングバッファとは別の問題です。

リングバッファはソフト側で受信データをためる仕組みですが、
OERRはUSART内部の受信処理が追いつかなかったときに発生するエラーです。

そのため、リングバッファを使っていても、
OERR対策は必要になります。

FERR(フレーミングエラー)対策

次に、FERR(Framing Error)についても確認しました。

UART通信では、1文字のデータは

スタートビット → データビット → ストップビット

という形で送られます。

通常、ストップビットは「1」になります。

しかし、ノイズやボーレートのズレなどで、
ストップビットが正しく受信できない場合があります。

この状態が FERR(フレーミングエラー)です。

PICでは、FERRが発生しているかどうかを
RCSTAbits.FERR で確認できます。


if (RCSTAbits.FERR) {
    unsigned char dummy = RCREG;
    return;
}

FERRは、受信した1文字に対して発生するエラーです。

そのため、FERRが立っていた場合は、
そのデータは壊れている可能性があるため、RCREGを読んで捨てます。

ここで重要なのは、
RCREGを読まないと受信データが残ったままになるという点です。

つまり、エラーが出たデータであっても、
一度RCREGを読んで処理を進める必要があります。

今回のコードでは、
FERRが発生した文字はリングバッファには入れず、
そのまま捨てるようにしました。

最終的な割り込み処理

OERRとFERRの処理を加えた割り込み処理は、以下のようになります。


void __interrupt() isr(void) {

    if (RCSTAbits.OERR) {
        RCSTAbits.CREN = 0;
        RCSTAbits.CREN = 1;
    }

    if (PIR1bits.RCIF) {

        if (RCSTAbits.FERR) {
            unsigned char dummy = RCREG;
            return;
        }

        unsigned char next = (head + 1) % BUFFER_SIZE;

        if (next != tail) {
            buffer[head] = RCREG;
            head = next;
        } else {
            unsigned char dummy = RCREG;
        }
    }
}

まとめ

今回は、UARTの割り込み受信を実装し、
さらにリングバッファを使って複数文字の受信に対応しました。

最初は1文字ずつの受信だけを想定していましたが、
実際にはTera Termからの貼り付けやファイル送信のように、
短時間で複数文字が連続して送られてくることがあります。

そのため、受信したデータを一時的に保存するリングバッファが必要になります。

また、大量データが一気に流れた場合には、
OERRによってUSARTの受信が止まる可能性があるため、
CRENをクリアして復旧する処理も追加しました。

さらに、通信フォーマットが崩れた場合に発生するFERRについても確認し、
エラーが発生したデータはRCREGを読んで捨てるようにしました。

今回の内容で、UART受信処理はかなり実用的な形に近づいたと思います。

次回は、このUARTの知識をもとに、
いよいよDMX通信の入口に進んでいきます。

コメント

タイトルとURLをコピーしました