こんにちは、ENGかぴです。
Raspberry Pi PicoをVSCodeの拡張機能であるPico SDKを使用するとUARTを実装することができます。UARTのAPIの使い方をシリアルモニター用と外部機器用に分けて通信する例でまとめました。
本記事はPico SDKを使ってC/C++のプロジェクトを作成してシリアル通信を行います。下記記事の電文をシリアルモニターで送出して動作確認を行います。
Raspberry Pi PicoでSerialライブラリを使用する
Raspberry Pi Pico(以下Picoとする)と拡張基板のGrove Shield for Pi Picoを使用しています。PCとPicoの通信をUSBを使って通信するためUSB信号とシリアル通信を変換するモジュールとしてAE-CH340E-TYPEC(秋月電子)を使用しています。また外部機器としてPIC16F1827(以下PICマイコンとする)と通信します。
VSCodeのダウンロードとインストールの方法やVSCodeにPicoの開発環境を追加する方法は下記記事を参考にしてください。
Raspberry Pi Picoの開発をVSCodeで行う方法
Picoを使用してArduino IDEやVSCodeで動作確認したことをまとめています。
UARTのAPIを使用する
プロジェクトを生成する際にStdio supportのConsole over USBを選択するとUSB CDCを使用してシリアルモニターに文字列を表示することができます。シリアルモニターからPicoにデータを送信する場合は処理が停滞するブロッキングするAPIを使用するため実用的ではありません。
USB CDC用のTinyUSBを使用するとUARTのAPIと同様に通信ができますが、ディスクリプタを実装したりベクター登録が必要になったりするため制限があります。
個人的には手軽にシリアルモニターを使用したいため、USB CDCを使用せずにConsole over UARTを選択してシリアル通信とUSB通信を変換する方法でシリアルモニターを使用する方法を推奨しています。
プロジェクトの生成
プルジェクトの生成方法はRaspberry Pi Picoの開発をVSCodeで行う方法で説明していますが、FeaturesとStdio supportのUART関係にチェックを入れます。

