pico-sdk使用してLCD表示(SPIバス)

LCDの動作確認のためにArduino IDEで動作確認(受入試験みたいなもので以下のリンク)しましたが、Cのライブラリ使わないとスペアナ計算と結合できないのでpico-sdk使ってやってみます

https://isehara-3lv.sakura.ne.jp/blog/2025/06/20/ラズピコでspiバス経由でlcd表示させる/

ライブラリは使い方がシンプルなので構造が単純な以下を使います

https://github.com/Imel23/lcd-st7789-library

ライブラリを使う時には、ハードワイヤリングの設定をソース上で行うこととSPIの動作モードを指定しないと動作しなかった(以下変更部分のコード)、それ以外には画面ローテーションの指定もしています、この記事の最後の方に記載

SPI clockは62.5MHzのままだと動作に不安があるので20MHzまで落としてます、どうせ62.5MHzでも最初の電源オンのチラつき(データが初期状態では不定だから)はあるから、バックライト信号の制御がいずれ必要

// Pin definitions for the LCD connection
//#define BL_PIN 13 // Backlight pin
#define DC_PIN 20   // Data/Command pin
#define RST_PIN 21  // Reset pin
#define MOSI_PIN 19 // SPI MOSI pin
#define SCK_PIN 18  // SPI Clock pin
#define CS_PIN 17   // Chip Select pin

// SPI configuration
#define SPI_PORT spi0
#define SPI_BAUDRATE 2000000  // 62.5 MHz SPI clock

// Function prototypes for internal use
static void lcd_write_command(uint8_t cmd);
static void lcd_write_data(uint8_t data);
static void lcd_set_window(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2);

// Initialize the LCD
void lcd_init() {
    // Initialize SPI
    spi_init(SPI_PORT, SPI_BAUDRATE);
    spi_set_format(SPI_PORT, 8, SPI_CPOL_1, SPI_CPHA_1, SPI_MSB_FIRST);     // to add this line

ライブラリ用にcmakeファイルに設定を追加して、現状の中身は、

# Generated Cmake Pico project file

cmake_minimum_required(VERSION 3.13)

set(CMAKE_C_STANDARD 11)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)

# Initialise pico_sdk from installed location
# (note this can come from environment, CMake cache etc)

# == DO NOT EDIT THE FOLLOWING LINES for the Raspberry Pi Pico VS Code Extension to work ==
if(WIN32)
    set(USERHOME $ENV{USERPROFILE})
else()
    set(USERHOME $ENV{HOME})
endif()
set(sdkVersion 2.1.1)
set(toolchainVersion 14_2_Rel1)
set(picotoolVersion 2.1.1)
#set(picoVscode ${USERHOME}/.pico-sdk/cmake/pico-vscode.cmake)
#if (EXISTS ${picoVscode})
#    include(${picoVscode})
#endif()
# ====================================================================================
set(PICO_BOARD pico2_w CACHE STRING "Board type")

set(PICO_SDK_PATH "$ENV{HOME}/pi/pico/pico-sdk")
#set(PICO_SDK_PATH "/Users/usamiryuuichi/pi/pico/pico-sdk")


# Pull in Raspberry Pi Pico SDK (must be before project)
include(pico_sdk_import.cmake)

project(dsp C CXX ASM)

# Initialise the Raspberry Pi Pico SDK
pico_sdk_init()

# Create a library for the LCD driver
add_library(lcd_driver STATIC
    lcd_st7789_library.c
    font_5x7.c
)

add_library(CMSISDSP STATIC IMPORTED GLOBAL)
set_target_properties(CMSISDSP PROPERTIES IMPORTED_LOCATION
  $ENV{HOME}/pi/pico/CMSISDSP/build/bin_dsp/libCMSISDSP.a)

#target_link_libraries(dsp CMSISDSP ...)

# Add executable. Default name is the project name, version 0.1

add_executable(dsp dsp.c )

pico_set_program_name(dsp "dsp")
pico_set_program_version(dsp "0.1")

# Modify the below lines to enable/disable output over UART/USB
pico_enable_stdio_uart(dsp 0)
pico_enable_stdio_usb(dsp 1)

# Add the standard library to the build
target_link_libraries(dsp
        pico_stdlib
        pico_multicore
        hardware_adc
        hardware_dma
        hardware_pio
        hardware_pwm
        hardware_spi
        CMSISDSP
        lcd_driver
        hardware_timer 
        pico_multicore
)

target_link_libraries(lcd_driver
    pico_stdlib
    hardware_spi
)

