UART、好きですよね?
こんにちは。Yuseiと申します。普段はダラダラ好きなコードを書いて遊んだり、雑に高校生をやったり、ロボットを作ったりしています。
今回は私自身ロボットでよく使っている、シリアル通信の方式 "UART" で少し遊んでみました。
なお、タイトル通りのことの結果だけ知りたい人は、TXを実装するまで飛んでください。
#UARTについて
そもそも論 -- UARTとは
UART (Universal Asynchronous Receiver/Transmitter, ユーアート) は、調歩同期方式によるシリアル信号をパラレル信号に変換したり、その逆方向の変換を行うための集積回路である。
https://ja.wikipedia.org/wiki/UART
ということなんだそうです。 先ほど私はUARTを「シリアル通信の方式」と書きましたが、これは正しくもあり(そういうコンテクストで使われるので)、間違いでもある(元はTransmitterやReceiverのハードウェアの名前だから)ということになります。
「通信ペリフェラルの一種」という言い方をすれば無難でしょうか.
調歩同期方式(=非同期通信)です。それゆえに、以下の特徴があります。
- 受信側と送信側の両方がクロックを持ち、同じクロックで動作することで、通信を実現しています。
- 信号線一本(&GND)で片道の通信ができます。もう一本つけると往復で通信できます。
- マスター、スレーブの区別がなく、対等に通信します。
以下に、接続の概略を示します。 TX(Transmit)とRX(Receive)の配線がクロスするのが特徴です。
これは、TX、RXそれぞれのピン名がそれぞれのマイコン自身から見た名前であり、RX受信するには相手のTXから出ている信号が欲しい、TX送信するにはRXに流し込みたいという風になるからです。
それでは、この中の信号はどうなっているのでしょうか。
UARTの信号とは
このセクションに関しては、
を合わせて読んでいただければ完全理解できるはずですが、一応ここにも書いておきます。
以下が、基本的なUART通信の信号です。 デジタル信号ですので、TTLレベル/CMOSレベルのいずれかに揃えて,HIGH/LOWの2値でやり取りします。
- 通信はまず、信号ラインがHIGHの状態からLOWになるところ(立ち下がり)で始まります
- そこから1bit分の時間はLowに保ちます。この
0
になるビットがスタートビットです - ここからデータの送信を始めます。送信の単位は1バイトです。1バイトに含まれる8つの各ビットの0/1 に合わせて、、ビッグエンディアン(=1の位から上位の位への順)で、電圧をLOW/HIGHしていきます。各ビットのHIGH/LOWを保つ時間の長さは1bit分の時間です。
- (パリティビットを使うときは、DB7がパリティになります。)
- 最後に1bit分の時間かそれ以上長くHIGHに保ちます。これがストップビットです。
1bit分の時間
先に述べたように、UARTは非同期通信方式です。
そのため、それぞれが同じ周期のクロックを持って通信を行い、それぞれが1bit分の時間を計ります。
その通信全体で共通してもつクロックの周期を、baudrate(ぼーれーと)と呼びます。
baudrateが速い方がより高速で通信できます。baudrateは、そのまま通信の速度です。単位はbpsで、一般に9600
,115200
あたりがよく用いられるようです。
このbaudrateから、簡単に1bit分の時間が計算できます。
というのも、bpsは bit per secondです。
つまり1秒あたり何ビット送るかということになります。
これをくるっとひっくり返して、1ビットあたりに何秒かけるかを求めれば良いわけですから、bpsの逆数をとれば良いのです。
(1bit分の時間)= 1 / (baudrate) です。
時間の誤差について
上で求めた時間ですが、実はある程度の誤差を許容します。
言い換えれば、規定の時間よりも1bit分の時間が多少長かったり,短かったりしても、普通に通信ができます。これは、10bit送り切って1bit分以上ののずれが出ない程度まで許容されるということになります。
ソフトウェアシリアル
説明が長くなりました。
今回やってみた「遊び」は、UARTを、普通のマイコンのGPIOでやってみるというやつです。
普通、マイコンからUARTを用いる際には、専用のペリフェラルチップがSoCに含まれていて、それに接続された特定のピンを用いて行います。
そのため、利用できるUARTペリフェラルの数はある程度限られてきます。
しかし、前のセクションまででご紹介したように、実はUARTの信号はかなり単純です。
そこで、UARTの信号を、ソフトウェアからGPIOのHIGH/LOWに変化させることで生成し、ソフトウェア的にUARTを実装しようということを試みました。
ちなみに、Arduinoには、同様のことを試みたSoftwareSerial
というライブラリがあります。今回は私のUARTの勉強を兼ねていたのでそちらのコードは参照していないのですが、Arduino界隈ではかなり頻繁に使われるライブラリです。
なお、今回やってみるマイコンとして選んだのは STmicro Instruments社のSTM32F303k8t6で、mbedフレームワークで動かしました。
TXを実装する
とりあえず1byteポーリングで, 'A'
を送信するコードを下記に示します。
#include <mbed.h>
#define UART_DELAY 104 // 1/9600
Serial pc(USBTX,USBRX);
DigitalOut RX(D4);
DigitalOut TX(D5);
int main() {
TX=1;
pc.baud(230400);
pc.printf("Boot");
while(1) {
TX=0;//START
wait_us(UART_DELAY);
TX=1;//DB0
wait_us(UART_DELAY);
TX=0;//DB1
wait_us(UART_DELAY);
TX=0;//DB2
wait_us(UART_DELAY);
TX=0;//DB3
wait_us(UART_DELAY);
TX=0;//DB4
wait_us(UART_DELAY);
TX=0;//DB5
wait_us(UART_DELAY);
TX=1;//DB6
wait_us(UART_DELAY);
TX=0;//DB7
wait_us(UART_DELAY);
TX=1;//Stop
wait(1);
}
}
これだけです。 これだけで、'A'
を送信することができるはずです。
確認には、FTDI社のFT232RLを用いたUSB-シリアル変換アダプタを用いました。
以下のように接続してください。
STM32 | FTDI |
---|---|
D4 | TX |
D5 | RX |
GND | GND |
なお、このマイコンは3.3vで動作しているので、そのようにアダプタを設定してさい。
このプログラムでの送信は、9600 8-N-1 (9600bps、8bit幅,パリティなし、ストップビット1)で設定しています。
ハマりポイント: 時間について
このプログラムでは、1bit分の時間を 定義値 UART_DELAY
で定義しています。単位はμsです。この値は、本来 1/baudrate (s)になるはずで、baudrateが9600bpsのときは1/9600≒104μs となるはずなのですが、私が確認したところではこれではうまく通信できませんでした。
代わりに、91<UART_DELAY<102の範囲で設定することで、動作が確認できました。
時間が短くなる理由
ある程度マイコンを触った方ならお分かりかもしれませんが、GPIOの設定には時間がかかります。
実際に、以下のコードで(正確ではありませんが)簡単にそれにかかる時間を測定してみました。
#include <mbed.h>
#define UART_DELAY 97//101Max,92 Min(104 in theory)
Serial pc(USBTX,USBRX);
DigitalOut RX(D4);
DigitalOut TX(D5);
Timer t;
int main() {
// put your setup code here, to run once:
TX=1;
pc.baud(230400);
pc.printf("Boot");
while(1) {
TX=1;//START
t.reset();
t.start();
TX=0;//START
t.stop();
pc.printf("Time:%d useconds.\n",t.read_us());
wait_ms(300);
}
}
シリアルモニターには以下のように出てきました。
Time:8 useconds.
Time:7 useconds.
Time:7 useconds.
Time:8 useconds.
Time:7 useconds.
Time:8 useconds.
:
:
これから、ピンの変化にはおよそ7,5μ秒かかっていることがわかります。
これを用いると、先ほどの変化の理由が見えてきます。
つまり、元の104μ秒-7.5μ秒=96.5μ秒となり、UART_DELAY
の変域の中心である96.5μ秒にぴったり一致します。
以上より、実際にこのmbedソフトウェアシリアルを用いる際には、7.5μs(実際には8μ秒くらいがいいと思います)ずらして扱うのが良いでしょう。あるいは、ピンを変化させてから,Ticker
を用いるのもアリかもしれませんね。
私はTXは一旦これで実験レベルには満足したので、また気が向いたらリファクタリングをしてクラスに整理して、putc
printf
やRxIrq
を実装しようと思います。
おそらく来週中には終わると思いますので、続報をお楽しみにしていただければ幸いです。そのうちGPLv3あたりのライセンスでライブラリとして公開すると思います。
To Be continued ... RX編に続く!
FIFOバッファ作るの、楽しいね。