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

Joseph Halfmoon

ラズパイPicoのPIO(Programable IO)、前回は任意のデータの8ビットパラレル出力を行ってみました。今回は、前回の8ビット出力に加えて、4ビットのパラレル入力を追加してみたいと思います。実験用に外付け回路を追加。でもまだまだPIOアセンブラは数行、極めてシンプルであります。

(末尾に実験に使用したソース類を掲げました。ラズパイPico用のツールチェーンはRaspberry Pi 4 —Raspberry Pi OS 32bit—上のものを使用し、PCからVS Codeでリモート接続して作業しています。なお、今回使用のソース類は前回のもののチョイ直しです。)

実験用の外付け回路

8ビットのパラレル出力、4ビットのパラレル入力を試験するにあたって外付けしたのは、以下のデバイスであります。

東芝デバイス&ストレージ社 TC74HC00AP

お馴染み、標準ロジックIC 74HC00 です。2入力のNAND回路を4回路搭載。14ピンのICです。接続図を先頭のアイキャッチ画像に貼り付けました。

  • ラズパイPicoのGP2からGP9までの8本をNANDゲートの入力に接続
  • NANDゲートの出力4本をラズパイPicoのGP10からGP13に接続

という具合。GP2からの8本にデータを出力すれば下から2ビットづつのNANDを取った結果がGP10からの4本に返ってきます。回路は極めて簡単、分かり易い思うのですが、いまどき流行らないパラレル接続、たった8ビットでも配線多いな。波形をとるための準備もあって、実際は配線類が醜いです。こんな感じ。
PIO002DUT_Photo

PIOアセンブラ本体部分

ソース全文を末尾に掲げましたが、肝心のPIO命令部分は以下だけです。

.wrap_target
    pull    block
    out     pins, 8 [8]
    in      pins, 4
    push    block
.wrap
  1. pull block でCPUからTx FIFOに書き込まれたデータをOSRに読み出す。FIFOが空なら待つ。なお、デフォルトでブロッキングだが、ここでは明示的に block と指示。
  2. out pins, 8 [8] で出力用にマップされているpins(端子)にOSRから8ビット分のデータを出力する。そのあと8サイクルの「待ち」を入れる。
  3. in pins, 4 で入力用のマップされているpins(端子)からISRより4ビット分のデータを入力する
  4. push block でRx FIFOにISRの内容を書き出す。FIFOが満杯なら待つ。なお、デフォルトでブロッキングだが、ここでは明示的に block と指示。

折角のPIOなのですが、この処理自体はCPUがFIFOを読み書きしてくれるタイミング依存です。意味的にはソフトウエアでGPIO操作をするのと大差ありません。CPUのタイミングに依存しないコードはまた次回かな。

PIOの初期化関数
static inline void pio002_program_init(PIO pio, uint sm, uint offset, uint opin, uint ipin)
{
    pio_sm_config c = pio002_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_in_pins(&c, ipin);
    sm_config_set_in_shift(&c, false, false, 0); // shift_left, no-autopush
    for (uint pidx=opin; pidx < (opin + 8); pidx++) {
        pio_gpio_init(pio, pidx);
    }
    for (uint pidx=ipin; pidx < (ipin + 4); pidx++) {
        pio_gpio_init(pio, pidx);
        gpio_pull_up(pidx);
    }
    pio_sm_set_consecutive_pindirs(pio, sm, opin, 8, true);
    pio_sm_set_consecutive_pindirs(pio, sm, ipin, 4, false);
    sm_config_set_clkdiv(&c, 128);
    pio_sm_init(pio, sm, offset, &c);
    pio_sm_set_enabled(pio, sm, true);
}

前回からすると少し行数増えました。MicroPythonだとこのあたりを1行で指定できるので楽なのですが、C/C++では個別に設定しないとなりませぬ、メンドイ。入出力の端子を設定するのが主な作業です。今回、ツボにハマったのが、入出力の「シフト」の指定です。FIFOへのPUSH/POPは符号なし32ビット値ですが、今回とりあつかっているのは出力8ビット、入力4ビットです。LSBが右にあるとして、それら入出力の値は右詰めでおきたいです。

  • 出力の場合は、OSRに右詰でおいて右シフトすれば、GP2から順番にデータがアサインできました。
  • 入力の場合は、ISRに左シフトで読み込めば、右詰めデータになり、そのときLSBがGP10になりました。

