鳥なき里のマイコン屋(137) ラズパイPico、C/C++SDKでPIO、サイド出力

Joseph Halfmoon

前回は、ラズパイPicoのユニークなPIO(Programable IO)でパラレル入出力をやってみました。ただし、出力タイミングはCPUからの書き込み次第でした。今回は、CPUタイミングを排除し、ハード側の一定のタイミングで出力するとともに、パラレル出力には必須のストローブ信号を付加してみたいと思います。

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

(末尾に実験で使用した、cmake用のCMakeLists.txt、C言語ソース、PIOアセンブラソースを添付いたしました。クロス開発ツールは、Raspberry Pi 4上のRaspberry Pi OS<32bit版>上で運用し、PC上のVS Codeからリモート接続で使用しています。)

前回からの差分のポイント

パラレル出力データはCPUからの書き込みによります。前回は、CPUがPIOのFIFOに書き込むのを blocking で待っていました。CPU書き込みをトリガに出力動作がスタートする方式です。これに対して今回は、

    • 常に一定のタイミングで8ビット・パラレルデータを出力し続ける
    • 出力時にはストローブ信号も発生する
    • CPUからの書き込みデータはFIFOで受けるが、出力するのはハード側のタイミングによる。CPUからデータが到着しない場合は、前回のデータを再出力する。

という方式にいたしました。このため、PIOの以下の機能を使用しています。使用している機能についての説明は、RP2040 Datasheet 3.5 Functional Detailsあたりが詳しいです。

    1. ノンブロッキングの pull
    2. サイド出力

前回使用したブロッキングのpull命令は、CPUからのデータがFIFOに無いと「待ち」に入ってしまいました。ノンブロッキング指定するとデータがFIFOに無い場合も即座に次の命令に制御が移り、待ちには入りません。でもデータをもらっていないのにどうするのというと、 x レジスタに格納されている値を出力用のレジスタである osr レジスタにコピーします。その後出力用に out 命令を発行すれば、x レジスタの内容が出力されるというわけです。このような仕組みなので前回のデータを x レジスタに残しておけば、CPUからのデータが無ければそれが出力されます。以下のコードでは、

    1. 初回だけは前回のデータが不在なので、ブロッキングでpull
    2. 2回目以降はノンブロッキングでpull
    3. pullした後は、常に x レジスタに値を保存しておく(CPUからの書き込みがあればその値が入るし、CPUからの書き込みがなければ x からコピーしたOSRの中身を再び x に書き戻すだけ)

という手順です。

    pull    block           side    0
    mov     x, osr          side    0
.wrap_target
    out     pins, 8         side    1
    pull    noblock         side    0
    mov     x, osr          side    0
.wrap
サイド出力

PIOはOUT、SET命令で出力できる端子のセット以外にもう一つ、sideという指定で出力できる端子のセットを持つことができます。ただしこちらのビット幅は最大5ビットまでと狭いです。また、サイド指定すると無関係な遅延設定に使えるビット数が減るというビットエンコード上の関係があります(同じフィールドをシェアしているから。)

サイド信号は一定のタイミングで出力したいクロック信号、ストローブ信号など制御信号に向いた機能ではないかと思います。上記のPIOアセンブラ例では、出力 out の際に「ストローブ信号のつもり」の信号1ビットを操作しています。外部ではストローブ信号の立下りエッジで、パラレル出力をラッチすればバリッドな値を取り込めるはず。

上記例だと以下のようなタイミングです。

    1. PIOクロック設定は1サイクル約1.04μ秒
    2. ループはPIO命令3サイクルで回っている。
    3. ストローブ信号のハイのパルス幅は1サイクル分
動作結果

末尾のコードをビルドして、ラズパイPicoに書き込みRUNさせました。まずオシロスコープで「ストローブ信号」(もどき)を観察。以下のように約3.07μ秒(微妙に予定より短いけれども、追及せず、まあいいか)で回っており、ストローブ信号のハイ期間も予定どおりの約1μ秒程度です。

SIDE_WAVEつづいて、8ビットのパラレル出力と合わせて「ロジアナ」表示で確かめてみました。Clockと書かれているのが「ストローブ信号」です。前回と違って、ハード固定のタイミングで動作していることが分かると思います。一方出力データは192が2回つづくなど、CPUからの書き込みが間に合わない場合は、前回の値がそのまま再出力されていることが分かると思います。Cのコードを見ていただくとわかりますが、PIO側は固定の約3μ秒で回っていますが、CPU側はそれより遅い5μ秒+α時間(よくわからんケド)で回っているためです。

LOGIC_WAVEサイド出力と、ノンブロッキングの使い方をやってみました。PIOには、たった9命令しかないといいつつ奥ば深い。。。

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

鳥なき里のマイコン屋(138) ラズパイPico、外付けDAC、MCP4725接続 へ進む

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(pio003 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(pio003 pio003.c )

pico_set_program_name(pio003 "pio003")
pico_set_program_version(pio003 "0.1")

pico_enable_stdio_uart(pio003 0)
pico_enable_stdio_usb(pio003 1)

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

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

# Add any user requested libraries
target_link_libraries(pio003
        hardware_pio
        )

pico_add_extra_outputs(pio003)
CのMainプログラムソース
#include <stdio.h>
#include "pico/stdlib.h"
#include "hardware/gpio.h"
#include "hardware/pio.h"
#include "pio003.pio.h"

#define OPINS   (2)
#define SPIN   (14)

int main()
{
    uint32_t result;
    uint32_t testPat=0;

    stdio_init_all();         
    printf("Start PIO003.\r\n");

    for (int i=OPINS; i<(OPINS+8); i++) {
        gpio_init(i);
        gpio_set_dir(i, GPIO_OUT);
        gpio_put(i, 1);
    }

    PIO pio = pio0;
    uint sm = pio_claim_unused_sm(pio, true);
    pio_sm_restart(pio, sm);
    uint offset = pio_add_program(pio, &pio003_program);
    pio003_program_init(pio, sm, offset, OPINS, SPIN);
    while( testPat < 0xFFFF0000 ) {
        pio_sm_put(pio, sm, testPat++);
        sleep_us(5);
    }
    pio003_program_stop(pio, sm);
    printf("End of Execution.\r\n");
    return 0;
}
PIOアセンブラのソース
; PIO003
; GPIO nonblocking output test
.program pio003
.side_set   1

    pull    block           side    0
    mov     x, osr          side    0
.wrap_target
    out     pins, 8         side    1
    pull    noblock         side    0
    mov     x, osr          side    0
.wrap

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

static inline void pio003_program_init(PIO pio, uint sm, uint offset, uint opin, uint spin)
{
    pio_sm_config c = pio003_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
    sm_config_set_sideset_pins(&c, spin);
    for (uint pidx=opin; pidx < (opin + 8); pidx++) {
        pio_gpio_init(pio, pidx);
    }
    pio_gpio_init(pio, spin);
    pio_sm_set_consecutive_pindirs(pio, sm, opin, 8, true);
    pio_sm_set_consecutive_pindirs(pio, sm, spin, 1, true);
    sm_config_set_clkdiv(&c, 128);
    pio_sm_init(pio, sm, offset, &c);
    pio_sm_set_enabled(pio, sm, true);
}

static inline void pio003_program_stop(PIO pio, uint sm)
{
    pio_sm_set_enabled(pio, sm, false);
}
%}