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 
#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

 

玄関ドアの鍵シリンダ交換とスマートキー化

玄関のシリンダー錠にキーがスッと入らなくなったので、寿命もあるしセキュリティもあるからシリンダ交換

ドアの型格から検索してネットでオーダー

<現物>

事前のブツチェックで分解してたので交換は簡単

<交換後>

シリンダ部分は新品になった

次にスマート化は、

・価格

・複数のキー制御

・アプリの使いやすさ

を考慮してsesameシリーズをオーダー、スマホにアプリインストしてペアリングしてやれば使えます

指紋認証でも解錠できるようにsesame touchもオーダー、mifareカードなどでも認証可能だからホテルのようなカードキー的な使い方もできる

今の所解錠方法は、アプリと指紋認証だけの設定だけど、追加しておくと便利そうなのはPASMO(要はスマホタッチ)かな、もちろん物理キーは非常用に持っておくべき、電池切れや故障時への対応用として

アタッチメントはアジャスタブルになっているのである程度は汎用性ある、但しアタッチメントだけではメイン側のキーは相手先の物理形状の制限で取り付けがネジ2本しか使えないのでイマイチ、機能はしますが

<外観>

タイマー機能で解錠後にある時間経由したら自動で施錠するような設定にしています、Wi-Fiへの接続はつまりネットに交換するということになるからセキュリティ的には脆弱になるだろうからやらない、家の外で鍵の情報見てもねと思うので

 

あとドア開放の時に自動で施錠しないような開放センサーはあったほうが良いかと思い始め

 

admin

ラズピコ2 W

ラズピコ2もサポートが整ってきたと思うので購入(ピンなしでdebug端子含めて後付け)、ラズピコに比較すると例えばIoTなどのセキュアアプリケーション構築には向きそうです

<セットアップ>

Arduino IDE(2.3.5)での設定、w/ debug probe

と、

既存のrp2040用のコードをコンパイル(そのままコンパイル可能)してアップロードすると、

デバッガーも使えました

<性能比較>

HUB75のLEDアレイのスキャンタイム(ラズピコがほぼ14ms:リフレッシュサイクルでおよそ70Hzでラズピコ2が10ms切るぐらい)はラズピコでのRustの実行速度とほぼ同じだから、ハードの進化は偉大なり

使い道は今のところ未定、例えばTelloのコントローラーとしてはコマンド制御はともかくも、動画のストリーミング処理にはメモリもCPU能力も足りないし、当然ライブラリもない

 

admin

あと550円支払う?

今の回線はNuroの2Gbpsですが、NuroのWi-Fiルーター(規格ac:WiFi5)で頭打ちになっています

有線とWi-Fiの速度測定の結果は、

こんな感じで、有線だとG-LANのほぼ性能限度までの速度が出てるから上昇の余地があるということ

Wi-Fiルーターは契約を変えればaxバージョンが入手可能ですが、月額550円がプラスされるから、別に今で速度問題はないんだからそのままでは良いと言えば良いのだけど、接続されるデバイスがほぼax(WiFi6)対応になっているのに、わざわざ遅い環境のままにするのもね、という思いもありだよね

追加のax規格のWi-Fiルーターをブリッジ接続というのも有り得るけど、それだと今の1.5倍ぐらいにしかならないから、明らかに投資効果あると思えるのは倍だろうから中途半端な感じ

 

admin

3Dプリンタ(Flashforge3)不調と原因

しばらく前から、3Dプリンタでラフトの整形(PLAフィラメント)がうまくいかない、さらに造形物も空間埋めの六角形の成形が変dという状況でした

こんな感じで、ラフトがうまく整形できなから、剥がすと造形物に張り付く

色々試行してもうまくいかないので、ヘッドを新規に調達

右従来使ってたもの、左新品、実は従来使用してたのは温度が265℃で今回調達品は240℃品、やれてはいるけど見かけそんな劣化してるようには見えない

ヘッドの寿命はメーカーによると公称200時間と言うけど、プリンタの使用時間がそもそも200時間に達してない、ヘッドは三個目ですが

ヘッド交換してフィラメント押し出すと、なんとABSのフィラメントが吐き出された、おそらくこいつはPLAフィラメントの温度条件では溶けないから造形の邪魔になっていたと予測されます、どこにあったんだろう?

ともかくもキャリブレション後に造形すると、新品ノズルらしく造形面は綺麗です(センターがそれ)、左右は以前のテスト造形サンプルで左は塗装してます

復旧したようで、フィラメント送りモーターからの脱調音も出なくなった、結局つかえてたんだね、フィラメントは防湿収納して完了

 

admin

ラズパイでAvahiは外部サービスの状態をモニターしている

WSGIやFlaskサーバーでサービスを再起動すると、なぜmDNSが再度実行されるかというとAvahi(mDNSのラズパイ実装)がサービスをモニターしていて、再起動されるとCache flushを実行して、結果としてクライアントは再びmDNSを実行するようです

<tcpdumpの実行ログ>

MacBookからtfliteサービスのリクエストを出した時のログ、一度Cache flushが発行されると、タイムアウト(TTL)するまでは発行されない

% sudo tcpdump -i en0 udp port 5353 and src host 192.168.1.19

Password:
tcpdump: verbose output suppressed, use -v[v]... for full protocol decode
listening on en0, link-type EN10MB (Ethernet), snapshot length 524288 bytes

(サービス立ち上げ後にクライアントから最初にリクエスト)
09:36:44.585023 IP 192.168.1.19.mdns > 224.0.0.251.mdns: 0*- [0q] 1/0/0 (Cache flush) A 192.168.1.19 (39)
09:36:44.585025 IP 192.168.1.19.mdns > 224.0.0.251.mdns: 0*- [0q] 1/0/0 (Cache flush) AAAA 240d:1a:896:8300:99fc:1d59:78ab:a156 (51)


(サービスの再起動後のリクエスト)
09:39:08.361077 IP 192.168.1.19.mdns > 224.0.0.251.mdns: 0*- [0q] 1/0/0 (Cache flush) AAAA 240d:1a:896:8300:99fc:1d59:78ab:a156 (51)
09:39:08.361545 IP 192.168.1.19.mdns > 224.0.0.251.mdns: 0*- [0q] 1/0/0 (Cache flush) A 192.168.1.19 (39)

Avahiはdaemonで動作しています

$ ps -aux|grep avahi
avahi        655  0.0  0.0   7696  2672 ?        Ss   09:25   0:00 avahi-daemon: running [rasp5.local]

Avahiがどのようなロジックで外部サービスの状態をモニターしているのかはソース読まないとわからないけど

https://github.com/avahi/avahi

READMEには

AVAHI SERVICE DISCOVERY SUITE

とあるから、mDNSの実装よりもこちらがメインのように思える

 

admin