Pico三昧(2) Pico C/C++ SDKで74HC595接続、PIO制御編単品

Joseph Halfmoon

前回は、定番の74HC595シフトレジスタをフルソフト制御で扱ってみました。今回はラズパイPicoの特徴であるPIO(プログラマブルIO)を使って制御してみます。既にMicroPythonからPIOによる74HC595の制御はやってみているので、PIOアセンブラはPythonコードからの移植、という感じ。

(ラズパイPico関係 総目次はこちら

ビルド環境については前回説明しているので、前回記事をご参照ください。使用したソース等は本記事の中に全文あります。

PIOを使用するC/C++ SDKのプロジェクトの生成

C/C++ SDKによるラズパイPicoのプロジェクトは、Raspberry Pi  Pico Project Generator を使用させていただきます。これが楽です。GUIモードでの設定はこんな感じ。

projectGenerator
Library Options で PIO interface のところをチェックしておくと PIOを使うつもりで 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(pio595 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(pio595 pio595.c )

pico_set_program_name(pio595 "pio595")
pico_set_program_version(pio595 "0.1")

pico_enable_stdio_uart(pio595 0)
pico_enable_stdio_usb(pio595 1)

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

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

# Add any user requested libraries
target_link_libraries(pio595
        hardware_pio
        )

pico_add_extra_outputs(pio595)

1行手動追加した部分は以下です。

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

上記は、これから記述するPIOアセンブラのソースから生成されるオブジェクト(PIOステートマシンのオブジェクト)をCから呼び出すときに使うヘッダファイルを自動生成するために必要な1行です。

makeが走らないと、必要なツールがセットアップされずヘッダファイルが生成されません。そしてヘッダファイルはPIOアセンブラのソースから生成されます。「とりあえず」テキトーなものでもPIOアセンブラのソースを書いてから(VSCode環境であればCMakeList.txtを変更するとCMake自体は走る筈なので)、make を走らせてヘッダファイルを生成して置いた方が良いかもしれません。ヘッダファイルが無いと、VSCodeからC本体のソースを編集するときに、手助けが無くて長い関数名を書くのに難渋します。

PIOアセンブラのソース

既に以下の投稿で、MicroPythonから74HC595を制御しているので、今回は「実績」のあるコードをMIcroPythonの形式からC/C++SDKの形式に1対1変換する方法をとりました。

MicroPython的午睡(16) ラズパイPico、PIOで74HC595制御、簡単

MicroPythonでは、Pythonの関数としてPIO制御コードを書いてしまえるのでソースファイルを分ける必用もなく、また、面倒なヘッダファイルなどの生成も不要で楽です。その代り、C/C++SDKでは、細かいところまで自分で指定して制御できるじゃないか、と思います。作成したPIO制御コードは以下に。アセンブラというより、半分はCのヘッダファイルへ行くインライン関数ですが。

; PIO595
; 74HC595 output test
; opin SI(18)
; spin SCK(19), RCK(20)

.program pio595
.side_set   2

.wrap_target
    pull    block           side    0
    set     x, 7            side    0
loop:
    out     pins, 1         side    0
    nop                     side    0x1
    nop                     side    0
    jmp     x-- loop        side    0
    nop                     side    0x2
    nop                     side    0
.wrap

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

static inline void pio595_program_init(PIO pio, uint sm, uint offset, uint opin, uint spin)
{
    pio_sm_config c = pio595_program_get_default_config(offset);
    sm_config_set_out_pins(&c, opin, 1);
    sm_config_set_out_shift(&c, true, false, 0); // shift_right, no-autopull
    sm_config_set_sideset_pins(&c, spin);
    pio_gpio_init(pio, opin);
    pio_gpio_init(pio, spin);
    pio_gpio_init(pio, spin+1);
    pio_sm_set_consecutive_pindirs(pio, sm, opin, 1, true);
    pio_sm_set_consecutive_pindirs(pio, sm, spin, 2, true);
    sm_config_set_clkdiv(&c, 128);
    pio_sm_init(pio, sm, offset, &c);
    pio_sm_set_enabled(pio, sm, true);
}

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

このPIOアセンブラのファイル(pio595.pio)を手動追加するところがメンドイです。まあ一度雛形作ってしまえば以下同文式にいけるじゃないかと。

なお、元のPythonコードに則り、シリアルデータとシリアルクロック、出力レジスタへのストアクロックの3線のみPIO制御とし、アウトプット・イネーブルとレジスタクリアはソフト制御に残しています。また、配線の順番はMicroPythonのときと微妙に変わっています(前回に回路図かかげました。)

PIOアセンブラ関数を呼び出すCのコード

以下に上記のPIOアセンブラコードを呼び出し、74HC595のテスト用に0から255までのデータを送って出力させるサンプル・プログラムを掲げます。プログラム本体は、前回使用のCコードを元に、74HC595への8ビットデータの送信部分のみをPIOアセンブラの呼び出しに変更しただけのものです。

なお、PIOアセンブラの呼び出し部分は、以下でやってみています。今回はそこで使ったコードの流用です、これまた。

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

今回のソースは以下に。

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

// 74HC595 PINS
#define SI      (18)
#define SCK     (19)
#define RCK     (20)
#define G_      (21)
#define SCLR_   (22)

void clear595() {
    gpio_put(SCLR_, false); //SCLR_=Low, clear
    sleep_us(1);
    gpio_put(SCLR_, true);
}

void outputEnable595() {
    gpio_put(G_, false); //G_=Low, outputEnable
}

void outputDisable595() {
    gpio_put(G_, true); //G_=High, outputDisable
}

void setup595pio() {
    gpio_init(G_);
    gpio_set_dir(G_, GPIO_OUT);
    outputDisable595();
    gpio_init(SCLR_);
    gpio_set_dir(SCLR_, GPIO_OUT);
    clear595();
}

int main()
{
    uint8_t dat = 0;

    stdio_init_all();
    puts("74HC595 PIO state machine.");
    setup595pio();

    PIO pio = pio0;
    uint sm = pio_claim_unused_sm(pio, true);
    pio_sm_restart(pio, sm);
    uint offset = pio_add_program(pio, &pio595_program);
    pio595_program_init(pio, sm, offset, SI, SCK);
    outputEnable595();

    while (1) {
        pio_sm_put(pio, sm, dat);
        dat = (dat < 255)? ++dat : 0;
        sleep_us(100);
    }

    pio595_program_stop(pio, sm);
    puts("End of Execution.\r\n");
    return 0;
}
実機動作確認

ソースコードの主要部分は流用なので、珍しくほとんどトラブルなくビルド完了、実機に書き込みました。実機動作が以下に。

まずは、PIOアセンブラで制御しているシリアルクロック(黄色のC1)と、シリアルデータ(青色のC2)の動作の様子。

SI_SCK_TIM

PIOステートマシンが端子を操作してシリアル転送してくれているのが確認できました。今回PIOステートマシンのクロックは、128分周もしているのでかなり遅い設定です。PIOステートマシン自体はこの128倍速でも動作可能な筈。そこまで行くと流石に74HC595の方の速度が問題になるかもしれません。まあ、この128分周であれば、マイコンもロジックも余裕のよっちゃん。

74HC595の8ビットの出力端子の様子がこちら。PIOステートマシンに与える8ビットデータはソフトで100μS毎に更新しています。値がその周期で変化しているのが分かります。なお、シフトレジスタの一番遠い方をロジアナ表示のMSBにしてしまったために、ソフトウエア側とはMSBとLSBが逆転した表示になっています。

LOGIC

PIOを使って1個の74HC595を制御できました。次回は複数個の74HC595をPIO制御してみたいと思います。

Pico三昧(1) Pico C/C++ SDKで定番74HC595接続、ソフト制御編 へ戻る

Pico三昧(3) Pico C/C++ SDKで74HC595制御編2並列 へ進む