はじめに
現在、組み込み系のプロジェクトに携わっており、アプリケーション側の開発を担当しています。
これまでにもUARTという言葉は何度も聞いたことがあり、
- シリアル通信をするもの
- ボーレートを設定する
- TX/RXを使って通信する
- 受信バッファがある
くらいの概要は理解しているつもりでした。
しかし、実際に開発をしていると、アプリケーション側でも「ドライバ側のシリアル通信の都合」を考慮しなければならない場面が意外とあります。
今回は、「UARTってそもそもどうやって通信しているの?」というところから、エラーやバッファ、I2C・SPIとの違いまでを勉強したので、自分自身の理解を整理する意味も込めてまとめてみます。
UARTとは?
UARTは、Universal Asynchronous Receiver/Transmitterの略です。
UART通信では、基本的に以下のように、送信線(TX)と受信線(RX)を使って通信します。
UARTの大きな特徴は、クロック信号を別途送らないことです。
SPIなどではクロック線を使って、
「このクロックに合わせてデータを読んでね」
と同期させます。
一方UARTでは、
「この速度でデータを送るから、そっちも同じ速度で読んでね」
という方式になります。
そのため、送信側と受信側であらかじめ通信速度(ボーレート)などの設定を合わせておく必要があります。(後述)
どんな場面でUARTを使うの?
UARTは非常にシンプルな通信方式なので、組み込みの世界ではかなり頻繁に登場します。
例えば、
- マイコンと外部モジュールの通信
- Wi-Fiモジュールとの通信
- USB-UART変換器を利用したPCとの通信
- マイコンのデバッグログ出力
- ブートローダーとの通信
- ファームウェア書き込み
などがあります。
特に組み込み開発では、
PC
↓
USB-UART変換器
↓
UART
↓
マイコン
という構成にして、PCからマイコンにコマンドを送ったり、マイコンからログを出したりすることがあります。
ArduinoなどでもUARTを利用する機会がありますね。
UARTのフレームについて
UARTでは、1回の通信単位をフレームとして考えることができます。
一般的には、以下のような構成です。
ただし、すべてが必ず存在するわけではありません。
例えば、
8N1
というUART設定を見かけることがあります。
これは、
8 : データビット8bit
N : パリティなし(None)
1 : ストップビット1bit
という意味になります。
UARTはどうやってデータを送っているの?
例えば、文字「A」を送ることを考えてみます。
ASCIIコードでは、
A = 0x41
です。
2進数にすると、
01000001
となります。
UARTでは、このデータをそのまま「01000001」と送るわけではありません。
データの前後に、スタートビットやストップビットなどを付けて送信します。
UARTでは一般的にデータビットのLSB(最下位ビット)から送信します。
そのため、0x41 = 01000001の場合、実際には
1 → 0 → 0 → 0 → 0 → 0 → 1 → 0
の順番で送信されます。
なぜスタートビットが必要なの?
UARTにはクロック線がありません。
そのため受信側は、
「いつからデータが始まったの?」
ということを自分で判断する必要があります。
そこで、通信していない状態では通常、信号線をHIGHにしておきます。
アイドル状態
HIGH ───────────────────────
そして通信を開始するときに、
HIGH
↓
LOW
に変化させます。
これがスタートビットです。
受信側は、
「お、HIGHだった線がLOWになったぞ。UARTの通信が始まったな」
と判断し、そこから一定の時間間隔でデータを読み取っていきます。
ボーレートとは?
ここで重要になるのがボーレートです。
例えば、
9600 baud
という設定を見たことがあると思います。
UARTでは、一般的なNRZ方式の場合、1ボーあたり1ビットを送るため、実質的に
9600 bit/s
程度の速度になります。
つまり、
1秒間にどれくらいの速度でシンボルを送るか
を表しているのがボーレートです。
UARTでは一般的に、
9600
19200
38400
57600
115200
などが使われます。
ボーレートが違うと何が起こる?
例えば送信側が、
115200 baud
なのに、受信側が、
9600 baud
だったらどうなるでしょうか。
当然、受信側は正しくデータを読むことができません。
送信側は高速にビットを送っているのに、受信側は「9600 baudの速度で来る」と思っているからです。
ボーレートの不一致が発生した結果、
- データビットを正しく読めない
- パリティエラー
- フレーミングエラー
- 受信データそのものが壊れる
などにつながる可能性があります。
UARTで発生する代表的なエラー
UART通信では、いくつか代表的なエラーがあります。
今回は、
- パリティエラー
- フレーミングエラー
- オーバーランエラー
について見ていきます。
パリティエラー
パリティビットを使用している場合、受信側はデータを受け取った後にパリティをチェックします。
このとき、
「送信されてきたデータとパリティビットの組み合わせがおかしい」
となると、パリティエラーになります。
例えば、
送信時
DATA PARITY
10110010 0
だったのに、途中でビットが変化して、
受信時
DATA PARITY
10100010 0
のようになった場合です。
パリティエラーが起きる原因としては、
- 電気的なノイズ
- 信号品質の問題
- ボーレートの不一致
- クロック誤差などによるサンプリング位置のずれ
などが考えられます。
フレーミングエラー
UARTでは、
START
DATA
STOP
というフレーム構造になっています。
例えばストップビットが1の場合、
START DATA STOP
0 [ 8 data bits ] 1
となります。
受信側が「ここがストップビットだ」と判断したタイミングで、
0
が来てしまった場合、
「あれ?ストップビットのはずなのにLOWになっている」
となります。
これがフレーミングエラーです。
フレーミングエラーが起きる原因としては、
- 通信線の異常
- 電気的なノイズ
- 信号品質の問題
- ボーレートの不一致
などが考えられます。
オーバーランエラー
そして、今回個人的に一番気になっていたのがオーバーランエラーです。
これは、アプリケーションやドライバを書く側にとって非常に重要です。
簡単に言うと、
受信したデータを処理する前に、次のデータが到着してしまった
ことで発生するエラーです。
例えばUARTの受信側に、
受信レジスタ
があるとします。
そこに、
A
が到着しました。
しかしCPUがまだ、
Aを読み出していない
状態だったとします。
そこへ次の、
B
が到着すると、
CPU
│
│ まだAを処理していない
↓
UART受信レジスタ
┌─────┐
│ A │
└─────┘
↓ Bが到着
┌─────┐
│ A ? │ ← まだ残っている
└─────┘
となります。
UARTハードウェアの構成によって詳細は異なりますが、受信データを格納できる場所がいっぱいになった状態でさらにデータが到着すると、オーバーランエラーが発生します。
オーバーランエラーが起きる原因としては、
- CPUが受信データを読むのが遅い
- 割り込み処理が遅い
- UART受信割り込みの優先度が低い
- 他の割り込み処理にCPUを取られている
- アプリケーション側の処理が重い
- UARTの通信速度が速すぎる
- 受信FIFOやソフトウェアバッファが小さい
などがあります。
つまり、
UART
↓
受信データ
↓
ドライバ
↓
バッファ
↓
アプリケーション
という流れのどこかで処理が追いつかなくなると、問題につながります。
大体ソフトウェア側の受信処理が遅れることで発生するエラーとされており
受信処理のタイミングや割り込みの優先順位が低いと発生することが多いようです。
UARTとI2CとSPIの違い
組み込み開発ではUARTだけでなく、
- UART
- I2C
- SPI
も頻繁に登場します。
ざっくり比較すると、こんな感じです。
| UART | I2C | SPI | |
|---|---|---|---|
| 通信方式 | 非同期 | 同期 | 同期 |
| クロック | なし | あり | あり |
| 主な信号線 | TX/RX | SDA/SCL | SCLK/MOSI/MISO/CS |
| 通信形態 | 1対1が基本 | 複数デバイス | 複数デバイス |
| 構成 | シンプル | アドレス指定 | CSでデバイス選択 |
| 速度 | 比較的低速 | UARTより高速なことが多い | 高速 |
| 配線 | 少ない | 少ない | UARTより多い |
| 用途 | モジュール・デバッグ等 | センサー・IC | メモリ・高速デバイス等 |
UARTの強いところ
UARTの魅力は、とにかくシンプルなことです。
配線が少ない
基本的には、
TX
RX
GND
程度で通信できます。
クロック線が必要ありません。
実装が比較的簡単
UARTは非常に古くから使われている通信方式なので、多くのマイコンやSoCにUARTのハードウェアが搭載されています。
OSやドライバからも扱いやすく、
/dev/ttyXXX
のようなデバイスとして見える環境もあります。
デバッグに便利
組み込み開発ではUARTがデバッグ用途に非常に重宝します。
例えば、
[INFO] System Start
[INFO] Sensor initialized
[INFO] Temperature = 25.3
[ERROR] Communication failed
のようなログをUARTから出力して、PCのターミナルで確認することができます。
モジュールとの接続に便利
GPS、Bluetooth、Wi-FiなどのモジュールではUARTインターフェースが用意されていることがあります。
その場合、
マイコン
│
UART
│
↓
モジュール
というシンプルな構成で通信できます。
UARTの弱いところ
一方で、UARTにも弱点があります。
通信速度が比較的遅い
例えば115200 baudの場合、1秒間に115200ビット程度です。
さらにUARTでは、
Start bit
Data bits
Parity
Stop bit
などのオーバーヘッドがあります。
例えば8N1の場合、
1 Start
8 Data
1 Stop
なので、1byteのデータを送るために10bit必要です。
したがって、
115200 bit/s ÷ 10 bit
= 11520 byte/s
程度が理論上の最大値になります。
つまり、
115200 baudだから、1秒間に115200 byte送れる
というわけではありません。
通信設定を合わせる必要がある
UARTでは、
ボーレート
データビット
パリティ
ストップビット
などの設定を送受信側で合わせる必要があります。
例えば、
送信側:115200 8N1
受信側:9600 8N1
では正常な通信ができません。
データの信頼性を自分で考える必要がある
UARTそのものには、
再送
ACK
パケット番号
データ長
CRC
などの仕組みがありません。
必要であれば、UARTの上にプロトコルを設計します。
例えば、
┌──────┬──────┬──────┬─────────┬───────┐
│ STX │ LEN │ CMD │ DATA │ CRC │
└──────┴──────┴──────┴─────────┴───────┘
のような独自フォーマットを作ることがあります。
受信処理が遅いとデータを失う可能性がある
今回、アプリケーション側の立場から一番意識したいのがこれです。
UARTでは、
送信速度
↓
UART受信
↓
ドライバ
↓
バッファ
↓
アプリケーション
という複数の処理段階があります。
そのため、
送信速度 > 受信処理速度
になると、バッファが徐々に埋まっていきます。
最終的にはオーバーランやバッファオーバーフローなどによって、データを失う可能性があります。
おわりに
今回、UARTについて改めて調べてみて、個人的には、
「UART = シリアル通信するためのもの」
という理解から、
「UARTはビットを送受信する仕組み。その上にドライバやバッファ、さらに上位プロトコルが存在する」
というところまで理解できたのが大きかったです。
特にアプリケーション側から見ると、
アプリケーション
↓
ドライバ
↓
UART
↓
通信線
↓
UART
↓
ドライバ
↓
アプリケーション
という全体像を意識することが重要だと感じました。
「コマンドを送ったのにレスポンスが途中までしか来ない」
「たまに通信に失敗する」
「大量のデータを送るとおかしくなる」
といった問題が起きたときも、
- ボーレートは合っているか
- フレーム設定は合っているか
- パリティエラーは出ていないか
- フレーミングエラーは出ていないか
- UARTのFIFOは溢れていないか
- ドライバの受信バッファは十分か
- アプリケーションの受信処理が遅くないか
- 上位プロトコルでメッセージ境界を正しく扱えているか
というように、原因を段階的に切り分けられるようになります。
組み込み開発でUARTを使っている方や、私と同じように「概要は知っているけど、ちゃんと理解できているか怪しい」という方の参考になれば幸いです。



