« Google Driveへのパソコンフォルダーの同期方法 | トップページ | PICOのPWMをDMAで動作させる方法 »

2022年12月27日 (火)

PICOのPIOについて

Raspberry Pi PicoのPIOについて調べてみたので備忘録。

Raspberry Pi Picoはメインプログラムとは別にアセンブラレベルコードを実行することができる。これはメインプログラムとは別に(並行して)動作するプログラムのようだ。基本アセンブラレベルのコードを書くことになる。これはかなり強力な機能で、メインプログラムでタイミングを意識することなく、決まったサイクルでオペレーションを実行できることになる。

その1からその2まで記録。。。

その1ーーー

開発手順

ここではオンボードLEDを点滅するPIOを書いてみる。

  1. hello.pioを書く。この中にアセンブラレベルのコードを書く。このhello.pioはCヘッダーファイルに変換され、Cプログラムから参照される。
  2. Cプログラムを書く。
  3. 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

« Google Driveへのパソコンフォルダーの同期方法 | トップページ | PICOのPWMをDMAで動作させる方法 »

ラズパイ日記」カテゴリの記事

コメント

コメントを書く

(ウェブ上には掲載しません)

« Google Driveへのパソコンフォルダーの同期方法 | トップページ | PICOのPWMをDMAで動作させる方法 »