鳥なき里のマイコン屋(142) ラズパイPico、SDKでUart入出力

Joseph Halfmoon

以前、ラズパイPico上のMicroPythonでUart通信を行ったことはあったのですが、C/C++SDKではstdoutの出力先としての利用ばかりでした。今回C/C++SDKでも双方向の通信をやりたかったのでちょっと実験してみました。そして、長らく誤解してきたことにも気づきました。

※「鳥なき里のマイコン屋」投稿順Indexはこちら

(末尾に今回実験に使用したソース全文を掲げました。)

まずはラズパイPicoのSDKのHardware APIのドキュメンテーションへのリンクを貼り付けておきます。

hardware_uart

UARTへの入出力を行う場合、割り込みも使えますが、blocking なAPIも用意されています。今回、接続する相手はホスト上の端末エミュレータなので簡単に使える blocking なAPIでやってみます。

ハードウエアの接続

まず、ラズパイPicoが2つ持っているuartのうち、uart1を使用して実験を行いました。uart1にアサイン可能なポートは複数ありますが、以下の端子を使用です。

    • GP4、uart1 TX
    • GP5、uart1 RX

また、ホストのRaspberry Pi 4の方は、

    • GPIO14、TX
    • GPIO15、RX

です。TXがRXに、RXがTXに接続するようにクロス配線です。

接続したところが以下に。

uartTest_DUTこの状態で、母艦のRaspberry Pi 4とラズパイPicoの間には3つの通信経路が存在しています。

    1. USB(Raspberry Pi 4側がホスト、ラズパイPicoがデバイス)、Raspberry Pi 4上のシリアルデバイスとしては、 /dev/ttyACM0 として見える
    2. デバッグ用のSWDCLK, SWDIO。ラズパイPicoの短辺のデバッグポートに接続しているもの。デバッガを利用したプログラムのダウンロードやデバッグに使用。
    3. ラズパイPicoの uart1 と、Raspberry Pi 4上の /dev/ttyS0 の間の接続

テスト時には母艦の上で複数のシリアル通信を使い分ける必要があるので、以下のようにalias を定義して使っています。

alias picoUSB='minicom -b 115200 -o -D /dev/ttyACM0'
alias picoUart='minicom -b 115200 -o -D /dev/ttyS0'
ソフトウエアの設定

ラズパイPico上では以下のような設定としました。

    1. stdio は USBシリアルに向け、uartには接続しない(ディセーブル)
    2. プログラムでは uart1 を使って、母艦上の端末ソフトとの間で双方向の通信テストを行う

1のために CMakeLists.txt では以下のように記述しています。

pico_enable_stdio_uart(uartTST0 0) 
pico_enable_stdio_usb(uartTST0 1)
実行結果

末尾のコードをビルドし実行しました。そのとき母艦のRaspberry Pi 4上で 端末ソフト minicom を二つ走らせ、片方は /dev/ttyACM0 (ラズパイPicoのstdio)、もう一方は /dev/ttyS0 (ラズパイPicoのuart1)に接続してみました。

動作している様子は以下に。

uart1_working
左側の /dev/ttyACM0 には標準出力に向いている printf の結果が出力されています。右側の /dev/ttyS0では、以下の操作が繰り返されています。

    1. プロンプトのつもりの > が表示される
    2. 端末から適当に一文字キー入力する
    3. ラズパイPico側から入力されたキーのキャラクタが折り返されてくる
    4. つづいて復帰改行
    5. 上記でキー入力されたキャラクタコードを16進数に変換したものも返されてくる
    6. 再度復帰改行

2のステップのキー入力に blocking のAPIを使っているので、minicomにキー入力をするまでラズパイPicoは待っています。予定通りの動作をしていますな。まあ、これを膨らませていけば、uart使ってラズパイPicoのプログラムに何か指令を送る、といったことも可能にできるでしょう。

uart0 は使えないの?

さて、上記は 2つある uartのうち、uart1を使って動作させました。もう一つのuartである uart0については、かねてからの疑問があり、後回しにしていました。

