モダンOSのお砂場(90) NucleoでArduinoからMbed OS6、シリアル通信

Joseph Halfmoon

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ドキュメントは以下に。

BufferedSerial

最後に今回使用の 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);
}
今回実験のハードウエア

ずるずるとソフト、ハードをチョイ直ししながら使ってきたので、この辺で実験用ハードの回路図を改めて掲げておきます。DUT_schematic

現物(NUCLEO-F401REボード上にArduino UNO用の「バニラ味」シールドボード上にデバイスを載せてあるもの)写真が以下に。DUT_wShieldB

今回実験に使用したソース全文

本体の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;
}

モダンOSのお砂場(89) NucleoでArduinoからMbed OS6、printf へ戻る

モダンOSのお砂場(91) ArduinoとNucleo(Mbed OS6)間シリアル通信 へ進む