レベル変換 (3) UART その3 Arduino UNOから送信-2
第1回目では、4種類のマイコン・ボードのUART端子の初期電圧を調べました。
前回、5Vで動作しているArduino UNOの送信端子Tx(11番ピン)から「Spresense」と「ラズパイ」へデータを送る実験をしました。今回、「Trinket M0」、「ESP32(DevKitC)」をつないでレベル変換回路を試します。
●Trinket M0とESP32(DevKitC)の特徴
Trinket M0とESP32(DevKitC)は3.3Vで動作します。I/O端子もHIGHは3.3Vなので、Arduino UNOの送信する5Vが直接かからないようにしなければなりません。
UARTで使われる端子は、何も信号がない状態ではマーク=HIGHになると想定していました。第1回の実験では、Arduino UNOとラズパイは、送受信の端子はともにHIGHでした。しかし、Trinket M0とESP32(DevKitC)では、送信端子はHIGH、受信端子はLOWでした。
●Arduino UNO(5V)から送信してTrinket M0(3.3V)で受信する
ショットキー・ダイオードのSD103Aを使ってレベル変換します。受信端子のデフォルトがLOWなので、10kΩで3.3Vへプルアップします。Trinket M0のUARTは3番のRxと4番のTxがデフォルトで利用できます。開発言語はMicroPythonとArduino IDEが使えます。ここではデフォルトで使えるMicroPythonでプログラムを作りました。
接続です。Arduino UNOからLOWが送られると、ダイオードのカソードよりアノードが高い電圧になるので電流が流れ、Trinket M0のRx(3番)端子もLOWになります。Arduino UNOからHIGHが送られると、ダイオードのアノードよりカソードが高い電圧になるので電流は流れず、Trinket M0のRx(3番)端子は10kΩでプルアップしているのでHIGHになります。
送信側のArduino UNOのスケッチです。11番ピンから送信します。
#include <SoftwareSerial.h>
SoftwareSerial mySerial(10, 11); // RX, TX
void setup(){
mySerial.begin(9600);
}
void loop(){
mySerial.write("123");
}
受信側のTrinket M0のプログラムです。Rx端子で受信します。
Analog Discovery2を使って、これらの実験中の波形を示します。画面の上側ブロックがアナログ信号です。オレンジ色が送信波形で、青色がTrinket M0のRxピンの受信波形です。
下側のブロックはUARTのデータをデコードして表示する画面です。オシロスコープのチャネル1(オレンジ色)にDIO2ピンを、オシロスコープのチャネル2(青色)にDIO1ピンをつないでいます。送信している"123"を正しくデコードしています。HIGH/LOWは電源の50%付近を境に判断しているようなので、マイコンが正しくHIGH/LOWを判断できなくとも、デコードできる場合が多いです。
Trinket M0のRx端子は、LOWが0.5V、HIGHが3.6Vで、正常に受信できています。Trinket M0のマイコンATSAMD21のI/Oピンの上限電圧は、Vdd+0.3Vなので3.6Vです。
●Arduino UNO(5V)から送信してESP32(3.3V)で受信する
ショットキー・ダイオードのSD103Aを使ってレベル変換します。受信端子のデフォルトがLOWなので、10kΩで3.3Vへプルアップします。
(※)2018/10/5 初出の写真はUARTのデフォルトRXD0にマーキングしていました。ここはシリアルモニタと兼用なので使わず、UART2のIO16を利用したので修正しました。
Arduino UNOからLOWが送られると、ダイオードのカソードよりアノードが高い電圧になるので電流が流れ、ESP32のRx(16番)端子もLOWになります。Arduino UNOからHIGHが送られると、ダイオードのアノードよりカソードが高い電圧になるので電流は流れず、ESP32のRx(16番)端子は10kΩでプルアップしているのでHIGH(3.3V)になります。
送信側のArduino UNOのスケッチはTrinket M0の実験と同じです。11番ピンから送信します。
受信側のESP32のスケッチです。
HardwareSerial mySerial( 2 ); // UART2 16-Rx,17-Tx
void setup() {
Serial.begin(9600);
mySerial.begin(9600);
}
void loop() {
if ( mySerial.available() > 0 ) {
Serial.println( mySerial.read() );
}
}
実行結果です。
波形です。LOWが0.5V、HIGHが3.6Vです(青色)。正常に受信できています。I/Oピンの上限電圧はVdd+0.3Vなので3.6Vです。
今度は、MOSFET BSS138を使います。BSS138は、2回路、4回路入りレベル変換ボードによく使われています。
このときの波形です。LOWが0.3V、HIGHが3.6Vです(青色)。正常に受信できています。
MOSFETのゲートの接続を変えました。
このときの波形です。LOWが0.8V、HIGHが3.6Vです。正常に受信できています。
(※)送信と受信は約3mのツイスト・ペア線で接続しています。それが原因かどうかははっきりしませんが、受信側の波形に大きなノイズが観測されています。許容される電圧をプラスもマイナス側も超えています。マイコンのI/Oには何らかの保護回路が入り、多くは、Vdd+0.3Vを超えた電圧、-0.3Vを下回る電圧はダイオードによる保護回路が働き、マイコン内部には高い電圧は入らないはずですが、期待して回路を利用するのはよくないです。
レベル変換回路に使われるデバイスにバス・トランシーバがあります。入出力に独立した電源をつなぎます。また、最近のアイソレータは同じ用途に使えます。これらを使うほうが、駆動能力やノイズ・マージンを高くとれるので、安全な通信ができます。
UARTのデータはHIGH/LOWのパルス幅のほぼ中央でサンプリングされるので、立ち上がりと立ち下がりのノイズでデータが化けるということはあまり考えられません。しかし、外来ノイズは、ツイスト・ペア線を使っても差動回路ではないので飛びこんでデータが化けることがあります。データ形式を、パリティありにしていてもエラーが起こってもだれもリトライをしてくれません。したがって、スケッチでたとえばCRC-8のデータ・チェックを追加し、チェックした結果が異なれば再送を要求するプログラムにしないと、確実なデータのやり取りはかないません。
TTLレベルのUARTは数十cm以内の近距離を前提として使うのが安全です。数mの距離を延ばすときは、RS-232Cの物理層に変換すれば、最大10mは規格内です。それでも、ノイズによるデータ化けは必ず発生します。時間の余裕があれば、複数回同じデータをやり取りをして、内容が同じであれば正しいというようなスケッチを書くと対応できることもあります。
したがって、本文の事例は、
- 伝送路のノイズ対策はされていないのでデータが化ける可能性、外部からのノイズを受けることへの対策がない
- データが化けたとき、データの復活やリトライをまったく考慮していない
というとても簡易的なレベル変換の事例です。伝送距離が数cm程度であれば、実験や動作確認には便利に使えます。筆者は5mほどケーブルを伸ばし、室外にGPSモジュールを設置してプログラムの開発時に利用しています。
伝送距離を伸ばし、ノイズに強く、データが不完全なときにリトライもしてくれる環境にRS-485を使ったModbusプロトコルがあります。しかし、いま、入手できるライブラリでは、Arduino用のマスタ用は使えるレベルですが、スレーブ側がうまく使えません。2018年7月に純正のRS-485ボードが出荷され始めたので、将来、純正のライブラリが用意されるかもしれません。