Pico三昧(13) Pico C/C++SDKで4.096MHzクロック生成その2PIO

Joseph Halfmoon

前回PWMで生成したクロックで十分かな~と思ってしまったので、もう一度似たようなことをやるのはカッタるいです。でも自分でPIO(プログラマブルIO)でもクロック作ってみると書いてしまったのでやらないと。大分ラズパイPicoのPIOにも慣れたんですが、PIOアセンブラでたった2行。けれどヘッダ部分書くのがメンドイのよね。

PIOでのFractional分周器の設定

前回PWMを使ってクロック生成したときは、周波数の計算のパラメータが多いので、いくつにしたらよいかチト迷いました。今回PIO(プログラマブルIO)アセンブラでの場合は、ひたすら1と0を繰り返すだけのパターン、2分周器としてSMを動かすので、Fractional分周器自体は

4.096×2=8.192MHz

が生成できればよいです。PIOのFractional分周器は小数点以下8ビットもあるゴージャスなもの(PWMは小数点以下4ビット)なので選択はたやすいです。ラズパイPicoの sysclk=125MHzに対して小数点以下は66/256=0.2578125で

125 / 15.2578125 ≒ 8.1925 MHz

と「かなり」近い値となります。周波数の計算はPIOの方が楽。

試作ソース

まずは、ビルドに使う CMakeLists.txt から。いつものように自動生成のCMakeLists.txtをベースとしますが、PIOを使用する場合、毎度書いているように

pico_generate_pio_header

のところは、手動で追加しないとPIOアセンブラのソースからヘッダを自動生成してくれません。メンドイ。

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

pico_set_program_name(clkGenPIO "clkGenPIO")
pico_set_program_version(clkGenPIO "0.1")

pico_enable_stdio_uart(clkGenPIO 1)
pico_enable_stdio_usb(clkGenPIO 0)

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

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

# Add any user requested libraries
target_link_libraries(clkGenPIO
        hardware_pio
        )

pico_add_extra_outputs(clkGenPIO)

続いてPIOアセンブラのソースです。以下のコードをご覧頂いたら分かるとおり、アセンブラ、たったの2行です。それも NOP。NOPループからクロックが紡ぎだされるのは side 出力のお陰です。ただ1と0を繰り返すだけ。

しかし、例によってPIOアセンブラを呼び出すヘッダファイル部分がメンドイです。特に今回気をつけないとならないのは、ここにFractional分周器の設定があることです。以下のところであります。

sm_config_set_clkdiv_int_frac

ここに整数部分15, 小数部分66と「ハードコード」してしまっています。こんな感じ。

; 4.096MHz clock generation by PIO
; spin CLK_OUT(16) 

.program clkGenPIO
.side_set   1

.wrap_target
    nop     side    1
    nop     side    0
.wrap

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

static inline void clkGenPIO_program_init(PIO pio, uint sm, uint offset, uint spin)
{
    pio_sm_config c = clkGenPIO_program_get_default_config(offset);
    sm_config_set_sideset_pins(&c, spin);
    pio_gpio_init(pio, spin);
    pio_sm_set_consecutive_pindirs(pio, sm, spin, 1, true);
    sm_config_set_clkdiv_int_frac(&c, 15, 66);
    pio_sm_init(pio, sm, offset, &c);
    pio_sm_set_enabled(pio, sm, true);
}

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

最後にCのmain関数部分です。「伝統的なLチカ」Cコードの前でPIOアセンブラの上記コードを呼び出しています。上記はPIOステートマシンの初期化さえ済ませれば勝手に走りつづけます。initした後はCPUは何もすることがありません(それでLチカしてます、ハートビート信号的な?)

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

#define CLK_OUT     (16)
#define LED_PIN     (25)

int main()
{
    stdio_init_all();
    puts("Generate 4.096MHz clock by PIO. v001");
    PIO pio = pio0;
    uint sm = pio_claim_unused_sm(pio, true);
    uint offset = pio_add_program(pio, &clkGenPIO_program);
    clkGenPIO_program_init(pio, sm, offset, CLK_OUT);
    gpio_init(LED_PIN);
    gpio_set_dir(LED_PIN, GPIO_OUT);
    while (true) {
        gpio_put(LED_PIN, 1);
        sleep_ms(1000);
        gpio_put(LED_PIN, 0);
        sleep_ms(1000);
    }
    puts("End of Execution. <NEVER!>");
    return 0;
}
実機動作の確認

端子にオシロ(Digilent Analog Discovery2)をつけて観察した時間波形が以下に、周波数の測定機能を使って測定した周波数は4.0938MHzとな。

前回、クロックの品質が気になるぜ、ということでFFTかけてみました。こんな感じ。

PIO_FFT

これに対して、前回のPWMで生成したクロックにFFTかけるとこんな感じ。

PWM_FFT

たまたまなのかも知れないけれど、前回のPWMで生成したクロックの方が「横の邪魔者」が抑止されているんでないかい。ホントか。

大した用途でないから、どちらでも構わんですか。まあ実際に使ってみてOKなら良しといたしましょう。今度は実験の回路作るのがまたメンドイ。

Pico三昧(12) Pico C/C++SDKで4.096MHzクロック生成その1 PWM へ戻る

Pico三昧(14) PicoでもGo! TinyGoのオブジェクトがPico上で走るまで へ進む