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

Joseph Halfmoon

ラズパイPicoのPIOですが、MicroPythonから使う方が簡単かな~などと思う今日この頃です。でもこちらのシリーズでは気を取り押してC/C++SDKでやります。さて、前回は単純bit-bangingでしたが、今回は任意の多ビットの値をPIO使って出力してみたいと思います。こんな進捗では何時までかかることやら。まあ急ぐ理由もなし。

末尾に今回実験で使用したCのソース全文を掲げました。ラズパイPicoの母艦(ツールチェーンを動かすデバイス)はRaspbian OS(32bit)のRaspberry Pi 4機を使い、PC上のVS Codeからリモート接続して使用しています。

今回、USBシリアルをイネーブルにしたつもりなのに、/dev/ttyACM0が見えないという問題が再び発生しました。前回うまくいったCmakeLists.txtとソースをまねっこしているので、今回うまくいかないのはなんでだろう?再び ttyS0接続に逃げてしまったので、CmakeLists.txtは以下のようになりました(後で原因究明しないとな。何時やるんだ、自分。)

今回利用の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(pio001 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(pio001 pio001.c )

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

pico_set_program_name(pio001 "pio001")
pico_set_program_version(pio001 "0.1")

pico_enable_stdio_uart(pio001 1)
pico_enable_stdio_usb(pio001 0)

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

# Add any user requested libraries
target_link_libraries(pio001
        hardware_pio
        )

pico_add_extra_outputs(pio001)

CMakeList.txtを修正してセーブすると自動でcmakeしてくれているみたいなので、ついついおろそかになるのですが、cmakeしただけであるとまだ以下のツールがセットアップされてません。

  • ELF2UF2Build
  • PIOASM

cmake後、一度buildディレクトリに入って make すれば、上記のツールがセットされるので安心かと。

PIOアセンブラ本体

さてPIOの制御ですが、今回も実質たった2命令です。なお前回は、JMP命令で「回して」いましたが、今回は疑似命令.wrap使っています。ポイントは以下です。

  1. 「pull block」命令で、CPUからの符号なし32ビット値を出力用シフトレジスタ(OSR)にブロッキング読み取りする(実際はblockがデフォルト動作なのでことさらに記述する必要はなし)
  2. 「out pins, シフトビット数」命令で、OSRの内容からシフトビット数分のデータをPINに出力する。

これを繰り返せば、CPUから与えられる多ビットの数値を繰り返しピンに出力することができるというものです。

CPUからPIOステートマシンの間には、符号なし32ビット値を書き込めるTX-FIFOというものがあります。PIO側からはpull命令でこのFIFOを読み取ることになります。ブロッキング読み出しなので、CPUからの書き込みが遅れている場合はPIO側が待つことになります。

前回のようにPIOの都合で「回って」いれば、ハードウエアのクロック通り正確なタイミングで端子操作が可能ですが、今回のようにループの中にCPU「待ち」が入ってしまっているとCPUソフトウエアの都合がモロに見えることになります。ハードウエアのタイミングが重要な応用ではpullをブロッキングしない方が良いかもしれません。そのための仕組みも用意されています。

また、PIOアセンブラヘッダに出力されるインライン関数部分は、前回のチョイ直しとは言え、だんだん分量が増えつつあって、醜いです。このあたり、MicroPythonからPIO使うときはもっと簡素に書けるのですがいたしかたありません。

; PIO001
; GPIO output test
.program pio001

.wrap_target
    pull    block
    out     pins, 8 [8]
    nop     [8]
.wrap

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

static inline void pio001_program_init(PIO pio, uint sm, uint offset, uint opin)
{
    pio_sm_config c = pio001_program_get_default_config(offset);
    sm_config_set_out_pins(&c, opin, 8);
    sm_config_set_out_shift(&c, true, false, 0); // shift_right, no-autopull
    for (uint pidx=opin; pidx < (opin + 8); pidx++) {
        pio_gpio_init(pio, pidx);
    }
    pio_sm_set_consecutive_pindirs(pio, sm, opin, 8, true);
    sm_config_set_clkdiv(&c, 128);
    pio_sm_init(pio, sm, offset, &c);
    pio_sm_set_enabled(pio, sm, true);
}

static inline void pio001_program_stop(PIO pio, uint sm)
{
    pio_sm_set_enabled(pio, sm, false);
}
%}
Cプログラム本体

プログラム本体で行っているのは以下です(使用しているAPIについてのSDKドキュメンテーションはこちら。)

  • 8ビット値0x00、0x55、0xAA、0xFFをPIOへ送って8本の端子から出力させる。
  • 上記を無限ループで繰り返す
  • なお、CPUはPIOへの1データ送信毎に50μ秒待つ

8ビットの値のうち最下位のビット0(黄色)とその上のビット1(青色)をオシロで観察したところが以下です。

PIO 2bit waveformbit waveform最下位のビット0の周期が約100μSであることが分かると思います。これはPIO側のタイミングではなく、CPU側で送信の度に50μS待つように設定しているためです。今回設定ではPIO側の処理の方が早いので「ブロッキング」でCPUを待っている形です。

実際に8ビット値が出力できている様子はこちら。0x00, 0x55, 0xAA, 0xFFが繰り返されているのが見えると思います。

Logic8bit

 

出力はできたような気がします。次は入力ですな。

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

鳥なき里のマイコン屋(136) ラズパイPico、C/C++SDKでPIO、今度は入力 へ進む

#include <stdio.h>
#include "pico/stdlib.h"
#include "hardware/pio.h"
#include "pio001.pio.h"

#define OPINS   (2)

void checkPat(PIO pio, uint sm) {
    pio_sm_put(pio, sm, 0x00);
    sleep_us(50);
    pio_sm_put(pio, sm, 0x55);
    sleep_us(50);
    pio_sm_put(pio, sm, 0xAA);
    sleep_us(50);
    pio_sm_put(pio, sm, 0xFF);
    sleep_us(50);
}

int main()
{
    stdio_init_all();
    printf("Start PIO001.\r\n");

    PIO pio = pio0;
    uint offset = pio_add_program(pio, &pio001_program);
    uint sm = pio_claim_unused_sm(pio, true);
    pio001_program_init(pio, sm, offset, OPINS);
    while (1) {
        checkPat(pio, sm);
    }
    pio001_program_stop(pio, sm);
    printf("End of Execution.\r\n");
    return 0;
}