以前にラズパイPicoのMicroPythonしていたときに REPL は uart0 に向いている、という記述を何処かで読んだ記憶がありました。通常MicroPythonはUSB接続のみでもREPLが使えます。REPLがuart0に接続しているなら、他の目的には使えないね、と避けてました。

またC/C++SDKでの利用でも、どういう分けか、たまにRaspberry Pi 4が ラズパイPicoのUSBシリアルに接続している /dev/ttyACM0 を認識しなくなるような場合があり(無理やりRESETかけたようなときに発生するみたい?原因不明)、その際には、stdioをuart0に向け直して切り抜けたりしていました。このため、uart0は何かの時の「リザーブ」扱いでした。

外付けのUSBチップ(多くはデバッグ制御用のマイコン)経由でUSBシリアルと通信しているようなマイコンボードでは、ターゲットマイコン上のUARTの一つを、外部でUSBチップに接続してUSBシリアルと通信するようなことがあります。しかし、ラズパイPicoにはそのようなお供のチップは無く、ターゲットマイコンRP2040自体が内蔵するUSBをTiny USBを使って制御しています。

stdio がUARTに向いていなければ、uart0 はUSBシリアルとは独立につかえるんじゃね?

実際にやってみました。末尾のソースを uart0 用に書き換え、Raspberry Pi 4からのTX,RXを uart 0の GP0, GP1につなぎ変えてみました。

ほえほえー、stdioと混信もせず、普通に動くじゃん

なんだかな~。別にハードウエアのUARTと、USBのCDC( Communication Device Class )経由の stdio は無関係なのね。いままで使わずにいて損した感じだな~。誤解していたな~。本当だよな?

鳥なき里のマイコン屋(141) ラズパイPico、ハードウエア割り算器の利用 へ戻る

鳥なき里のマイコン屋(143) ラズパイPico、PlatformIOでArduino へ進む

実験に使用したソース全文
#include <stdio.h>
#include "pico/stdlib.h"
#include "hardware/clocks.h"

#define UART_BAUD   (115200)
#define UART1_TX    (4)
#define UART1_RX    (5)
#define UART0_TX    (0)
#define UART0_RX    (1)
#define BUF_SIZE    (8)

int main()
{
    char temp;
    char buf[BUF_SIZE];

    stdio_init_all();

    uart_init(uart0, UART_BAUD);
    gpio_set_function(UART0_TX, GPIO_FUNC_UART);
    gpio_set_function(UART0_RX, GPIO_FUNC_UART);

    for (int i=0; i<1000; i++) {
        printf("STD-OUT> UART TEST. %d\n", i);
        uart_putc(uart0, '>');
        temp = uart_getc(uart0);
        uart_putc(uart0, temp);
        snprintf(buf, BUF_SIZE, "%hhx", temp);
        uart_puts(uart0, "\r\n0x");
        uart_puts(uart0, buf);
        uart_puts(uart0, "\r\n");
    }

    puts("End of Execution.");
    return 0;
}
CMakeLists.txt
# Generated Cmake Pico project file

cmake_minimum_required(VERSION 3.13)

set(CMAKE_C_STANDARD 11)
set(CMAKE_CXX_STANDARD 17)

# initalize pico_sdk from installed location
# (note this can come from environment, CMake cache etc)
set(PICO_SDK_PATH "/home/pi/pico/pico-sdk")

# Pull in Raspberry Pi Pico SDK (must be before project)
include(pico_sdk_import.cmake)

project(uartTST0 C CXX ASM)

# Initialise the Raspberry Pi Pico SDK
pico_sdk_init()

# Add executable. Default name is the project name, version 0.1

add_executable(uartTST0 uartTST0.c )

pico_set_program_name(uartTST0 "uartTST0")
pico_set_program_version(uartTST0 "0.1")

pico_enable_stdio_uart(uartTST0 0)
pico_enable_stdio_usb(uartTST0 1)

# Add the standard library to the build
target_link_libraries(uartTST0 pico_stdlib)

# Add any user requested libraries
target_link_libraries(uartTST0
        hardware_clocks
        )

pico_add_extra_outputs(uartTST0)