プロジェクトを生成する際にUART関係の項目にチェックを入れます。FeaturesではUARTにチェックを入れます。Stdio supportはConsole over UARTにチェックを入れます。例ではConsole over USBにもチェックを入れていますが、UARTのみ使用する場合はチェックしなくても問題ありません。
プロジェクトを生成するとデフォルトでUART関連の初期化と定義が自動で生成されます。
PR:わからないを放置せず、あなたにあった最低限のスキルを身に着けるコツを教える テックジム 「書けるが先で、理解が後」を体験しよう!
初期化処理を実装する
// Use pins 0 and 1 for UART0
//#define UART0_TX_PIN 0
//#define UART0_RX_PIN 1
// Use pins 4 and 5 for UART1
#define UART1_TX_PIN 4
#define UART1_RX_PIN 5
//初期化の例
//uart_init(uart0, 115200);
uart_init(uart1, 19200);
//gpio_set_function(UART0_TX_PIN, GPIO_FUNC_UART); // 例: TXにGPIO0を設定
//gpio_set_function(UART0_RX_PIN, GPIO_FUNC_UART); // 例: RXにGPIO1を設定
gpio_set_function(UART1_TX_PIN, GPIO_FUNC_UART); // 例: TXにGPIO4を設定
gpio_set_function(UART1_RX_PIN, GPIO_FUNC_UART); // 例: RXにGPIO5を設定
デフォルトで準備されているコードを流用しながらUART0とUART1の設定を行います。UART0はデフォルトでGPIO0(TX)とGPIO1(RX)が設定されているので条件を変更しない限り処理を追加する必要はありません。
初期化の例でコメントアウトしている個所はデフォルトで設定されているため省略しても問題ありません。
UART0とUART1のデフォルトピンは下記の通りです。
種別 | デフォルトピン(ハードウェア) |
---|---|
UART0 | GPIO0(TX)、GPIO1(RX) |
UART1 | GPIO4(TX)、GPIO5(RX) |
デフォルトピンはハードウェアUARTのピンでありUART専用で使用できるためボーレートの条件を高速にすることができます。
デフォルトピン以外を指定することができますが、その場合はソフトウェアUARTとなりAPIが生成したUART信号に従うのでボーレートを高速にできないなど制約があるためデフォルトピンを使用してシリアル通信を行うことを推奨します。
uart_init()関数は指定したUARTのボーレートの条件を指定します。第1引数にUARTの種別と指定し、第2引数にボーレートを指定します。ボーレートは外部機器のボーレートと一致させる必要があります。
UART1は外部機器のPICマイコンに合わせて19200bpsを指定しています。
gpio_set_function()関数はGPIOピンの周辺機器の設定を行います。第1引数に対象のGPIOピンを指定し、第2引数に周辺機器の機能を指定します。UARTとして使用するためデフォルトから変更せずにGPIO_FUNC_UARTを指定しています。
受信と送信の例
//uart0で受信してuart1に出力する例
while(uart_is_readable(uart0)){
u0_rcvdata.buf[u0_rcvdata.wp] = uart_getc(uart0);
uart_putc(uart0,(char)u0_rcvdata.buf[u0_rcvdata.wp] );// PCから受信したデータを表示
uart_putc_raw(uart1,(char)u0_rcvdata.buf[u0_rcvdata.wp] );
if( ++u0_rcvdata.wp >= RING_SZ ){
u0_rcvdata.wp = 0;
}
}
uart_is_readable()関数でUARTが受信状態を確認します。引数に指定したUARTに受信データが存在する場合はtrueが戻り値になります。
uart_getc()関数で引数に指定したUARTの受信データを読み込みます。戻り値が読み込んで受信データになるので変数に格納(3行目)します。
uart_putc()関数は第1引数に指定したUARTに対して、第2引数で指定したchar型のデータを改行変換ありで送信します。文字の\nを送信すると\nを\r\nに変換して送信します。シリアルモニターに改行して表示する場合に使用します。
uart_putc_raw()関数は上記と同じ使用方法ですが改行変換がありません。外部機器への電文など決まったバイト列の場合は変換しないほうが良い場合もあるため、外部機器への送信はuart_putc_raw()関数を使用することを推奨します。
例ではシリアルモニターに受信した文字を表示しつつ、UART1に文字を送信しています。
PR:スキマ時間で自己啓発!スマホで学べる人気のオンライン資格講座【スタディング】まずは気になる講座を無料で体験しよう!
動作確認
Pico SDKのシリアルモニターからPicoに文字列のデータを送信しPICマイコンが応答するかを確認します。電文の構成は下記記事を流用しています。シリアルモニターから送信した電文でPICマイコンのLEDを点灯/消灯します。
PICマイコンに送信する電文

電文はヘッダーからチェックサムまでの文字列を送信します。Picoから送信するヘッダーは「’P’」(0x50)としPICマイコンのLEDを点灯させる場合は「0x50 0x04 0x30 0x31 0x32 0x33 0xC6」を送信します。LEDを消灯させる場合は「0x50 0x04 0x33 0x32 0x31 0x30 0xC6」を送信します。
PICマイコンからはヘッダー「’A’」に点灯の場合は文字列の「OK-1」、消灯の場合は「OK-2」とチェックサムを付加して応答します。
動作確認用の回路図

PicoのUART0をシリアルモニターで使用するためUSBモジュール(AE-CH340E-TYPEC)を使用しています。PicoはDC3.3Vのマイコンなので電圧をDC5V~DC3.3Vに変換して接続しています。
UART1はPICマイコンのシリアルピンと接続します。シリアルモニターからUART0を使ってPicoに電文を送信します。

シリアルモニターでポートを選択します。USBモジュールを挿入して生成されたCOMポートを選択します。ボーレートはデフォルトの115200bpsを選択します。
Picoは電源を投入して5秒後に「Hello, UART!」を表示します。その後電文待機した状態になるのでシリアルモニターから電文を送信します。電文は16進数で送信するため入力欄横のリストから「16進数として送信」を選択します。

