ソフトウェア処理の限界に挑む。AVR用高速シリアル・ライブラリを作ってみた。

以前に、送信専用ではあるがソフトウェアだけで実現した高速シリアルライブラリを紹介した。

Hi-Speed Serial TX Library for AVR Series

送信ができると受信もやってみたくなる。ただ、ソフトウェアのみによる受信処理にはピン変化割り込みを使うのが一般的であるが高速化という観点ではその割り込みレイテンシーが最高速度を制限してしまう。仮にビット毎に割り込み処理した場合どんなに頑張っても200Kbps程度が限界だろう。より高速化するには割り込みはスタートビット検出時のみとしそのまま一気にビットを組み立てるしかない。ちなみに割り込みではなくポーリング方式ならさらなる高速化を期待できるが他のことが全くできなくなってしまうので現実的ではない。

データシートによるとINTピン変化ラッチに2.5-3.5サイクル(PCINTは+2サイクル?)、割り込み実行に6-8サイクル、ここまでで既に10サイクル前後も必要だ。10サイクルは16MHzクロックで0.625usなので単純に考えると1Mbps程度が受信処理の限界値。但し、適切なタイミングで受信処理が開始できさえすれば、その後はソフトウェア処理でもビット当たり2サイクルで処理できるので全然余裕はある。

残る問題は、割り込み開始処理のレジスタ退避や受信データの保存処理が間に合うかどうかだが、そこはアセンブラでカリカリにチューニングして限界までサイクルカットするしか方法はないがアセンブラだけで作ってしまうと柔軟性に欠けるため、今回も、テンプレートクラスとインライン・アセンブラとの組み合わせで実装してみた。テンプレート・クラスによりアセンブラ・コードを柔軟に生成するための手段であり、ある意味、最強とも言える。

ちなみに1Mbpsでの余裕は0.5usもないので他の割り込みとカチあったりすると簡単に受信データ喪失或いは化けてしまうことに注意してほしい。割り込みを多用する機能(USB/UART/I2C/SPI)と同時利用したりするとかなりの高確率で受信データ喪失或いは化けが発生してしまうはずだ。タイマー割り込みもエラーを引き起こす原因となる可能性があると考えるべきだろう。特にC言語のコールバック関数を呼び出すタイプの割り込み処理はかなり大きなオーバーヘッドがかかるため気を付けなければならない。

受信データ喪失或いは化けはソフトウェア受信処理型の欠点でもあり対策としては通信速度を落とすぐらいのことしかないが通信速度を落とすと今度は割り込み禁止期間が長くなってしまうという弊害がでる。それが他の問題を引き起こすようなら通信速度は落とさずアプリ側にてリトライなどエラー対策を別途検討するしかない。

今のところ、手元にあるATtiny85/ATMega32U4で動作確認しているがATmega328はなぜか持っていないので動作未確認。

【仕様】
・半二重通信(送受信同時処理は出来ない)
・送信は、16MHzクロックで38400-8Mbpsまで。
・受信は、16MHzクロックで38400-1Mbpsまで。
・通信形式は、data 8-bit/none-parity/1-stopbit固定。
・送信ピンは任意のピンに対応。
・受信ピンはMAXUART_RXPIN_XX型を指定。
・割り込みハンドラーは未実装なので受信ピンに対応したMAXUART_RXPIN_XX_ISRマクロによりアプリ側で実装する必要がある。
・INT0/INT1,…PCINT0/PCINT1,…などの割り込みソース毎に一つのピンだけが受信可能。同じ割り込みソースで複数の受信ピンを同時利用することは出来ない。

【921600bpsでの受信開始タイミングの確認】

【概要】

ライブラリを開始する。

ライブラリを終了する。

受信バッファをクリアする。

受信バッファ内の受信済みバイト数を取得する。

受信バッファから1バイト読み込む。受信バッファが空の場合は、-1を返す。

受信バッファから複数バイト読み込む。バッファ(buf)に格納したバイト数を返す。

