鳥なき里のマイコン屋(134) ラズパイPico、C/C++SDKでプログラマブルIO

Joseph Halfmoon

他のマイコンでPIOと言えばパラレルIOでしょうが、ラズパイPico(のRP2040マイコン)では違います。プログラマブルIO。CPUとは独立に動作するIO専用のステートマシン。「MicroPython的午睡」シリーズで何度か使ってみましたが、C/C++から使うときはお作法が違う、ということで今回はPIO使ったオブジェクトのビルドのサンプル、bit-bangingを。

(末尾に今回実験で使ったファイル類全文を掲げました。)

強力なPIOですが、今回実験は単にGPIO端子をペコペコとトグルさせるだけの

bit-banging

であります。「PIO業界」(そんな業界があるのか?)のHello Worldということで。

さて、PIOは、PIOステートマシン側とCPU側の両方でプログラムする必要があります。SDKのPDFドキュメントはPIOのステートマシンが持つ命令について詳しい上に、サンプルコードなどもいくつも掲載されています。これ読むのが最初ですかね(MicroPythonでPIOプログラムするときも、結局PIO側についてはこれを見てました。)

Raspberry Pi Pico C/C++ SDK

しかし、PIOを制御するためのSDKのAPIについては、Raspberry Pi Pico SDK Documentationの以下のページの方が探しやすくて、見やすい感じじゃないかと思います。

hardware_pio

ただね、どこみてもPIO使ったプロジェクトのビルド過程についてはあまりまとまったことは書いてない気がします。

とりあえずGUIのプロジェクト・ジェネレータでプロジェクト生成

ラズパイPicoの開発用母艦にはRaspberry Pi 4 を使用しています。ラズパイPicoのプロジェクト生成には Raspberry Pi Pico Project Generator を使わせていだだいておりますが、最近怠慢なので、もっぱらGUIモードで使っています。こんな感じ。

PIO000_ProjGen上記の設定で、「一応」 PIO を使うつもりの CMakeLists.txt (後でちょいと不足があること分かりますが)が生成され、buildディレクトリに必要なファイルがコピーされます。また、PCからVSCodeでリモート編集しているのですが、VS Code上でのビルドやデバッグに必要なファイル類もセットアップしてくれます。後はPCからリモート接続のVS Codeで実コードを書いてビルドすれば良い筈。

ビルド成功までの道のり

生成したプロジェクト・ディレクトリをVS Codeで開いて分かるのは、「Cのソースの雛形ファイルは用意されているけれどPIO ASMのそれは無い」ということであります。CMakeLists.txtの中でPIOを使うよ、という設定はしてくれる(ちょっと不足)けれど具体的なソースは自分で作れ、ということでしょう。

拡張子 “.pio” のPIOアセンブラのソースを該当のディレクトリ内に作成、その後Cのソースも作成してみました。さてビルド。エラーですね。

[build] Build finished with exit code 2

ビルドの出力を読んでみると、PIOアセンブラのソースを読み取って「自動生成される筈」のヘッダファイルが不在のためのエラーでした。あれれ自動で作ってくれるのではないの?原因追及すると、ジェネレータが作ってくれたCMakeLists.txt にはヘッダファイル自動生成のための記述が抜けていたのです(ヘッダを自分で書く人にあわせてある?)CMakeLists.txtに以下1行を追加いたしました(pio000というのが、今回生成したプロジェクトのお名前です。)

pico_generate_pio_header(pio000 ${CMAKE_CURRENT_LIST_DIR}/pio000.pio)

VS CodeはCMakeList.txtを変更すると即座に裏でCMakeかけてくれているみたい。さてこの修正でビルドは通るようになりました。

bit-bangingプログラムの中でやっていること

Cのソース内でやっていることを列挙すると以下のようです。

  1. PIOの2つあるインスタンスのうち pio0 を使用する。
  2. pio0の中で使用可能なPIOのプログラムメモリのオフセットアドレス(offset)と空いているステートマシン(sm)の番号を得る。
  3. 得たpioインスタンス、sm、offsetを引数にして自動生成されたヘッダソースの中にある初期化関数を呼び出してPIOをスタートさせる
  4. CPUは勝手にしばらく走る
  5. CPUの実行が終わったところで、PIOも止める

CPUは設定だけで、PIOがやる仕事の内容には「一切口を出してない」ことが分かります。