# Add the standard include files to the build
target_include_directories(dsp PRIVATE
        ${CMAKE_CURRENT_LIST_DIR}
        $ENV{HOME}/pi/pico/CMSISDSP/CMSIS-DSP/Include
        $ENV{HOME}/pi/pico/CMSISDSP/CMSIS_6/CMSIS/Core/Include
        ${PICO_SDK_PATH}/src/rp2
)

target_include_directories(lcd_driver PRIVATE
    ${CMAKE_CURRENT_LIST_DIR}
    ${PICO_SDK_PATH}/src/common
    ${PICO_SDK_PATH}/src/rp2
)

pico_add_extra_outputs(dsp)

ディレクトリ構成は、

スペアナのコードに追加したのは、動作確認のためだけですが、

// core1 will be used for LCD display control
#include "pico/stdlib.h"
#include "lcd_st7789_library.h"
#include "hardware/spi.h"

void core1_main()
{
    // Initialize the LCD
    lcd_init();

    // Fill the screen
    lcd_fill_color(create_color(255, 255, 255));

    // Draw a rectangle
    lcd_draw_rect(10, 10, 100, 50, create_color(0, 0, 255));

    // Draw some text
    lcd_draw_text(20, 20, "Hello, World!", create_color(255, 0, 0), create_color(255, 255, 255), 1); // char color, bg color, font size

    while (1)
    {

    }
}

実行結果は、

ローテーション指定はライブラリ中のここで、関数もあるようですが関数で後から指定すると表示が二重になった

    lcd_write_command(0x36);  // Memory Data Access Control
    lcd_write_data(0xA0);     // display rotation

 

admin

ラズピコでspiバス経由でLCD表示させる

スペアナプロジェクト用ですが、2インチのLCD(240*320 dots)を購入、SDカードのアクセスもできるようになってますが

https://learn.adafruit.com/2-0-inch-320-x-240-color-ips-tft-display/pinouts

動作確認はArduino IDE使って、ライブラリは、

をインストします

接続は以下のソースコードの通りですが、電源はピコの3.3V出力から取ろうとすると過電流(おそらくピコの電源電圧低下して動作不能状態になってる)でUF2ファイルのダウンロードできないので、USB電源であるVBUSから取らないと機能しなかった

電源電圧とバックライト制御信号(PWM)をまとめてVBUSに接続しています、従ってバックライトの輝度はmax状態ということ、GNDは一本なのでピコ – LCD間の接続は全部で8本です

<テスト用コード>

#include <adafruit_gfx.h>
#include <adafruit_st7789.h>
#include <spi.h>

// ピン定義(PicoのGPIO番号)
#define TFT_CS    17  // pin 22
#define TFT_RST   21  // pin 27
#define TFT_DC    20  // pin 26
#define TFT_SCK   18  // pin 24
#define TFT_MOSI  19  // pin 25

SPISettings settings(2000000, MSBFIRST, SPI_MODE3);
Adafruit_ST7789 tft = Adafruit_ST7789(&SPI, TFT_CS, TFT_DC, TFT_RST);

void setup() {
  tft.init(240, 320, SPI_MODE3);
  delay(100);

  tft.fillScreen(ST77XX_WHITE);   // 背景を白に設定
  tft.setRotation(1);             // 画面を90度回転
  tft.setCursor(20, 100);         // 文字の表示位置を設定
  tft.setTextColor(ST77XX_BLUE);
  tft.setTextSize(3);             // 文字サイズを設定
  tft.print("Hello Pico");       // 文字の表示
}

void loop() {
  delay(1000);
}

ST7789は制御回路にフレームメモリを持っているので、ラズピコからのリフレッシュ動作は不要なので使いやすいと思う、まあラズピコでリフレッシュさせたら負荷も大変だろうから、こういう制御チップがあるということだろう

 

admin

ラズピコ2でスペアナ(基本ロジック)

pico-sdkとCMSIS-DSPを使って作る、ラズピコ2で動かすスペアナプロジェクトの基本ロジックを作ってみた

・ADCはDMA転送を使う、割り込み処理の中でFFT

大雑把にはそうだけども、実はラズピコのADCをDMAモードで動かすとクロックデバイドが効かない、つまり常におよそ500KHzサンプルモードになってしまう、対応方法は500KHzでサンプル(256*10個)した後にIIRの一次フィルタ処理、その後に10個ごとのデータを使えば50KHzサンプル相当になる

・窓はHanning窓を使う

おそらく一般的だろうし、

・テスト用の信号はPWM機能を使う

picoの特徴としてsm(state machine)とPWM機能、どちらもCPUの介在なしで信号発生ができる、があるからPWM使って方形波を作成してADCへの入力とする

<FFTの実行結果>

デバッガからの読み出し値をプロットしてみた、奇数の高調波がきちんと検出できている