「50 04 30 31 32 33 C6」を入力してリターン(または送信マーク:紙飛行機)を押すとデータが送信されてLED2が点灯します。同時にシリアルモニターに「OK-1」が表示されます。文字列の両端に文字化けした表記がありますが、テキストデータ以外のバイナリデータによる文字化けです。
次に「50 04 33 32 31 30 C6」を入力して送信するとLED2が消灯します。シリアルモニターには「OK-2」が表示されます。
ソースコード全体
ソースコードは記事作成時点において動作確認できていますが、使用しているライブラリの更新により動作が保証できなくなる可能性があります。また、ソースコードを使用したことによって生じた不利益などの一切の責任を負いかねます。参考資料としてお使いください。
#include <stdio.h>
#include "pico/stdlib.h"
#include "hardware/uart.h"
#define U0_BAUD_RATE 115200
#define U1_BAUD_RATE 19200
#define PIN_DO1 25
#define TIME_OFF -1 //タイマーを使用しない場合
#define TIME_UP 0 //タイムアップ
#define BASE_CNT 10 //ベースタイマカウント値
#define LED_ONOFF 50
// Use pins 0 and 1 for UART0
#define UART0_TX_PIN 0
#define UART0_RX_PIN 1
// Use pins 4 and 5 for UART1
#define UART1_TX_PIN 4
#define UART1_RX_PIN 5
#define RING_SZ 128
struct RING_MNG{
uint8_t wp;
uint8_t rp;
uint8_t buf[RING_SZ];
};
RING_MNG u0_rcvdata;
RING_MNG u1_rcvdata;
uint32_t beforetimCnt;
int8_t timled = LED_ONOFF;
void mainApp(void);
void mainTimer(void);
int main()
{
stdio_init_all();
gpio_init(PIN_DO1); // ピン初期化
gpio_set_dir(PIN_DO1, GPIO_OUT); //DOピン
//uart_init(uart0, U0_BAUD_RATE);
uart_init(uart1, U1_BAUD_RATE);
//gpio_set_function(UART0_TX_PIN, GPIO_FUNC_UART); // 例: TXにGPIO0を設定
//gpio_set_function(UART0_RX_PIN, GPIO_FUNC_UART); // 例: RXにGPIO1を設定
gpio_set_function(UART1_TX_PIN, GPIO_FUNC_UART); //例: TXにGPIO4を設定
gpio_set_function(UART1_RX_PIN, GPIO_FUNC_UART); //例: TXにGPIO5を設定
sleep_ms(5000);
uart_puts(uart0, " Hello, UART!\n");
while (true) {
mainApp();
mainTimer();
}
}
/* メイン処理 */
void mainApp(void){
while(uart_is_readable(uart0)){
u0_rcvdata.buf[u0_rcvdata.wp] = uart_getc(uart0);
uart_putc(uart0,(char)u0_rcvdata.buf[u0_rcvdata.wp] );// PCから受信したデータを表示
uart_putc_raw(uart1,(char)u0_rcvdata.buf[u0_rcvdata.wp] );
if( ++u0_rcvdata.wp >= RING_SZ ){
u0_rcvdata.wp = 0;
}
}
while(uart_is_readable(uart1)){
u1_rcvdata.buf[u1_rcvdata.wp] = uart_getc(uart1);
uart_putc(uart0,(char)u1_rcvdata.buf[u1_rcvdata.wp] );// PCから受信したデータを表示
if( ++u1_rcvdata.wp >= RING_SZ ){
u1_rcvdata.wp = 0;
}
}
if( timled == TIME_UP ){
timled = LED_ONOFF;
gpio_put(PIN_DO1, !gpio_get(PIN_DO1));
}
}
/* タイマ管理 */
void mainTimer(void){
if( to_ms_since_boot(get_absolute_time()) - beforetimCnt > BASE_CNT ){
beforetimCnt = to_ms_since_boot(get_absolute_time());
//10msごとにここに遷移する
if( timled > TIME_UP ){
timled--;
}
}
}
メインのファイルの内容をコピーして置き換えることで使用できます。
プロジェクトを生成時のCMakeファイルの構成によって動作しない場合があります。CMakeLists.txtの構成で条件が不足している可能性があるため必要に応じて修正してください。
関連リンク
Arduinoのライブラリを使って動作確認を行ったことを下記リンクにまとめています。
Arduinoで学べるマイコンのソフト開発と標準ライブラリの使い方
Seeeduino XIAOで学べるソフト開発と標準ライブラリの使い方
ESP32-WROOM-32Eで学べるソフト開発と標準ライブラリの使い方
PR:企業で求められる即戦力技術を身に付ける テックキャンプエンジニア転職
最後まで、読んでいただきありがとうございました。