バカなので、最初、ぼんやりと入出力とも右シフト指定してしまったので、そのときは入力ビットがMSB側に入ってしまいました。下のビットをいくら見てもゼロ入力、おかしい入力できてないとアセリました。ピンとMSB、LSBの対応関係をちゃんと確かめろよ、自分。

なお、上記コードではpio_gpio_init()関数で出力だけでなく、入力端子までPIOに向けていますが、ドキュメントを読む限り入力時の指定は不要です。ここは念のため。また入力端子にプルアップまで指定してますが、外付け回路は7400なので不必要です。これまた惰性で念のため。

実行結果

stdioに入出力をダンプした結果がこちら。testPatternの方が出力した8ビット値(HEX)、Resultの方が入力した4ビット値(HEX下一桁有効。)

Start PIO002.
testPattern: 00
Result: 0f
testPattern: 01
Result: 0f
testPattern: 02
Result: 0f
testPattern: 03
Result: 0e
testPattern: 04
Result: 0f
~途中略~
testPattern: fe
Result: 01
testPattern: ff
Result: 00
End of Execution.

最初のところオール0出力で、結果は f と入力オール1、期待と一致。testPattern: 03にて、ようやく下2ビット出力0b11となったところで、Result: eと下1ビット入力0となりました。ちゃんとNAND動作してるようです。当然か。

一応、パラレル入出力できましたが、今回のPIO SMはCPUを待って動いているので、PIOのご利益があまりないです。次はPIOを勝手に走らせて、CPUのタイミングには依存しないようにしてみたいと思います。

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

鳥なき里のマイコン屋(137) ラズパイ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)

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

pico_set_program_name(pio002 "pio002")
pico_set_program_version(pio002 "0.1")

pico_enable_stdio_uart(pio002 1)
pico_enable_stdio_usb(pio002 0)

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

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

# Add any user requested libraries
target_link_libraries(pio002
        hardware_pio
        )

pico_add_extra_outputs(pio002)
実験に使用した PIO アセンブラソース
; PIO002
; GPIO output/input test
.program pio002

.wrap_target
    pull    block
    out     pins, 8 [8]
    in      pins, 4
    push    block
.wrap

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

static inline void pio002_program_init(PIO pio, uint sm, uint offset, uint opin, uint ipin)
{
    pio_sm_config c = pio002_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_in_pins(&c, ipin);
    sm_config_set_in_shift(&c, false, false, 0); // shift_left, no-autopush
    for (uint pidx=opin; pidx < (opin + 8); pidx++) {
        pio_gpio_init(pio, pidx);
    }
    for (uint pidx=ipin; pidx < (ipin + 4); pidx++) {
        pio_gpio_init(pio, pidx);
        gpio_pull_up(pidx);
    }
    pio_sm_set_consecutive_pindirs(pio, sm, opin, 8, true);
    pio_sm_set_consecutive_pindirs(pio, sm, ipin, 4, false);
    sm_config_set_clkdiv(&c, 128);
    pio_sm_init(pio, sm, offset, &c);
    pio_sm_set_enabled(pio, sm, true);
}

static inline void pio002_program_stop(PIO pio, uint sm)
{
    pio_sm_set_enabled(pio, sm, false);
}
%}
実験に使用したCソースコード
#include <stdio.h>
#include "pico/stdlib.h"
#include "hardware/gpio.h"
#include "hardware/pio.h"
#include "pio002.pio.h"

#define OPINS   (2)
#define IPINS   (10)

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

    stdio_init_all();         
    printf("Start PIO002.\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, &pio002_program);
    pio002_program_init(pio, sm, offset, OPINS, IPINS);
    while(testPat < 256) {
        printf("testPattern: %02x\r\n", testPat);
        pio_sm_put(pio, sm, testPat++);
        result = pio_sm_get_blocking(pio, sm);
        printf("Result: %02x\r\n", (result & 0xFF));
        sleep_ms(2000);
    }
    pio002_program_stop(pio, sm);
    printf("End of Execution.\r\n");
    return 0;
}