Raspberry Pi PicoのPIOについて調べてみたので備忘録。
Raspberry Pi Picoはメインプログラムとは別にアセンブラレベルコードを実行することができる。これはメインプログラムとは別に(並行して)動作するプログラムのようだ。基本アセンブラレベルのコードを書くことになる。これはかなり強力な機能で、メインプログラムでタイミングを意識することなく、決まったサイクルでオペレーションを実行できることになる。
その1からその2まで記録。。。
その1ーーー
開発手順
ここではオンボードLEDを点滅するPIOを書いてみる。
- hello.pioを書く。この中にアセンブラレベルのコードを書く。このhello.pioはCヘッダーファイルに変換され、Cプログラムから参照される。
- Cプログラムを書く。
- buildする。
hello.pioディレクトリに以下を配置
+ CMakeLists.txt
+ hello-pio
+ hello.pio
+ hello-pio.c
+ CMakeLists.txt
+ build
buildディレクトリにて以下を実行してbuildする。
$ cmake ..
$ make
以下、実際に書いたコード。
参考になった情報1と情報2。ただし、情報1の通りコードを書いてもLEDはブリンクしない(サイクルが短すぎて点灯しっぱなしに見える)。
hello.pio
.program hello
loop:
;.wrap_target
set pins, 1 [19] ; Turn LED on and wait another 19 cycles
nop [19] ; Wait 20 cycles
nop [19] ; Wait 20 cycles
nop [19] ; Wait 20 cycles
nop [19] ; Wait 20 cycles
set pins, 0 [19] ; Turn LED off and wait another 19 cycles
nop [19] ; Wait 20 cycles
nop [19] ; Wait 20 cycles
nop [19] ; Wait 20 cycles
nop [19] ; Wait 20 cycles
;.wrap
jmp loop
// 上記loopのwrapも同じ結果になる
% c-sdk {
static inline void hello_program_init(PIO pio, uint sm, uint offset, uint pin) {
// Configオブジェクトを定義する
pio_sm_config config = hello_program_get_default_config(offset);
// Configオブジェクトにgpioピンpinを割り当てる
pio_gpio_init(pio, pin);
sm_config_set_set_pins(&config, pin, 1); // pinから1ピンを連続割当、ここでは1ピンのみの割当。
// pinから1ピンを出力に設定。ここでは1ピンのみを設定。
pio_sm_set_consecutive_pindirs(pio, sm, pin, 1, true);
// クロック分割数を決める。divの分だけsysclkがマスクされる。これをやらないとLED ON/OFFが早すぎてLED点灯しっぱなしに見える。
static const float pio_freq = 2000;
float div = (float)clock_get_hz(clk_sys) / pio_freq;
sm_config_set_clkdiv(&config, div);
// 初期化とイネーブ
pio_sm_init(pio, sm, offset, &config);
pio_sm_set_enabled(pio, sm, true);
}
%}
hello-pio.c
#include <stdio.h>
#include <stdbool.h>
#include <pico/stdlib.h>
#include <hardware/pio.h>
#include "hardware/clocks.h" // hello.pioでclk_sysを参照しているので必要
#include <hello.pio.h> // hello.pioから生成されるヘッダーファイルをincludeする
#define LED_BUILTIN 25 // オンボードLEDのGPIO
int main() {
stdio_init_all();
PIO pio = pio0;
uint state_machine_id = 0;
// hello.pioで定義したhelloはhello_programと自動的に命名される。
uint offset = pio_add_program(pio, &hello_program);
hello_program_init(pio, state_machine_id, offset, LED_BUILTIN);
while(1) {
// Cプログラムで何かをやっていたとしても、これとは並行してpioが動作している
}
}
CMakeLists.txt(hello.pioディレクトリ)
cmake_minimum_required(VERSION 3.12)
include(pico_sdk_import.cmake)
#project(pico_examples C CXX ASM)
project(hello-pio C CXX ASM)
set(CMAKE_C_STANDARD 11)
set(CMAKE_CXX_STANDARD 17)
pico_sdk_init()
add_subdirectory(hello-pio)
CMakeLists.txt(hello-pioディレクトリ)
add_executable(hello-pio
hello-pio.c
)
# CMAKE_CURRENT_LIST_DIRはCMAKEによって自動設定される
pico_generate_pio_header(hello-pio ${CMAKE_CURRENT_LIST_DIR}/hello.pio)
target_link_libraries(hello-pio
pico_stdlib
hardware_pio
)
# hexファイルやuf2ファイルを生成する
pico_add_extra_outputs(hello-pio)
hello.pio.h
CMakeLists.txtのpico_generate_pio_header()によって以下のヘッダーファイルが生成される。
// -------------------------------------------------- //
// This file is autogenerated by pioasm; do not edit! //
// -------------------------------------------------- //
#pragma once
#if !PICO_NO_HARDWARE
#include "hardware/pio.h"
#endif
// ----- //
// hello //
// ----- //
#define hello_wrap_target 0
#define hello_wrap 10
static const uint16_t hello_program_instructions[] = {
// .wrap_target
0xf301, // 0: set pins, 1 [19]
0xb342, // 1: nop [19]
0xb342, // 2: nop [19]
0xb342, // 3: nop [19]
0xb342, // 4: nop [19]
0xf300, // 5: set pins, 0 [19]
0xb342, // 6: nop [19]
0xb342, // 7: nop [19]
0xb342, // 8: nop [19]
0xb342, // 9: nop [19]
0x0000, // 10: jmp 0
// .wrap
};
#if !PICO_NO_HARDWARE
static const struct pio_program hello_program = {
.instructions = hello_program_instructions,
.length = 11,
.origin = -1,
};
static inline pio_sm_config hello_program_get_default_config(uint offset) {
pio_sm_config c = pio_get_default_sm_config();
sm_config_set_wrap(&c, offset + hello_wrap_target, offset + hello_wrap);
return c;
}
static inline void hello_program_init(PIO pio, uint sm, uint offset, uint pin) {
pio_sm_config config = hello_program_get_default_config(offset);
pio_gpio_init(pio, pin);
sm_config_set_set_pins(&config, pin, 1);
pio_sm_set_consecutive_pindirs(pio, sm, pin, 1, true);
static const float pio_freq = 2000;
float div = (float)clock_get_hz(clk_sys) / pio_freq;
sm_config_set_clkdiv(&config, div);
pio_sm_init(pio, sm, offset, &config);
pio_sm_set_enabled(pio, sm, true);
}
#endif
その2ーーー
sm_config_set_out_shiftについて
.program hello
.wrap_target
out pins, 1 ; 結果的に1ワード分のデータを右シフトで1ビットずつ出力される。
.wrap
% c-sdk {
static inline void hello1_program_init(PIO pio, uint sm, uint offset, uint pin, float fs) {
pio_sm_config c = pio_dac_program_get_default_config(offset);
// TX FIFOからデータをpullするまでのビットシフト実行指定、右向きに32ビットまでシフトする。
// つまり32ビット全部をpinに順次送り出し、全て送り出したらTX FIFOから自動で新しいデータをpullする。
sm_config_set_out_shift(&c, true, true, 32);
// pinから1ピンをoutに設定する
sm_config_set_out_pins(&c, pin, 1);
// クロックマスク周期を設定する(動作速度を調整する)
sm_config_set_clkdiv(&c, (float)clock_get_hz(clk_sys) / fs);
SDKドキュメントは以下のとおり。
◆ sm_config_set_out_shift()
static void sm_config_set_out_shift |
( |
pio_sm_config * |
c, |
|
|
bool |
shift_right, |
|
|
bool |
autopull, |
|
|
uint |
pull_threshold |
|
) |
|
|
|
inlinestatic |
Setup 'out' shifting parameters in a state machine configuration.
- Parameters
-
c |
Pointer to the configuration structure to modify |
shift_right |
true to shift OSR to right, false to shift OSR to left |
autopull |
whether autopull is enabled |
pull_threshold |
threshold in bits to shift out before auto/conditional re-pulling of the OSR |