<現状のコード:WordPressのテキスト機能で貼り付けると崩れるので本文にしてます>

ADCのサンプリングとFFTは一回だけで打ち止め、デバッグ用にですが


/* See https://m0agx.eu/practical-fft-on-microcontrollers-using-cmsis-dsp.html */
//
// DMA transfer & interrupt handling version
//
#include <math.h>
#include <stdio.h>

#include “arm_math.h”
#include “pico/stdlib.h”
#include “pico/time.h”
#include “hardware/sync.h”

// For ADC input & IRQ:
#include “hardware/adc.h”
#include “hardware/dma.h”
#include “hardware/irq.h”

// use multi core
#include “pico/multicore.h”
#include “hardware/pio.h”
#include “hardware/pwm.h”

void core1_main();

#define FFT_SIZE 256

// Channel 0 is GPIO26 for ADC sampling
#define CAPTURE_CHANNEL 0

int dma_chan;

uint32_t start_time;
uint32_t end_time;

q15_t fft_output[FFT_SIZE * 2]; // 出力(複素数 interleaved)
q15_t mag_squared[FFT_SIZE]; // パワースペクトル(Q13形式)

q15_t hann_window[FFT_SIZE];

arm_rfft_instance_q15 fft_instance;

#define RAW_SAMPLES 2560
#define DOWNSAMPLED 256
#define DECIMATE_N 10

uint16_t capture_buf[RAW_SAMPLES];
q15_t filtered_downsampled[DOWNSAMPLED];

static inline q15_t lowpass_filter_q15(q15_t input, q15_t prev, q15_t alpha)
{
int32_t one_minus_alpha = 32768 – alpha; // Q15で (1 – α)
int32_t filtered = ((int32_t)input * alpha + (int32_t)prev * one_minus_alpha) >> 15;
return (q15_t)__SSAT(filtered, 16);
}

void filter_and_downsample()
{
q15_t prev = 0; // IIRの初期値
q15_t alpha = 8192; // Q15形式の係数(約 0.25)

int down_idx = 0;

for (int i = 0; i < RAW_SAMPLES; i++)
{
// ADC raw は 12bit(0~4095)想定 → 中心化&スケーリング
int32_t centered = (int32_t)capture_buf[i] – 2048;
q15_t sample = (q15_t)__SSAT(centered << 3, 16); // ≒ Q15スケーリング Clipping would not happen in this case

// IIR フィルタ適用
prev = lowpass_filter_q15(sample, prev, alpha);

// N点ごとに出力へ保存
if ((i % DECIMATE_N) == 0 && down_idx < DOWNSAMPLED)
{
filtered_downsampled[down_idx++] = prev;
}
}
}

// FFT & Power calc
void perform_fft_and_power_spectrum(arm_rfft_instance_q15 *instance, q15_t *input, q15_t *output, q15_t *power_spectrum)
{
arm_rfft_q15(instance, input, output);
arm_cmplx_mag_squared_q15(output, power_spectrum, FFT_SIZE);
}

// ADC DMA transfer set up
void adc_dma_init()
{
// Init GPIO for analogue use: hi-Z, no pulls, disable digital input buffer.
adc_gpio_init(26 + CAPTURE_CHANNEL);

adc_init();
adc_select_input(CAPTURE_CHANNEL);
adc_fifo_setup(
true, // Write each completed conversion to the sample FIFO
true, // Enable DMA data request (DREQ)
1, // DREQ (and IRQ) asserted when at least 1 sample present
false, // We won’t see the ERR bit because of 8 bit reads; disable.
false // not Shift each sample to 8 bits when pushing to FIFO
);

adc_set_clkdiv(1.0f);
sleep_ms(1000);
// Set up the DMA to start transferring data as soon as it appears in FIFO
dma_chan = dma_claim_unused_channel(true);
dma_channel_config cfg = dma_channel_get_default_config(dma_chan);

// Reading from constant address, writing to incrementing byte addresses
channel_config_set_transfer_data_size(&cfg, DMA_SIZE_16);
channel_config_set_read_increment(&cfg, false);
channel_config_set_write_increment(&cfg, true);

// Pace transfers based on availability of ADC samples
channel_config_set_dreq(&cfg, DREQ_ADC);

dma_channel_configure(dma_chan, &cfg,
capture_buf, // dst
&adc_hw->fifo, // src
RAW_SAMPLES, // transfer count
false // start when triggered
);
dma_channel_set_irq0_enabled(dma_chan, true);
adc_run(true);
}

