
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;
}