Arduino APIと比較しながらArm純正RTOS、Mbed OS6の入出力API群を練習してます。前回はシリアル通信といいつつ、デバッグ用のUSB経由ホスト行きのコマゴマしたところを確認。今回は実際にシリアル通信してみたいと思います。ありがちな「ループバック試験」だけれども。自分で自分と通信してみるもの。
※「モダンOSのお砂場」投稿順Indexはこちら
NUCLEO-F401REのUART端子
前回、Arduino UNOのD0/D1端子にはシリアルポート(UART)のRX/TXが接続されているものの、回路的にはUNO R3とR4で異なることを確認しました。おさらいすると以下のようです。
-
- UNO R3、D0/D1にUSB経由でホストと通信するためのUARTの信号がそのまま出ている。よって、ここに変なものを接続するとヤバイよ。
- UNO R4、D0/D1にはUSB用とは別なUARTが接続されている。Serial1というオブジェクト経由で操作可能。
さて、NUCLEOではどうよ、ということで確認すると以下のようです。
-
- D0/D1ピンソケットにはデフォルト設定ではUART信号は接続されていない。D0/D1に出るべきUART信号はUSBインタフェースマイコン(ST-LINK)と「のみ」接続されている
Arduino UNO R3の路線を踏襲しつつも、電気的にヤバイことにならないように配慮されておりますな。なお、デフォルト設定ではと書きましたが、設定は「ハンダブリッジ」です。半田を盛り直せばD0/D1にUART信号(USBに行くやつ)を出力することも可能です。でもメンドくなる、多分やらない。
そこでUSBと接続されているUARTとは別の、プログラムで勝手に使えるUARTが「デフォルト設定」でどこかに出て無いの?というとありました。UART4というペリフェラルの端子がArduino UNO互換ピンソケットに接続されてます。以下のとおり。
-
- A0、TX
- A1、RX
これを使えば、UART通信(自分で自分で通信するループバック試験だけれども)の実験をできるというもの。
Mbed OS6のBufferedSerial API
Mbed OS6上でSerial(UART)通信を行う場合、
-
- BufferedSerial
- UnbufferedSerial
の2種類のAPIが使えます。まあ、APIの中でバッファリングしてくれる前者の方がお楽じゃないかと。今回はBufferedSerialの方を練習してみます。なお、ご本家のAPIドキュメントは以下に。
最後に今回使用の main.cpp の全文を載せておきますが、とりあえず BufferedSerial関係部分のみ抜き出したものが以下に。
APIの中でread/writeするバッファサイズの定数が以下に。
#define MAXIMUM_BUFFER_SIZE (32)
Arduino互換ピンソケットのA0、A1に9600ボーのUARTを接続するためのインスタンスの定義とバッファの定義が以下に。なお、詳細指定のAPIを呼び出さなければ8ビット、ノンパリティ、ストップ1ビットがデフォルトみたいです。
BufferedSerial uart4(ARDUINO_UNO_A0, ARDUINO_UNO_A1, 9600); char buf[MAXIMUM_BUFFER_SIZE] = {0};
ループバック試験用の通信部分が以下に。10進65は大文字のAです。Aから始まってZまで毎秒自分で自分に送信すると、AならB、BならCという風に次々にたらいまわし?(当然ZまでくるとAに戻る)にされるというだけのもの。
buf[0] = 65; while (true) { uart4.write(buf, 1); if (uint32_t num = uart4.read(buf, sizeof(buf))) { printf("RCV %c\n", buf[0]); for (uint32_t idx = 0; idx < num; idx++) { if ((buf[idx] >= 65) || (buf[idx] <= 90)) { buf[idx] = (buf[idx]==90) ? 65 : buf[idx] + 1; } } } ThisThread::sleep_for(1000ms); }
今回実験のハードウエア
ずるずるとソフト、ハードをチョイ直ししながら使ってきたので、この辺で実験用ハードの回路図を改めて掲げておきます。
現物(NUCLEO-F401REボード上にArduino UNO用の「バニラ味」シールドボード上にデバイスを載せてあるもの)写真が以下に。
今回実験に使用したソース全文
本体のmain.cpp。形ばかりスレッド使っているやつ。
#include "mbed.h" #include "AQM0802.h" #define BLINKING_RATE 500ms #define AIN_RATE 1000ms #define AOUT_RATE 100ms #define MAXIMUM_BUFFER_SIZE (32) I2C i2c(I2C_SDA, I2C_SCL); AQM0802 lcd(i2c); DigitalOut ledRED(ARDUINO_UNO_D2); Thread threadLED; AnalogIn ain5(ARDUINO_UNO_A5); Thread threadAIN5; PwmOut pwmD3(ARDUINO_UNO_D3); AnalogOut ao2(ARDUINO_UNO_A2); Thread threadAOUT2; BufferedSerial uart4(ARDUINO_UNO_A0, ARDUINO_UNO_A1, 9600); char buf[MAXIMUM_BUFFER_SIZE] = {0}; void blink_thread() { while (true) { ledRED = !ledRED; ThisThread::sleep_for(BLINKING_RATE); } } void ain_thread() { char dstr[8]; while (true) { sprintf(dstr, "%u", ain5.read_u16()); lcd.displaySTR(LCD_LLINE, dstr); ThisThread::sleep_for(AIN_RATE); } } void aout_thread() { while (true) { ao2 = 0.1f; ThisThread::sleep_for(AOUT_RATE); ao2 = 0.2f; ThisThread::sleep_for(AOUT_RATE); ao2 = 0.3f; ThisThread::sleep_for(AOUT_RATE); } } int main() { lcd.init(); lcd.displaySTR(LCD_HLINE, "Voltage"); pwmD3.period_us(2041); pwmD3.pulsewidth_us(64); threadLED.start(blink_thread); threadAIN5.start(ain_thread); threadAOUT2.start(aout_thread); uart4.enable_input(true); uart4.enable_output(true); buf[0] = 65; while (true) { uart4.write(buf, 1); if (uint32_t num = uart4.read(buf, sizeof(buf))) { printf("RCV %c\n", buf[0]); for (uint32_t idx = 0; idx < num; idx++) { if ((buf[idx] >= 65) || (buf[idx] <= 90)) { buf[idx] = (buf[idx]==90) ? 65 : buf[idx] + 1; } } } ThisThread::sleep_for(1000ms); } }
AQM0802 LCDパネル制御クラスのヘッダ
#ifndef AQM0802_H #define AQM0802_H #include "mbed.h" #define LCD_ADDR (0x7c) #define LCD_HLINE (0x80) #define LCD_LLINE (0xC0) #define LCD_MAXLEN (8) class AQM0802 { private: I2C& i2cRef; char wdata[2]; int status; public: AQM0802(I2C& ch); int writeCommand(char cmd); int writeData(char dat); int clear(); int displaySTR(char Lin, const char* message); int init(); }; #endif //AQM0802_H
AQM0802 LCDパネル制御クラスの本体
#include "AQM0802.h" AQM0802::AQM0802(I2C& ch) : i2cRef(ch) { wdata[0]=0; wdata[1]=0; } int AQM0802::writeCommand(char cmd) { status =0; wdata[0] = 0x0; wdata[1] = cmd; status = i2cRef.write(LCD_ADDR, wdata, 2, 0); return status; } int AQM0802::writeData(char dat) { status =0; wdata[0] = 0x40; wdata[1] = dat; status = i2cRef.write(LCD_ADDR, wdata, 2, 0); return status; } int AQM0802::clear() { status =0; wdata[0] = 0x0; wdata[1] = 0x01; status = i2cRef.write(LCD_ADDR, wdata, 2, 0); return status; } int AQM0802::displaySTR(char Lin, const char* message) { int idx = 0; status =0; wdata[0] = 0x0; wdata[1] = Lin; status = i2cRef.write(LCD_ADDR, wdata, 2, 0); wdata[0] = 0x40; while ((status == 0) && (idx < LCD_MAXLEN) && (*message != 0)) { wdata[1] = *message++; status = i2cRef.write(LCD_ADDR, wdata, 2, 0); idx++; } return status; } int AQM0802::init() { status =0; wdata[0] = 0x0; wdata[1] = 0x38; thread_sleep_for(40); status = i2cRef.write(LCD_ADDR, wdata, 2, 0); thread_sleep_for(2); wdata[1] = 0x39; status = i2cRef.write(LCD_ADDR, wdata, 2, 0); thread_sleep_for(2); wdata[1] = 0x14; status = i2cRef.write(LCD_ADDR, wdata, 2, 0); thread_sleep_for(2); wdata[1] = 0x70; status = i2cRef.write(LCD_ADDR, wdata, 2, 0); thread_sleep_for(2); wdata[1] = 0x56; status = i2cRef.write(LCD_ADDR, wdata, 2, 0); thread_sleep_for(2); wdata[1] = 0x6c; status = i2cRef.write(LCD_ADDR, wdata, 2, 0); thread_sleep_for(2); wdata[1] = 0x38; status = i2cRef.write(LCD_ADDR, wdata, 2, 0); thread_sleep_for(2); wdata[1] = 0x01; status = i2cRef.write(LCD_ADDR, wdata, 2, 0); thread_sleep_for(2); wdata[1] = 0x0c; status = i2cRef.write(LCD_ADDR, wdata, 2, 0); thread_sleep_for(2); wdata[1] = 0x01; status = i2cRef.write(LCD_ADDR, wdata, 2, 0); thread_sleep_for(100); return status; }