UARTと言う通信方式は、非常にシンプルで便利な方式です。ただ、予め互いに通信速度を擦り合わせておかないと通信が成立しないことが難点でした。
色々なセンサモジュールなどとの通信に使っているSPIやI2Cなどと言った通信仕様には、データ信号線の他に同期信号線、所謂クロック信号があります。
これらの通信仕様ではクロックに同期して通信が実行されるため、クロックを出力するマスタ側の都合で通信速度を決定することが出来ます。このおかげで、予め通信速度を擦り合わせておかなくても通信が成立するのです。
UARTの場合、クロックの存在しない非同期通信なので、受信データRXD信号に関して言うと、アイドル状態のRXD=1からスタートビットのRXD=0に変化するタイミングを使って、送信側と受信側のタイミングを同期化します。同期化できるのはこのタイミング一点なので、その後は予め擦り合わせた通信速度に従ってデータを送受信します。
予め擦り合わせた通信速度で作り付けなので、一旦システムが確定してしまうと、中々通信速度を変更することが出来ません。まして、動的に変更するのは至難の業となります。一般的には、新しい通信速度を擦り合わせるためにUART以外の通信手段が必要になります(1^^;)。これでは本末転倒ですよね。
UARTの通信信号線(TXD,RXD)だけを使って、何とか、相手の通信速度を検出することが出来ないでしょうか?
UART通信フォーマットの中で、8N1(8ビットデータ、LSBファースト、パリティなし、1ストップビット)と言う方式の通信では、以下図の様な通信波形が発生する場合があります。
0x55(大文字U)以外のデータを通信する場合には、0のビットや1のビットが連続して、上図の様なビット毎にトグルする波形とはなりません。
0の幅時間T0と1の幅時間T1を計測しながら、常にT0≒T1であることを確認していれば、0x55以外の通信データを排除して、通信速度の検出対象から除外することが出来ます。
また、一旦0x55ではないと判定した場合、連続して0x55を待ち続けるのではなく、以下のルールを追加します。
これによって、連続する通信波形の中で、たまたま0x55だと誤検出してしまう可能性を大きく減少させることが出来ます。本ページでは仕様として、「600baud以上の通信速度を検出可能」とします。600baudで1フレーム(10ビット)分を送信する時間約16.7msにマージンを持たせて、「無通信時間」を25ms以上とします。
どのようなデータを通信したとしても、1フレームの中でスタートビットは必ずRXD=0、ストップビットは必ずRXD=1となるので、1フレーム分以上(≧25ms)の無通信時間(RXD=1)が続く以上、他の通信と隔絶して、単体の「大文字U」を通信した時だけにフォーカスすることが出来ます。
※と言っても、25msと言う時間はそんなに長い時間ではないので、連続する通信の中で「たまたま大文字Uの前後に25ms以上の時間が空いてしまう」ケースなども想定してソフトウェアを組む必要があります。→誤検出した場合に不良動作とならないような対処が必要。
データの送信側は、自分の通信速度で最初に「大文字U」を送信することで、相手(受信)側にこちらの通信速度を検出させ、通信を確立することが可能となります。
前節の検討を踏まえて、未知の通信速度で「大文字U」が送信されてきた時、相手の通信速度を検出可能なハードウェア(回路)を設計して行きます。以下に検出アルゴリズムをまとめてみます。
上のアルゴリズム1)〜5)に従って動作するステートマシンを作ります。0)の所に記述した回路は、ステートマシンの周辺で検出をサポートする回路です。
ステート1)に於いて、RXD=1の状態で19ビットのカウンタBRCNTがオーバフロウFOVFすると、漸くスタートビットを検出するステート2)へ遷移するので、少なくとも21.8ms以上の無通信時間後に「大文字U」を受信した時が、相手の通信速度を自動検出できるタイミングとなります。ステート5)も同様です。
改めて、通信速度の検出条件を以下にまとめておきます。
通信速度の検出条件:
上の条件を満たす通信について色々と頭の中でシミュレーションしてみましたが、UARTの8N1形式通信に於いては、ほぼ「大文字U」を受信した時だけでした。
「大文字Uを受信した時だけ」と、言い切れないのは、いくつかそれ以外の通信で条件を満たすケースが見つかっているから、です。例えば下図をご覧ください。
図中コメントの通りです。
上図の例以外にも、検出条件を満たす可能性のある通信として、データ0x00の通信を指摘することが出来ます。他にもあるかもしれません。つまり、検出値は必ずしも正しくないことに留意してください。
例えば、上図の波形が115,200baudで送信された場合、今回の自動検出回路は23,040baud(=115,200/5)だと誤認識するでしょう。要は、そんな中途半端な通信速度、使いますか?ということです。良く使用される以下の様な通信速度に限定しておいて、「それに近い値が検出された時だけ、その通信速度に合わせる」等と言うルールにしておくだけで、ずいぶんと誤検出による不具合を減らすことが可能です。
良く使われる通信速度:
2,400baud/4,800baud/9,600baud/19,200baud/
38,400baud/57,600baud/115,200baud/等
この辺りはハードウェアよりソフトウェアの方が柔軟に対処可能なので、ソフトウェア側の処理に押し出します。
前回更新した新しいUART8N1ユニットでは、前の節で設計した通信速度の検出回路が搭載されているので、「大文字U」のデータを受信するとuartctlレジスタのBRDFフラグが立って、通信速度を検出したことをユーザに知らせます。
BRDFフラグが立つと同時に、1ビット分の平均時間をuartbresレジスタにセットしてあるので、ユーザはuartbresレジスタ値を確認した上で、必要に応じてその値をuartbaudレジスタに書き込むと、新しい通信速度に追従することが可能となります。
以下に通信速度自動検出のサンプルコードを示し、その下に解説を加えます。
// baud rate detection
ldwi r7,uartctl
ldw r0,[r7]
stw [r7],r0 // clear brdf flag
andi r0,uart_brdf
beq dbr_not_brdf
uartctlレジスタのBRDFフラグを確認して、BRDF=1(自動検出された)なら先に進みます。
// detected
ldwi r7,uartbres
ldw r0,[r7]
ldwi r7,uartbaud
ldw r3,[r7]
// margin
mov r2,r3
lsfti r2,-6
// r3=abs (r3 - r0)
sub r3,r0
bpl pcnt+2
neg r3
cmp r3,r2
blo dbr_not_brdf
// update uartbaud
stw [r7],r0
:
: メッセージ送信等の処理
:
dbr_not_brdf:
検出した新しい通信速度をuartbresレジスタから読み出して、現在のuartbaudレジスタ値と比較、ほぼ同じ場合は、検出した通信速度を無視します。uartbres≠uartbaudだった場合は、検出した新しい通信速度設定値をuartbaudレジスタに書き込みます。
今回のプログラムでは、通信速度の変化を検知した時にメッセージを送信するようになっているため、このような判定が入っています。
尚、uartbaudレジスタを変更すると、ただちにその値が有効となります。このため、uartbaudレジスタ値を変更した時に受信中だったデータは、正しくない場合があります。
仕様では、「大文字Uの受信前後に25ms以上の無通信時間があること」としてあるので、大文字U受信後の無通信時間内にuartbaudレジスタ値を変更できた場合には、問題ありません。
※データを1バイトも取りこぼせない様な状態では、uartbaudレジスタ値を書き換えることは避けてください(ソフトウェア側の責任範囲)。
同様に、データ送信中にuartbaudレジスタを変更してしまうと、受信側が誤ったデータを受信してしまうので、送信が行われていないことを確認した上で、uartbaudレジスタを変更しなければなりません。
ここまで検討してきた回路が、前回のデザインMCOC115EX4808に搭載した新しいUART8N1ユニットに既に搭載されています。うまく通信速度の検出が実行できるか、実機(FPGAボード)で確認して行きましょう。
以下リンクより、MoscoviumマイコンMCOC115EX4808のデザインをダウンロードできます。
FPGAボード(Sipeed Lichee Tang Primer)へ書き込む時に、以下のプログラムをアセンブルしたMIFファイルを使ってください。TD IDEのプロジェクトはMCOC_EX.alです。FPGAボードへの書き込み手順はこちらを参照してください。
実験用プログラム:det_baud.asm
実行すると、シリアルモニタから以下のコマンドを受け付けるようになっています。
シリアルモニタコマンド仕様:
では実験です。FPGAボードと、FTDI等のUSB⇔UART変換モジュールをこちらの様に接続します(下図参考)。
PC側の通信ソフトウェアはTera Termを使用します。
Tera Termでウインドウに文字を打ち込むと、打ち込んだ端から1文字毎にデータを送信します。人間がキーボードをたたくスピードには限界があるので、例えば「LUT」と言う文字列を打ち込んだ場合でも、「L」と「U」、「U」と「T」の間にそれぞれ25ms以上の時間が経過すると、通信速度検出の対象となります。
※意図しないタイミングで通信速度を変更してしまうと、変更した瞬間に受信中だったデータを取りこぼしてしまう恐れがあるため、プログラム(det_baud.asm)では、現在のuartbaud値と、検出したuartbres値がほぼ同じ値だった場合には、検出を無視してそのまま続行するようになっています。
一方、Arduino IDEシリアルモニタの場合は、文字列を打ち込んでリターンキーを押した時に全データを一気に送信するタイプの通信ソフトウェアなので、「LUT」の中に含まれる「U」に反応することはありません。……恐らく「L」と「U」、「U」と「T」の間に25msも間隔が空かないためです。
Arduino IDEのシリアルモニタを使う場合、「改行なし」に設定して「U」1文字だけを送信すると、通信速度の検出が発動します。
ともあれ、本ページではTera Termを使います。プログラム(det_baud.asm)のデフォルトは9,600baudになっていますので、まずは、Tera Term側の通信速度を9,600baudに合わせておいてください。
実験サンプル:【 】内はTera Termからの送信データ
【who】
OK+ uartbaud=0x09c3
OK: det_baud ver.1.00
まずは、通信速度を合わせてあるので、普通に【who】に反応して、メッセージが返ってきました。表示されたuartbaudレジスタ値0x09c3は、9,600baudで通信する場合の設定値です(↓)。
equ baud,9600 // baud rate
equ uart_baud,fcpu/baud-1 // uartbaud setting
次に、Tera Termの通信速度設定ウインドウ「Setupメニュー ⇒ Serial port」で、一方的に通信速度を115,200baudに変更してみます(下図)。
実験を続けましょう。
【who】
????
…… 【who】を打っても反応なし、または、ごみが返ってくる。
まだMoscovium側は9,600baudだと思っているので、通信が成立していません。まあ、普通はこうなります。
【U】
OK: new baud rate detected: uartbaud=0x00ce
Moscovium側に通信速度を検出させるため、「大文字U」を送信しました。「U」の前後に25ms以上の無通信時間が必要なので、1文字だけ「U」を入力してください。すると、通信速度を検出した旨のメッセージが返って来ました。表示されたuartbaudレジスタ値(0x00ce)から通信速度を計算してみると、
通信速度=24MHz/(uartbaud+1)≒115,942baud
と、115,200baudの通信速度に追従している様です。
実際の通信速度115,200baudと比べて、多少の誤差がありますが、以下の要因によるものと推察できます。
誤差は微小であり、UARTとして通信が破綻することは無く、期待通りに動作すると結論できます。
【who】
OK+ uartbaud=0x00ce
OK: det_baud ver.1.00
Moscovium側も115,200baudに合ったので、この先は普通に通信できます。Tera Term側を他の通信速度にも変更して、同じ手順を繰り返してみてください。一方的に通信速度を変更しても、「大文字U」を送信すると新しい通信速度に追従するはずです。
折角作り込んだ通信速度の自動検出回路なので、ブートローダプログラムにも、この機構を取り込んでおきました。
プログラムをダウンロードする時に自由に通信速度を選べるようになって便利なのですが、ブートローダ記憶用の組み合わせ論理ROMが大きくなるのが難点です。従って、ブートプログラム(mcoc_boot.asm)に以下のアセンブルオプションを仕込んでおきました。
ブートプログラムのアセンブルオプション:
現在、デザインデータページからダウンロード可能なブートローダROM(rom_boot_mat.v)は、パイロットLEDと、通信速度自動検出コードが有効になっていて、232バイトです。アドレスバスのビット幅を8ビットに指定してある(「romsiz 256」)ため256バイトが天井であり、もうギリギリです。
ブートローダがこれ以上大きくなるようなら、普通にROMマクロを作ってしまった方がお得だと思います(1^^;)/。
ブートモードで起動後、手動で「大文字U」を送り、オンボードLEDが赤色に点灯することを確認してください(ブートプログラムをパイロットLED有効でアセンブルしておかないと確認できません)。「U」送信の後で少し時間を空けてリターンキーを送信してから、プログラムデータを送信してください。
通信速度自動検出するまでもなく、通信速度が合っている場合(9,600baud同士など)は、「U」の送信は必要なく、今まで通りそのままプログラムデータを送信可能です。
テキスト通信だと、普通に「大文字U」を含んだデータが行き交います。通常のテキストデータ中に含まれる「U」を検出してしまう恐れや、何らかの特殊なデータ通信によって誤検出する危険はありますが、動的に通信速度を変更できるのは、とても使い勝手が良いですよ。