Skip to content

ReadUART

askn37 edited this page Dec 20, 2023 · 1 revision

ReadUART クラスリファレンス

最小フットプリントの、受信バッファ付き二線式準全二重UART通信ReadUART_Class基本クラス。 Arduino互換APIの<HardwareSerial.h>を代替する。 プロトコルは 8N1 専用。 送信方向は割込も計時器周辺機能もFIFOバッファも使用しないが、受信方向は割込を使用する。 従って全体割込禁止中でも、割込ハンドラの中からでもwriteは使用できるが、readBytes以外の受信動作は禁止される。

USART[n]周辺機能に対して<api/HarfUART.h>とは排他使用となる。

このクラスは複数のシリアルポートを同時並行使用する際の受信漏れを緩和する目的で使用される。 しかし割込を使うため高速高頻度通信には必ずしも適さない。(性能限界が低い)

reduceAVR は未対応

用例

#include <ReadUART.h>

Settings_SerialR0A(ReadUART_BUFFSIZE);

#ifdef Serial
#undef Serial
#define Serial SerialR0A
#endif

void setup (void) {
  Serial.begin(CONSOLE_BAUD);   /* {build.console_baud} */
}
void loop (void) {
  if (Serial.available()) Serial.write( Serial.read() );
}

送信方向はHarfUARTと同一の動作をする。すなわち 1文字分に限って全二重動作となる。 受信方向は受信割込を介して、指定量の緩衝バッファに蓄積される。 バッファ量指定は 1以上の任意値で与えることができ、2の冪乗である必要もない。省略はできない。 既定値はReadUART_BUFFSIZE定数(一般に32または64)で与えられる。 割込禁止中の受信動作readはできない。

行単位で着実に送信受信方向動作を切り替えるならreadBytesを用いるハンドシェイク動作とするのが良い。 前述の用例よりこちらのほうが実用にはより適する。 ポーリングを行うため、全体割込禁止中でも動作する。

void setup (void) {
  Serial.begin(CONSOLE_BAUD);
}
void loop (void) {
  size_t length;
  char buff[INTERNAL_SRAM_SIZE / 4]; // 適当な大きさの1行ぶんのバッファメモリ
  length = Serial.readBytes(&buff, sizeof(buff), '\n'); // `\n` 受信で方向切替
  if (length) Serial.write(&buff, length);
}

組込用途での UART利用はあらかたハンドシェイク動作なので全二重対応が必要な場面は少ない。

HarfUART との相違

  • peeklastfind メソッドが使用できる。
  • 全体割込禁止中は受信動作readが行われない。(readBytesは全体割り込み禁止中でも動作する)
  • 受信バッファはリング構造であるが、溢れた場合はその一周分を暗黙のうちに破棄する。(statusエラーは残さない)
  • 割込許容上限を超える頻度での受信動作は、主動作を暗黙のうちに停止させる。

<ReadUART.h>

依存性:<avr/io.h> <util/atomic.h> <Portmux.h> <peripheral.h> <api/Print.h>

クラスインスタンス

Settings_<インスタンス名>宣言は選択中の MCUで使用可能なものだけが用意されている。 Rの付かない同名のHarfUARTインスタンス名とは排他使用となる。 端的には<peripheral.h>で定義されるSerial[n][x]SerialR[n][x]に変えたものが使用可能。

#include <ReadUART.h>
Settings_SerialR0A(ReadUART_BUFFSIZE);
Settings_SerialR1A(1024);
Settings_SerialR2A(1024);
// etc...

ただし、同一ポートグループ内では使用できるのは単一のインスタンスだけである。

Settings_SerialR0A(ReadUART_BUFFSIZE);
Settings_SerialR0B(ReadUART_BUFFSIZE);  // ポートグループが重複するのでエラー

ソースコード全体ではただ一箇所でしか、同一インスタンス名は宣言できない。 異なる翻訳単位(ファイル)からはexternでインポートする。

#include <ReadUART.h>
extern ReadUART_Class SerialR0A;

Serial[n]エイリアスは通常、HarfUARTインタフェース名を指しているため、必要に応じて再定義しなければならない。

#ifdef Serial
#undef Serial
#define Serial SerialR0A
#endif

ポートグループはプライマリが0、以後追加ポート以降に1 2... の識別子が付く。
PORTMUX の DEFAULT選択がA、ALTERNATE_1がB、以後C D... の接尾子が付く。
インスタンスが複数あっても同一周辺機能なら同時使用できないことに注意。(違うクラスであっても)

どの型番でどの周辺機能が使用できるかは [modernAVR 周辺機能比較一覧] を参照のこと。

ReadUART_Class& begin (const uint32_t _baudrate)

UARTの使用を、任意のボーレート値で開始する。 自身のオブジェクトを返すので、メソッドチェーンを後続できる。 ボーレート値から実際に必要な設定値を計算してinitiateを呼び出すため、 事前コンパイル済定数を与えないと除算ライブラリが結合されることに注意。

Serial.begin(CONSOLE_BAUD);