受信割り込み処理。受信割り込みハンドラーから呼び出される。

1バイトを送信する。

複数バイトを送信する。

【サンプルスケッチ】
PB1を送信ピン、PB2を受信ピンとする例。※Arduinoのピン名とは違うことに注意。

【修正履歴】
2022-10-08
1M未満の速度で受信が誤動作するのを修正。

2020-07-19
ストップビット送信処理を全AVRシリーズ共通コードに変更。

2020-07-10
ライブラリのクラス名とファイル名を変更。インラインアセンブラの入出力パラメタの使い方が正しくなかったので修正。

2020-06-29
受信バッファ・フル時の処理サイクル数の違いにより受信処理自体が誤動作する可能性があったためバッファ状態に関係なく同じサイクル数(厳密には1サイクル違う)で処理できるようデータ保存処理を改良してみた。

2020-06-28
1Mbpsの連続送信データでも安定して受信できるように改良。

2020-06-27
1Mbpsでの連続受信が微妙に間に合っていなかったので改良。割り込み処理からリターンすると間に合わなくなるのでリターンせずに割り込み処理を再実行するようにしてみたがもう少し調整必要な感じ。ちなみに、送信元の最大連続送信バイト数は、このライブラリの受信バッファサイズ(規定値は32バイト)に制限され送信と送信の間には受信処理が間に合うよう間隔をあける必要がある。
また、送信側が複数バイトを連続送信するとき、サンプルスケッチのようなプログラムは全二重通信となりライブラリの送信処理の割り込み禁止が受信処理を妨害してしまうため誤動作することにも注意。

【ライブラリ】

【参照ライブラリ】
AVR用高速GPIOライブラリ

“ソフトウェア処理の限界に挑む。AVR用高速シリアル・ライブラリを作ってみた。” への4件の返信

  1. ATmega328では未検証との事でしたが、ひとまずarduino unoで試させてもらいました。

    当初57600bpsや115200bpsで運用しようとしていました。
    その際に送信は想定通りうまく行き、受信は1バイト毎に余計なデータが挿入されたものが取得できました。
    おかしいと思いソースを解読しようと思ったのですが、一度1Mbpsでも試しておこうと設定したところ、送受信ともに成功することが分かりました。
    1Mbps以上ではいずれもうまく行くことから、何かしらの調整が必要なのか、はたまたATmega328P依存なのかといった所を少しアシストしていただけると嬉しいです。
    ちなみに半二重の環境です。
    maxuart.hはATmega328上での動作確認がなされていないとの事でしたが、Arduino UNOに適用してみました。
    115200bpsにおいて送信は問題なく、受信は1バイト毎に無用なデータが挿入されることを確認。
    115200bps以下でも同様のためソースを追ってみようと思ったのですが、ふと高速ではどうだろうと1Mbpsで試したところ送受信とも問題なく。1Mbps以上では問題ない事を確認。

    低速時における挙動について、何かヒントをいただければと。

    1. コメントありがとうございます。

      コメントがスパムと判断されていて気づくのに時間がかかってしまいました。また、正しい記事のほうにコメントとして記載させていただきました。

      挙動から推測すると受信後に無用な受信割込みが発生しているものと思われたのでコードを見直してみたら...あきらかに間違ってるところがありました。タイプ・ミスみたいですね...(-_-;)

      706行目
      誤 if (MAXUART_SHIFT_CYCLE >= 32)
      正 if (MAXUART_CLOCK_CYCLE >= 32)

      投稿内のコードは修正しておきました。

      これが原因だとは思いますが、こちらでは試験できないので再度試してみていただけないでしょうか?

  2. こちらこそレスいただいているのに気付かず放置してしまいました。

    早速57600bpsで試しましたところ、所望の挙動に至りました。
    全域においてなかなかヨサゲです。

    1. テスト&コメント頂きありがとうございます。
      感謝!<(_ _)>

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

CAPTCHA


このサイトはスパムを低減するために Akismet を使っています。コメントデータの処理方法の詳細はこちらをご覧ください