PIOアセンブラソース内に書いていることは、前後半で分かれます。前半部分はPIOステートマシンに対するアセンブラ記述です。今回はたった3命令でおしまい。この3命令が延々と繰り返し実行されることになります。

後半部分は、上記のアセンブラ記述をCから呼び出すときに使うCのためのヘッダファイル生成のための記述です。よって、アセンブラといいつつCのコード。ヘッダファイルなので、inline関数として記述しておきます。実はPIO本体よりもココがちょっとメンドイです。

  1. 該当のGPIO端子をPIOで使えるようにする
  2. GPIO端子の方向(入出力)を決める
  3. PIOステートマシンの走る速さ(クロック)決める
  4. PIOステートマシンを初期化して走らせる

やっていることはMicroPythonからPIOを使うときと大差ないのですが、1行、1関数でかけるMicroPythonに比べると行数多い。。。文句いうなよ、自分。

実機で実行した結果

実機の横にDigilent AnalogDiscovery2を置いて、出力波形を観察してみました。こんな感じ。

bitBangWaveForm3命令で1ループ、ハイ期間は1命令分なので、ほぼほぼデューティ3分の1の波形が見えている感じ。ステートマシンは、システムクロックの64分周に設定したつもり。上記の測定値から逆算するとシステムクロックは125MHzくらいに見えます。本当は133MHzの筈だけれど。どこでこの差が生じるのだろう。。。

本日のところは、とりあえずビルドできるようようなりました。動作も簡単なbit-bangingで確認できました。しばらくPIOでいろいろやってみるつもりなので、追及はまた今度か。。いつものことだが追及が弱いな。

鳥なき里のマイコン屋(133) ラズパイPico、C/C++SDKでマルチコアもお手軽 へ戻る

鳥なき里のマイコン屋(135) ラズパイPico、C/C++SDKでPIO、多ビット出力 へ進む

手修正済のCmakeLists.txt
# Generated Cmake Pico project file

cmake_minimum_required(VERSION 3.13)
set(CMAKE_C_STANDARD 11)
set(CMAKE_CXX_STANDARD 17)
set(PICO_SDK_PATH "/home/pi/pico/pico-sdk")
include(pico_sdk_import.cmake)
project(pio000 C CXX ASM)

pico_sdk_init()
add_executable(pio000 pio000.c )

pico_generate_pio_header(pio000
        ${CMAKE_CURRENT_LIST_DIR}/pio000.pio)

pico_set_program_name(pio000 "pio000")
pico_set_program_version(pio000 "0.1")

pico_enable_stdio_uart(pio000 0)
pico_enable_stdio_usb(pio000 1)

target_link_libraries(pio000
        pico_stdlib
        hardware_pio
        )

pico_add_extra_outputs(pio000)
PIOアセンブラ “bit-banging” のソース
; PIO000
; Just GPIO bit-banging
.program pio000

loop:
    set pins, 1
    set pins, 0
    jmp loop

% c-sdk {
#include "hardware/gpio.h"

static inline void pio000_program_init(PIO pio, uint sm, uint offset, uint pin)
{
    pio_sm_config c = pio000_program_get_default_config(offset);
    sm_config_set_set_pins(&c, pin, 1);
    pio_gpio_init(pio, pin);
    pio_sm_set_consecutive_pindirs(pio, sm, pin, 1, true);
    sm_config_set_clkdiv(&c, 64);
    pio_sm_init(pio, sm, offset, &c);
    pio_sm_set_enabled(pio, sm, true);
}

static inline void pio000_program_stop(PIO pio, uint sm)
{
    pio_sm_set_enabled(pio, sm, false);
}
%}
PIOのbit-bangingプログラムを呼び出すCソース
#include <stdio.h>
#include "pico/stdlib.h"
#include "hardware/pio.h"
#include "pio000.pio.h"

#define SETPIN (22)

int main()
{
    int count=0;
    stdio_init_all();
    printf("Start PIO000.\r\n");

    PIO pio = pio0;
    uint offset = pio_add_program(pio, &pio000_program);
    uint sm = pio_claim_unused_sm(pio, true);
    pio000_program_init(pio0, sm, offset, SETPIN);
    while(count < 100) {
        printf("COUNT: %d\r\n", count++);
        sleep_ms(2000);
    }
    pio000_program_stop(pio0, 0);
    printf("End of Execution.\r\n");
    return 0;
}