// to apply Hann window & call FFT/Power calc
void fft_exec()
{
// q15_t input[FFT_SIZE];
q15_t windowed_input[FFT_SIZE];

float hann_correction = 1.0f / 0.5f; // ハニング窓で約0.5倍になる補正

start_time = time_us_32();

for (int n = 0; n < FFT_SIZE; n++)
{
int32_t val = filtered_downsampled[n] * hann_window[n];
windowed_input[n] = (q15_t)(val >> 15);
}

perform_fft_and_power_spectrum(&fft_instance, windowed_input, fft_output, mag_squared);

end_time = time_us_32();

//printf(“Execution time: %.2f us per FFT\n”, (float)(end_time – start_time));

float q13_to_float = 1.0f / 8192.0f; // Q13 → float

for (uint32_t j = 0; j <= FFT_SIZE / 2; j++)
{
float mag_q13 = (float)mag_squared[j] * q13_to_float;
float mag_corr = mag_q13 * hann_correction; // Hanning補正(約2倍)

float voltage_rms = sqrtf(mag_corr);
float db = 20.0f * log10f(voltage_rms + 1e-6f);
printf(“Bin %3u: %.2f dB\n”, j, db);
}
}

// IRQ handler
void dma_handler()
{
// stop ADC sampling
adc_run(false);

// Clear the interrupt request.
dma_hw->ints0 = 1u << dma_chan;

filter_and_downsample();

// Give the channel a new wave table entry to read from, and re-trigger it
dma_channel_set_read_addr(dma_chan, &adc_hw->fifo, false);
dma_channel_set_write_addr(dma_chan, capture_buf, false);
dma_channel_set_trans_count(dma_chan, RAW_SAMPLES, false);
dma_channel_start(dma_chan);
// adc_run(true); // make it oneshot only for debug purpose
// exec FFT process in parallel with ADC DMA transfer
fft_exec();
}

#define OUTPUT_PIN 2

void setup_pwm()
{
gpio_set_function(OUTPUT_PIN, GPIO_FUNC_PWM);
uint slice_num = pwm_gpio_to_slice_num(OUTPUT_PIN);

pwm_config config = pwm_get_default_config();
pwm_config_set_clkdiv(&config, 1.0f); // PWM clock = 125 MHz / 1 = 125 MHz

// 周期 = 25,000クロック → 2.5kHz(= 125M / 50.0k)
pwm_config_set_wrap(&config, 49999);
pwm_init(slice_num, &config, true);

// デューティー比 = 50%
pwm_set_gpio_level(OUTPUT_PIN, 25000);
}

int main()
{
stdio_init_all();
sleep_ms(1000);

setup_pwm();

multicore_launch_core1(core1_main);

// initialize the ADC buffer
adc_dma_init();

// to prepare Hanning window coefficient
for (int n = 0; n < FFT_SIZE; n++)
{
float hann = 0.5f * (1.0f – cosf(2.0f * M_PI * n / (FFT_SIZE – 1)));
hann_window[n] = (q15_t)(hann * 32767.0f);
}
// initialise FFT instance
arm_status status = arm_rfft_init_q15(&fft_instance, FFT_SIZE, 0, 1);

// to register IRQ handler and enable it
irq_set_exclusive_handler(DMA_IRQ_0, dma_handler);
irq_set_enabled(DMA_IRQ_0, true);

// first ADC sampling start
adc_run(false);

dma_channel_start(dma_chan);
adc_run(true);

// wait forever
__wfi();

//__BKPT(1);
}

// —————————————————————————-
// core1 will be used for LCD display control

void core1_main()
{
}


表示部分(ミニLCD)の検討しないと、信号の前処理(最小限オペアンプでの処理)も必要ですが、

 

admin

窓処理と計算方法の見直し(CMSIS-DSP)

現実には窓関数を使うから、窓をかけるのと計算方法(元のソースは最後の出力配列作成時のq15の扱い方が変)だったので処理方法を見直して、対数表示でも違和感ないように見直し

<コードの波形部分以外の全部>

/* See https://m0agx.eu/practical-fft-on-microcontrollers-using-cmsis-dsp.html */

#include <math.h>
#include <ltstdio.h>

#include "arm_math.h"
#include "pico/stdlib.h"
#include "pico/time.h"