F_CPUによって動作可能速度が定まるため、計算範囲外となる値では正常動作しない。 CPU主クロックは設定ボーレートの 8倍以上 8192倍未満でなければならない。 ただし限界付近ではマージンが不足するため安定しない。

特に受信方向は割込を経由するため、HarfUARTよりも最大実用速度は低下する。

ReadUART_Class& initiate (const uint16_t _baudrate)

UARTの使用を、指定の定数ラベルで開始する。 自身のオブジェクトを返すので、メソッドチェーンを後続できる。 定数ラベルは<Portmux_private.h>で事前計算され定義されている。 除算は使われない。

Serial.initiate(UART_115200);
/* etc */
Serial.initiate(UART_CONSOLE_BAUD);

F_CPUによって動作可能速度は定まるため、計算範囲外となる定数ラベルは定義されない。

UART_CONSOLE_BAUDは事前定義マクロCONSOLE_BAUDを元に生成されるが、 OSC-ULP 動作時は最大 2400bps、1200bps、あるいは 300bps の設定で上書きされるだろう。 F_CPU が 2400 未満では、Arduino IDE のシリアルモニター(下限300bps)とは通信できない。

void end (void)

UART装置の使用を停止し、専有していた外部端子を開放する。 受信割込も停止する。 RX線の内蔵プルアップも無効になる。

  • 一般に、UART対向機器の電源が落ちている場合は TX線が Hi-Zか LOWになるだろう。 これを調べるには TX端子をデジタル入力許可に変えなければならない。 end後はそれが可能な状態になる。

size_t write (const uint8_t _c)

1キャラクタを出力する。 送信緩衝バッファが空いていなければ空くまで待つ。 常に1を返す。 全体割込が禁止されていても動作する。

size_t write (const uint8_t* _buffer, size_t _length)

指定のバッファから指定の量を書き出す。書けた量を返す。 全体割込が禁止されていても動作する。

int read (void)

受信バッファから1キャラクタを取り出して返す。 入力が空かフレームエラーがあれば -1 を返す。 全体割込が禁止されていると正しく動作しないため、受信キャラクタ落ちを経験するだろう。

size_t readBytes (void* _buffer, size_t _limit, char _terminate = 0, uint8_t _swevent = 0)

指定の_bufferに、最大_limit文字数、 あるいは_terminate指定キャラクタ出現までを取得する。取得されたキャラクタ数を返す。 _bufferの最後に\0は補完されないことに注意。

_terminate指定キャラクタは取得内容に含まれる。しかし\0は指定できない。 受信キャラクタが\0であればそれは含まれる。 そしてBREAK と見做して受信待機を打ち切るだろう。

このメソッドが呼ばれると、少なくとも 20bit時間のあいだ通信線を監視して IDLE 状態が続いたなら何も取得せずに終了し0を返す。

つまりNOBLOCK動作であるから、必要に応じて繰り返し呼び出さなければならない。

逆に_bufferに格納される最初の文字はその前に 約20bit長以上の IDLE 状態が検出されなければならない。

従って例えば シリアルGPSの NMEA出力は、概ね新たな行頭から採取されるだろう。

_swevent0以外であるなら、それは事象システム EVSYS ストローブ指令と解釈され、 最初の受信キャラクタ検出の瞬間に以下の IOレジスタへ代入し、 任意の事象イベントを発火することが出来る。

tinyAVR-0/1 megaAVR-0 その他のmodernAVR
EVSYS_ASYNCSTROBE EVSYS_STROBE EVSYS_SWEVENTA

各ビットがEVSYSチャネル番号[0-7]に対応。チャネル8番以上には対応していない。
発火イベントが割込を惹起する場合はそちらの実行が優先され、そして受信落ちを経験するだろう。 だが全体割込禁止中でもこのメソッドが機能することは留意されたい。
この機能は例えば 1PPS割込のない シリアルGPSで、秒タイミングを測るのに使える。

size_t available (void)

受信バッファに格納されているキャラクタ数を返す。

size_t availableForWrite (void)

送信緩衝バッファが空いていれば(待たずにwrite可能なら)1を返す。 空いていなければ(前の送信実行中であれば)0を返す。 全体割込が禁止されていても動作する。

void flush (void)

送信緩衝バッファが空でかつ送信完了になるのを待つ。 全体割込が禁止されていても動作する。

int peek (void)

空ではない受信バッファに格納されている最初の文字を返す。 これは次にreadで読まれる文字である。 何も格納されていなければ -1 を返す。

uint8_t last (void)

最後に受信したUSARTn_DATALの値を返す。 一般にこれは空ではない受信バッファの最後尾にあるキャラクタに等しい。

bool find (const uint8_t _c = '\n')

空ではない受信バッファ内から指定キャラクタを探して見つかれば真を返す。 キャラクタ未指定時は'\n'を探す。

走査中に受信バッファが溢れた場合の結果は信用できない。
指定キャラクタまでのバッファ文字列取得コピーは、readBytesを使うと良い。

uint8_t status (void)

最後に更新されたUSARTn_DATAHの値を返す。

Print& print (...)

print文とその派生メソッドは<api/Print.h>を参照のこと。

....

この他の、特にタイムアウトを伴うストリーム読込系の機能は用意されていない。