unsigned char __697hz_raw[] = {// データ部分は省略};

unsigned int __697hz_raw_len = 512;

#define FFT_SIZE 256

// バッファ宣言
q15_t input_signal[FFT_SIZE];   // 入力(実数信号)
q15_t fft_output[FFT_SIZE * 2]; // 出力(複素数 interleaved)
q15_t mag_squared[FFT_SIZE];    // パワースペクトル(Q13形式)

void perform_fft_and_power_spectrum(arm_rfft_instance_q15 *instance, q15_t *input, q15_t *output, q15_t *power_spectrum)
{
    arm_rfft_q15(instance, input, output);
    arm_cmplx_mag_squared_q15(output, power_spectrum, FFT_SIZE);
}

int main()
{
    stdio_init_all();
    sleep_ms(1000);

    q15_t *input = (q15_t *)__697hz_raw;
    q15_t windowed_input[FFT_SIZE];

    float scaling_factor = 1.0f / 8192.0; // Q13 → float
    float hann_correction = 1.0f / 0.5f;  // ハニング窓で約0.5倍になる補正

    // ハニング窓
    for (int n = 0; n < FFT_SIZE; n++) { float hann = 0.5f * (1.0f - cosf(2.0f * M_PI * n / (FFT_SIZE - 1))); q15_t hann_q15 = (q15_t)(hann * 32767.0f); int32_t val = (int32_t)input[n] * hann_q15; windowed_input[n] = (q15_t)(val >> 15);
    }

    arm_rfft_instance_q15 fft_instance;
    arm_status status = arm_rfft_init_q15(&fft_instance, FFT_SIZE, 0, 1);
    printf("FFT init %d\n", status);

    uint32_t start_time = time_us_32();

    perform_fft_and_power_spectrum(&fft_instance, windowed_input, fft_output, mag_squared);

    uint32_t end_time = time_us_32();

    printf("Execution time: %.2f us per FFT\n", (float)(end_time - start_time));

    for (uint32_t j = 0; j <= FFT_SIZE / 2; j++)
    {
        float mag = mag_squared[j] * scaling_factor * hann_correction;
        float db = 10.0f * log10f(mag + 1e-12f); // 0対策のオフセット付き
        printf("Bin %3u: %.2f dB\n", j, db);
    }

    printf("\n");
    __BKPT(1);
}

<結果>

窓はHanningが適切っぽいし、フレーム数も256で簡易表示なら速度的にも速いから良さそうだ

ADCはノイジーだけど内蔵の12 bitsでやってみるのがPoCには適切だろうと思う

 

admin

ラズピコ2でのFFT実行時間(CMSIS-DSP)

以下の記事のCMSIS-DSPの実行時間を測定してみた

https://isehara-3lv.sakura.ne.jp/blog/2025/06/04/cmsis-dspをラズピコ2で使う/

該当部分だけのコードは、

    uint32_t start_time = time_us_32();
    for (int i = 0; i < ITERATIONS; ++i) {
        arm_rfft_q15(&fft_instance, input, output);
    }
    uint32_t end_time = time_us_32();

    printf("\nExecution time: %.2f us per FFT\n", 
          (float)(end_time - start_time) / ITERATIONS);

ITERATIONSを2以上にすると結果が変になる

つまり、inputがワークとして使われて破壊されてしまうようだ、一回だけ実行させた時の計算時間は、

Execution time: 392.00 us per FFT/

この数値、

https://github.com/jptrainor/cmsis-sandbox

にある、

fft execution time (us)
               32     64    128    256
 clean_q15    259    331    511    869

の256フレーム時の869μsと比較すると半分以下だから、それだけ高速化されているということ

但し、リンク先の情報でもわかるようにデータによって計算時間は変わるようで、clean(つまり単純正弦波)とnoizy(ノイズ重畳)ではかなり時間差ありますが、今回は正弦波だから、この869μsと比較するのが妥当

ともかくも256フレームでこの程度の実行時間であれば、小型のLCD(320*240)に表示する前提ならば問題ないレベルでしょう

 

admin

pico-sdkでADCからのDMA転送とADC精度

VScodeのラズピコエクステンション(Raspberry Pi Pico)にいくつかのサンプルコードがあるのでその中から内蔵ADCからDMA転送するコードがあるので、ついでにADCの精度も見てみた

<環境>

M4 MacBook, VScode、ラズピコエクステンション(Raspberry Pi Pico)使用

<変更箇所>

・結果を4bitシフトで8bitにしてるのを12bit獲得する

・ADC読み出し値の最大値と平均値を求める処理追加(GPIO26はGND接続)

・USBシリアルを使えるようにCMakeに追加(但し動作がイマイチ不安定、予想外の時に出力されて、期待した時は出力されない、なのでデバッガに期待)

・コンパイラが最適化すると見れないローカル変数があるので、変数が見れるようにDebugモードにするようにCMakeで設定

<接続>

こんな感じでGNDに落とす、

 

<結果>

予想通り、LSB側の3ビットぐらいはノイズに埋もれてる

 

<全体のコード>

core1側で三角波作って外部のDACで波形作成して、ADCに入力することを想定しているね

DMA処理自体はレジスタ操作とかは不要で、pico-sdkに関数が用意されている

/**
 * Copyright (c) 2021 Raspberry Pi (Trading) Ltd.
 *
 * SPDX-License-Identifier: BSD-3-Clause
 */
//
// ADC values are 4bits shifted, so the max value is 255
//
// -> modify to handle full scale 12bits(2025/6/4)
// it seems noisy, LSB 3bits are inaccrate
// 
#include <stdio.h>
#include "pico/stdlib.h"
// For ADC input:
#include "hardware/adc.h"
#include "hardware/dma.h"
// For resistor DAC output:
#include "pico/multicore.h"
#include "hardware/pio.h"
#include "resistor_dac.pio.h"

// This example uses the DMA to capture many samples from the ADC.
//
// - We are putting the ADC in free-running capture mode at 0.5 Msps
//
// - A DMA channel will be attached to the ADC sample FIFO
//
// - Configure the ADC to right-shift samples to 8 bits of significance, so we
//   can DMA into a byte buffer
//
// This could be extended to use the ADC's round robin feature to sample two
// channels concurrently at 0.25 Msps each.
//
// It would be nice to have some analog samples to measure! This example also
// drives waves out through a 5-bit resistor DAC, as found on the reference
// VGA board. If you have that board, you can take an M-F jumper wire from
// GPIO 26 to the Green pin on the VGA connector (top row, next-but-rightmost
// hole). Or you can ignore that part of the code and connect your own signal
// to the ADC input.

// Channel 0 is GPIO26
#define CAPTURE_CHANNEL 0
#define CAPTURE_DEPTH 1000

uint16_t capture_buf[CAPTURE_DEPTH];


void calc_max_and_avg(const uint16_t *buf, size_t len, uint16_t *max_val, double *avg_val) {
    uint16_t max = 0;
    uint32_t sum = 0;
    for (size_t i = 0; i < len; ++i) { if (buf[i] > max) max = buf[i];
        sum += buf[i];
    }
    *max_val = max;
    *avg_val = (double)sum / len;
}


void core1_main();

int main() {
    stdio_init_all();

    // Send core 1 off to start driving the "DAC" whilst we configure the ADC.
    multicore_launch_core1(core1_main);

    // Init GPIO for analogue use: hi-Z, no pulls, disable digital input buffer.
    adc_gpio_init(26 + CAPTURE_CHANNEL);

    adc_init();
    adc_select_input(CAPTURE_CHANNEL);
    adc_fifo_setup(
        true,    // Write each completed conversion to the sample FIFO
        true,    // Enable DMA data request (DREQ)
        1,       // DREQ (and IRQ) asserted when at least 1 sample present
        false,   // We won't see the ERR bit because of 8 bit reads; disable.
        false    // not Shift each sample to 8 bits when pushing to FIFO
    );

    // Divisor of 0 -> full speed. Free-running capture with the divider is
    // equivalent to pressing the ADC_CS_START_ONCE button once per `div + 1`
    // cycles (div not necessarily an integer). Each conversion takes 96
    // cycles, so in general you want a divider of 0 (hold down the button
    // continuously) or > 95 (take samples less frequently than 96 cycle
    // intervals). This is all timed by the 48 MHz ADC clock.
    adc_set_clkdiv(0);

    printf("Arming DMA\n");
    sleep_ms(1000);
    // Set up the DMA to start transferring data as soon as it appears in FIFO
    uint dma_chan = dma_claim_unused_channel(true);
    dma_channel_config cfg = dma_channel_get_default_config(dma_chan);

    // Reading from constant address, writing to incrementing byte addresses
    channel_config_set_transfer_data_size(&cfg, DMA_SIZE_16);
    channel_config_set_read_increment(&cfg, false);
    channel_config_set_write_increment(&cfg, true);

    // Pace transfers based on availability of ADC samples
    channel_config_set_dreq(&cfg, DREQ_ADC);

    dma_channel_configure(dma_chan, &cfg,
        capture_buf,    // dst
        &adc_hw->fifo,  // src
        CAPTURE_DEPTH,  // transfer count
        true            // start immediately
    );

    printf("Starting capture\n");
    adc_run(true);

    // Once DMA finishes, stop any new conversions from starting, and clean up
    // the FIFO in case the ADC was still mid-conversion.
    dma_channel_wait_for_finish_blocking(dma_chan);
    printf("Capture finished\n");
    adc_run(false);
    adc_fifo_drain();   // max four samples

    // Print samples to stdout so you can display them in pyplot, excel, matlab
    for (int i = 0; i < CAPTURE_DEPTH; ++i) {
        printf("%u, ", capture_buf[i]);
        if (i % 10 == 9)
            printf("\n");
    }

    uint16_t max_value;
    double average;

    calc_max_and_avg(capture_buf, CAPTURE_DEPTH, &max_value, &average);

    printf("Max: %u\n", max_value);
    printf("Average: %f\n", average);
}

// ----------------------------------------------------------------------------
// Code for driving the "DAC" output for us to measure

// Core 1 is just going to sit and drive samples out continuously. PIO provides
// consistent sample frequency.

#define OUTPUT_FREQ_KHZ 5
#define SAMPLE_WIDTH 5
// This is the green channel on the VGA board
#define DAC_PIN_BASE 6

void core1_main() {
    PIO pio = pio0;
    uint sm = pio_claim_unused_sm(pio0, true);
    uint offset = pio_add_program(pio0, &resistor_dac_5bit_program);
    resistor_dac_5bit_program_init(pio0, sm, offset,
        OUTPUT_FREQ_KHZ * 1000 * 2 * (1 << SAMPLE_WIDTH), DAC_PIN_BASE);
    while (true) {
        // Triangle wave
        for (int i = 0; i < (1 << SAMPLE_WIDTH); ++i)
            pio_sm_put_blocking(pio, sm, i);
        for (int i = 0; i < (1 << SAMPLE_WIDTH); ++i)
            pio_sm_put_blocking(pio, sm, (1 << SAMPLE_WIDTH) - 1 - i);
    }
}

 

admin

CMSIS-DSPをラズピコ2で使う

ラズピコ2の特徴の一つである、FPUとGPUを活用するのにちょうど良さそうなのがARM系プロセッサ向けに用意されているCMSIS-DSP、ラズピコのハードにも最適化されているので、例えばArduinoライブラリよりもFFT処理が高速化できるし、MNISTで使っている事例もあります

ラズピコに比較しておよそ5倍程度の高速化というような情報もあります

以下、VScodeでラズピコ2の環境を用意したので、コードからCMSIS-DSPを使えるようにします

https://isehara-3lv.sakura.ne.jp/blog/2025/05/27/ラズピコ2の開発環境vscode-raspberry-pi-pico/

<実行環境>

ラズピコ2W + デバッグボード、M4 MacBookVScode + Raspberry pi pico extension

参考になったのは、やはりRaspberryPI財団のページで、

https://forums.raspberrypi.com/viewtopic.php?t=365053

手順を述べると、

① ラズピコにカスタマイズされたスタティックライブラリを作成する

作成されるのは、libCMSISDSP.aという名称になります

② main.cからは①で作成したスタティックライブラリと、CMSIS関連ヘッダファイル群を呼び出せるようにinclucdeする

1-1 CMSIS-DSPのライブラリを作成するためのディレクトリ構成

ビルドの例は以下の通り、

mkdir build
cd build
cmake ..
make -j4

1-2 サンプルプログラムとCMSIS-DSPライブラリの組み込み

https://forums.raspberrypi.com/viewtopic.php?t=365053

にあるFFTを実行するサンプルプログラムをそのまま使用しました

“arm_math.h”がCMSIS-DSPを使用するための宣言です

VScodeで作成されたCMakeファイルに、以下の🔴部分の3行を追加します

これでRASPBERRY PI PICO PROJECTのCompile ProjectでUF2ファイルが作成されて、Flushすればラズピコ上で動作開始します

③ 実行結果

USBシリアルのポートは二つ見えますが、常時見えているのがデバッガー側、ラズピコ2側は瞬間でしか見えないので要注意、というかデバッガ使わないと見ることはできないと思う

コンソールに印字された結果(256個出てきますが、re/imの組み合わせなので実質128個、それの二乗平均しています)をコピーしてリニア軸で図にすると、

さらに、対数軸で見てみると、

それらしいグラフになっています

 

admin

Sonic PiのOSC制御

Sonic PiにOSC制御のインターフェースがMIDIと一緒に併記されていて、OSCって何のことと思いましたが、Open Sound Controlの略でUDP(速度大事だろうから)で送られる定義された制御インターフェースです

例えばSonic Pi側で以下のように待ち受けしておいて、

live_loop :osc_trigger do
  use_real_time
  note = sync "/osc:127.0.0.1:8001/trigger/synth"
  synth :prophet, note: note[0], release: 1.0
end

Node.jsから以下のようなコードでA~Kのキーを叩いで電文を送信すれば、Sonic Piで一オクターブ(半音なし)の合成をします、値はMIDIに相当する値ですが他のルールに則っても良いらしい

同じパソコンからなのでループバックアドレスになってますが、これは無論ネットワーク次第、ポートの4560は固定になってます

// original https://qiita.com/youtoy/items/a158b847b142f0a134a6
//
const osc = require("osc");
const keypress = require("keypress");

var udpPort = new osc.UDPPort({
  localAddress: "0.0.0.0",
  localPort: 8001,
  remoteAddress: "127.0.0.1",
  remotePort: 4560,
  metadata: true,
});

udpPort.on("message", function (oscMsg, timeTag, info) {
  console.log("An OSC message just arrived!", oscMsg);
  console.log("Remote info is: ", info);
});

udpPort.on("ready", function () {
  console.log("ready");

  keypress(process.stdin);
  process.stdin.on("keypress", (ch, key) => {
    if ((key && key.ctrl && key.name === "c") || (key && key.name === "q")) {
      process.exit();
    }
    switch (key.name) {
      case "a":
        sendValue(60, key.name);
        break;
      case "s":
        sendValue(62, key.name);
        break;
      case "d":
        sendValue(64, key.name);
        break;
      case "f":
        sendValue(65, key.name);
        break;
      case "g":
        sendValue(67, key.name);
        break;
      case "h":
        sendValue(69, key.name);
        break;
      case "j":
        sendValue(71, key.name);
        break;
      case "k":
        sendValue(72, key.name);
        break;
      default:
        break;
    }
  });

  process.stdin.setRawMode(true);
  process.stdin.resume();
});

function sendValue(inputValue, inputText) {
  udpPort.send({
    address: "/trigger/synth",
    args: [
      {
        type: "f",
        value: inputValue,
      },
    ],
  });
  console.log(`key.name: ${inputText}`);
}

udpPort.open();

キーボードは演奏家にとって大事だろうから、シンセサイザーとは別に色々なデバイスが選択できるのは良さそうです、MIDIとは共存する立ち位置のようですが

 

admi

ラズピコ2の開発環境(VScode + Raspberry Pi Pico)

Arduino IDEはもっさり感もありますが、機能も不足していて本格的に使おうとするとイマイチ

じゃVScode + PlatformIOどうなのと言うと、ラズパイ財団の関係からラズピコは公式サポートなし、と言うわけなのでラズパイ財団おすすめのVScodeの拡張機能(Raspberry Pi Pico)を使う、名前そのままですがVScodeの拡張機能で出てくるのでそれをインスト

プロジェクト管理画面から、作成、ビルド、書き込み、デバッグができます

定番のHello worldコードがデフォルトで作成(一部変更しています)されるので、

#include 
#include "pico/stdlib.h"


int main()
{
    stdio_init_all();
    int a = 10;
    while (true) {
        printf("Hello, world! a = %d\n", a);
        sleep_ms(1000);
        a += 1;
    }
}

ビルドしてラズピコ2にデバッガ経由で転送、

シリアル出力の有効化は、

% ls /dev/tty.*
/dev/tty.Bluetooth-Incoming-Port	/dev/tty.usbmodem1202
/dev/tty.debug-console			/dev/tty.usbmodem1301

usbmodemが二個見えてますが、どちらか一方はデバッガー

二つしかないから、交互に選択してみると、

USB-Serialでコンソールに出力されています

デバッガは、

こんな感じで普通に動作、変数aはコンパイラの最適化でprintf文中で使わないと落とされます、デバッガ機能確認用に定義したがprintfで使わないとデバッガで見えない

 

admi

USBインターフェースのSSDアダプタのベンチマーク

USBのSSD(2.5)ケースが不調なので買い替えついでにベンチマーク(AmorphousDiskMark)してみました

<測定条件>

本体はM4 MacBook Pro、①はポートリプリケーター経由USB Type-Aで接続、②、③はType-C本体直結

① USB3.0 SATA SSD用ケースが二台

② USB3.2 SATA用ケースが一台

③ USB3.2 M2.形状Mvne用アダプタが一台

④ MacBook本体内蔵SSD(比較値)

結果

① 二台実施してますが、一台はUSB2.0でしかつながらない、USB Type-Aコネクタを深く挿入するとドライブ認識しないから、緩く挿入だと2.0になる

② ドライブは①の最初のドライブを移設

SATAドライブではこの程度が限界らしい、速度差ほぼないから実はUSB3.0でSATA SSDなら十分かもしれない

③ Mvne SSDだと最大値はほぼ倍になった、でもそれだけかも知れない

④ 本体内蔵は流石に高速、しかしアプリレベルの体感(おそらくボトムの数値 + αがそれに該当するだろうから)ではそれほどのことはない

<測定結果からの結論>

  1. SATA SSDは格段の事情がない限り、もはや使うべきではない
  2. 外部SSDで使うならUSBは3.2程度で体感的な差は頭打ちになるだろう、あえてUSB4.0とかThunderBirdとかの必要性は感じない

 

admin