Arduino言語でのHUB75 LEDアレイ制御の一応の完成形

基本の表示機能の作成から、

https://isehara-3lv.sakura.ne.jp/blog/2024/12/16/hub75パネルを動かしてみた/

① 画像イメージ情報を3パターン用意

② LED駆動用にUSBの口が二つあるACアダプタ(二つで3Aの容量)用意してラズピコのケーブルも接続できるようにして物としてのまとまりをよくする

USB – DCプラグケーブル購入して、ジャックは見繕って加工してつないだ

③ M5Stackをコントローラーにして3パターンの画像切り替えと表示オフ機能を持たせた

ラズピコとM5Stackのコードと、LEDアレイとラズピコ接続回路図は以下のリンクから、

https://github.com/chateight/hub75_led_array_drive

Arduino言語ではこれでほぼ終わりの予定

 

admin

HUB75パネルをRustで駆動してみた

Arduino言語との速度比較のために、Rustでループ処理部分をベンチマークしてみた

プラットホームはembassyを使って、そのソースコードに埋め込み、表示パターンは階調表示のあるテストパターン、routine.rsは表示データの初期化を行うだけですが

コードは二つに分割

<main.rs>

//
// % cargo run --bin raspico

#![no_std]
#![no_main]

use embassy_executor::Spawner;
use embassy_rp::gpio::{Level, Output};
use {defmt_rtt as _, panic_probe as _};

mod routine;
use routine::DispData;

#[embassy_executor::main]
async fn main(_spawner: Spawner) {
    let p = embassy_rp::init(Default::default());
    let mut r1 = Output::new(p.PIN_2, Level::High);
    let mut r2 = Output::new(p.PIN_5, Level::High);
    let mut g1 = Output::new(p.PIN_3, Level::High);
    let mut g2 = Output::new(p.PIN_8, Level::High);
    let mut b1 = Output::new(p.PIN_4, Level::High);
    let mut b2 = Output::new(p.PIN_9, Level::High);
    let mut a = Output::new(p.PIN_10, Level::Low);
    let mut b = Output::new(p.PIN_16, Level::Low);
    let mut c = Output::new(p.PIN_18, Level::Low);
    let mut d = Output::new(p.PIN_20, Level::Low);
    let _e = Output::new(p.PIN_22, Level::Low);
    let mut clk = Output::new(p.PIN_11, Level::Low);
    let mut lat = Output::new(p.PIN_12, Level::High);
    let mut oe = Output::new(p.PIN_13, Level::High);

    let a_array: [u8; 16] = [0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1];
    let b_array: [u8; 16] = [0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1];
    let c_array: [u8; 16] = [0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1];
    let d_array: [u8; 16] = [0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1];

    let mut disp_data = DispData {
        _array64: [0; 64],
        array4d: [[[[0; 64]; 32]; 3]; 2],
    };
    let new_disp_data = disp_data.new();

    loop {

        for i in 0..16 {
            // row number
            if a_array[i] == 0 {
                a.set_low();
            } else {
                a.set_high();
            };
            if b_array[i] == 0 {
                b.set_low();
            } else {
                b.set_high();
            };
            if c_array[i] == 0 {
                c.set_low();
            } else {
                c.set_high();
            };
            if d_array[i] == 0 {
                d.set_low();
            } else {
                d.set_high();
            };

            for b in 0..15 {
                // shading
                lat.set_high();
                for k in 0..64 {
                    // column data
                    clk.set_low();
                    //
                    if new_disp_data.array4d[0][2][i][k] > b {
                        r1.set_high();
                    } else {
                        r1.set_low();
                    }
                    if new_disp_data.array4d[0][2][16 + i][k] > b {
                        r2.set_high();
                    } else {
                        r2.set_low();
                    }
                    if new_disp_data.array4d[0][1][i][k] > b {
                        g1.set_high();
                    } else {
                        g1.set_low();
                    }
                    if new_disp_data.array4d[0][1][16 + i][k] > b {
                        g2.set_high();
                    } else {
                        g2.set_low();
                    }
                    if new_disp_data.array4d[0][0][i][k] > b {
                        b1.set_high();
                    } else {
                        b1.set_low();
                    }
                    if new_disp_data.array4d[0][0][16 + i][k] > b {
                        b2.set_high();
                    } else {
                        b2.set_low();
                    }
                    clk.set_high();
                }
                oe.set_high();
                lat.set_low();
                oe.set_low();
            }
        }
    }
}

<routine.rs>

表示データの作成をするだけ、


static ARRAY64: [u8; 64] = [
    0, 1, 1, 2, 3, 5, 8, 13, 13, 8, 5, 3, 2, 1, 1, 0, 0, 1, 1, 2, 3, 5, 8, 13, 13, 8, 5, 3, 2, 1,
    1, 0, 0, 1, 1, 2, 3, 5, 8, 13, 13, 8, 5, 3, 2, 1, 1, 0, 0, 1, 1, 2, 3, 5, 8, 13, 13, 8, 5, 3,
    2, 1, 1, 0,
];

pub struct DispData {
    pub _array64: [u8; 64],
    pub array4d: [[[[u8; 64]; 32]; 3]; 2],
}

impl DispData {
    pub fn new(&mut self) -> Self {
        let mut dis_data = DispData {
            _array64: ARRAY64,
            array4d: [[[[0; 64]; 32]; 3]; 2],
        };
        for j in 0..3 {
            for i in 0..32 {
                dis_data.array4d[0][j][i] = ARRAY64;
            }
        }
        dis_data
    }
}

<実行速度>

およそ10ms(Arduino言語ではおよそ14msだから3割程度高速化)、繰り返し(メモリ読み出しとgpio書き込み)回数は一回のリフレッシュループで、

64*3*16*32 = 98,304(16行*3色*パネル上下*16階調)ということで一回のr/g/bデータの書き込みでおよそ1μs費やしている勘定で、クロック数ではおよそ130クロックとなります、階調処理での条件分岐が処理時間では大きそうですね

写真は行アドレス信号のDなので一周期がリフレッシュ周期

結果だけ見るとRustの優位性はそれほど無いように見えるかもしれないけど、メモリ管理やコンパイラの出来の良さ、複数タスク管理をasync/awaitで簡単に実現できるから他の言語で実現(Arduino言語ならRTOS使うことになるけれども、async/awaitのほうが軽量だと)するよりもアドバンテージはあると思う

P.S. 2024/12/24

この程度(3割)の改善ならば、pico 2を使えば簡単に逆転できそうだよね、消費電力とか別にすればね、ハードの進化は偉大なりか

 

admin

 

Rustでの非同期処理(async/await)

Rustの組み込み用のフレームワークにembassyがありますが、そのサンプルコードの中では非同期処理が使われているので、概略を見てみる

https://qiita.com/Kumassy/items/fec47952d70b5073b1b7

参考はちょっと古いですが、上記のリンクですが、tokioのバージョンでタイマー待ちの処理が変わっているので最新に合わせてコードを変えています

<環境>

・M1 Macbook Air

・rustc 1.82.0

・tokio = { version = “1.42”, features = [“full”] }
#[tokio::main]はコンパイル時に展開されて、
Rustがデフォルトでは持っていない非同期処理を可能とする
<サンプルコード>
//
// original from https://qiita.com/Kumassy/items/fec47952d70b5073b1b7
//
// [dependencies]
// tokio = { version = "1.42", features = ["full"] }
use std::{thread, time};
use tokio::time::{sleep, Duration};

async fn hello() -> String {
    let one_sec = time::Duration::from_millis(1000);
    // in case of async application, it looks better to use tokio::time crate
    // delay_for : before tokio 0.2
    sleep(Duration::from_millis(1000)).await;
    thread::sleep(one_sec);

    "hello, async fn".to_string()
}

// tokio: to support async runtime
#[tokio::main]
// the async keyword of the fn main() is necessary
async fn main() {
    // excute sysnc function and wait completion
    let greeting: String = hello().await;
    println!("{}", greeting);

    // new task create(spawn)
    // return the type Result
    // 
    let handle = tokio::spawn(async {
        sleep(Duration::from_millis(1000)).await;
        "spawn thread elapsed one second".to_string()
    });

    // define async block
    //
    let world = async {
        let one_sec = time::Duration::from_millis(1000);
        thread::sleep(one_sec);

        println!("hello, async block");
    };

    // rcv spwan thread return
    let result: String = handle.await.unwrap();
    println!("{}", result);

    // async block call and wait
    world.await;

    // wait to avoid main thread terminate before spawn task complete
    sleep(Duration::from_millis(1500)).await;
}

やっていること、

① async fn hello()での非同期処理

② asyncブロック(let world)での非同期処理

③ async非同期タスク(tokio::spawn)からの戻り値(let result: String)の取得

<実行結果>

hello, async fn
spawn thread elapsed one second
hello, async block

複数箇所に時間待ちを入れているので、その時間待ちが入って出力されます

let one_sec = time::Duration::from_millis(1000); 
sleep(Duration::from_millis(1000)).await;

tokioには時間街のためのsleep()関数がありますが、std::time::Durationとの違いはsleep()関数自体がasyncブロックの如く機能するということなので使い分けは必要です

 

admin

HUB75パネルを動かしてみた

オーダーしていたパーツが届いたので動かしてみた

HUB75のフラットケーブルをつなぐためにユニバーサルボードでラズピコとコネクタの配線、GPIOとの対応付はAmazonで売り切れになっている接続ボードのコネクションリストと同じにした、何かの時に役立つかもしれないから

最初はテストパターン表示

フィボナッチ数列の13までの昇順と降順でちょうど16になるからそれを4回繰り返したデータを用意して表示させてみた、リフレッシュサイクルは60フレーム/s以上(akafuji表示状態で測定すると1000/14 =71.429 フレーム/s)だから人の目には綺麗に見える

次にイメージデータを表示させてみる、データは「赤富士」のフリー素材を持ってきて最終的にb/g/rの[3][32][64]配列に落とし込む、実行はPython使って最終的にはコンソールに出力された以下のcolor_code変数をプリントしたものをArduino IDEに切り貼り

#
# to make r/g/b array from shrinked bmp image
#
from PIL import Image
import numpy as np
import cv2

# resize the image file
image = Image.open("akafuji.png")
resized_image = image.resize((64, 32))
resized_image.save("resized.bmp")

# get b/g/r value
image = cv2.imread('resized.bmp')
color_code = [[[0 for i in range(64)] for j in range(32)] for k in range(3)]

for x in range(64):
	for y in range(32):
		pixel = image[y, x]
	
		color_code[0][y][x] = image[y, x, 0]//16
		color_code[1][y][x] = image[y, x, 1]//16
		color_code[2][y][x] = image[y, x, 2]//16

print(color_code)		

元の64*32のbmpファイルは、

こんな感じ、

これをLEDで表示させると、

ゴミが出てます、データそのものは0になっているから、なんかしらの要因でそうなってますね

ちなみに、Arduino IDEのコードの核の部分は以下の通り

<main処理>

void setup1(){
  // set the gpio pins direction
  pinMode(r1, OUTPUT);
  pinMode(r2, OUTPUT);    
  pinMode(g1, OUTPUT);
  pinMode(g2, OUTPUT);    
  pinMode(b1, OUTPUT);
  pinMode(b2, OUTPUT);    
  pinMode(clk, OUTPUT);    
  pinMode(lat, OUTPUT);
  pinMode(oe, OUTPUT);    
  pinMode(a, OUTPUT);
  pinMode(b, OUTPUT);    
  pinMode(c, OUTPUT);
  pinMode(d, OUTPUT);
  pinMode(e, OUTPUT);

  gpio_put(oe, HIGH);
  gpio_put(lat, LOW);
  gpio_put(clk, LOW);
}

void loop1(){
  // message check from core0
  //
  while (rp2040.fifo.available()>0){
    // bool rp2040.fifo.pop_nb(uint32_t *dest)
    message = rp2040.fifo.pop();
  }
  // led display refreah loop
  //
  for (row_c = 0; row_c < 16; row_c++){
    // select row address
    gpio_put(oe, HIGH);
    gpio_put(a, a_sel[row_c]);
    gpio_put(b, b_sel[row_c]);
    gpio_put(c, c_sel[row_c]);
    gpio_put(d, d_sel[row_c]);
    gpio_put(e, e_sel[row_c]);
    
    // display row data with shade control
    for (byte j = 0; j < 15; j++){
      row_set(j);
      gpio_put(oe, HIGH);
      clock_func(lat);
      gpio_put(oe, LOW);
    }
  }
  //mov_data(1, 2);
}

<common.h>

行選択信号のeは64*32では使わないので常時LOW

// gpio pin assignment for HUB75

#define r1 (2)
#define r2 (5)
#define g1 (3)
#define g2 (8)
#define b1 (4)
#define b2 (9)
#define clk (11)
#define lat (12)
#define oe (13)
#define a (10)
#define b (16)
#define c (18)
#define d (20)
#define e (22)

// row select matrix

byte row_c = 0; // row counter

byte a_sel[32] = {0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1};
byte b_sel[32] = {0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1};
byte c_sel[32] = {0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1};
byte d_sel[32] = {0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1};
byte e_sel[32] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1};

<functions>

clock_func()でgpis_put()が一回だけだとパルスが細すぎてLEDパネルでうまくラッチできないから3回書き込みでパルス幅確保

// to make clock pulse

void clock_func(byte port){
  //digitalWrite(0, HIGH);
  gpio_put(port, 1);  // in case of wite once, data wouldn't be captured correctly
  gpio_put(port, 1);
  gpio_put(port, 1);
  //digitalWrite(0, LOW);
  gpio_put(port, 0);
}

// row data buffer write

void row_set(byte shade){
    for (byte i = 0; i < 64; i++){ 
// akafuji color order b/g/r 
// red 
      if (akafuji[2][row_c][i] > shade){
        gpio_put(r1, 1);        
      }
      else{
        gpio_put(r1, 0);
      }
      if (akafuji[2][row_c + 16][i] > shade){
        gpio_put(r2, 1);
      }
      else{
        gpio_put(r2, 0);
      }
      // green
      if (akafuji[1][row_c][i] > shade){
        gpio_put(g1, 1);
      }
      else{
        gpio_put(g1, 0);
      }
      if (akafuji[1][row_c + 16][i] > shade){
        gpio_put(g2, 1);
      }
      else{
        gpio_put(g2, 0);
      }
      //blue
      if (akafuji[0][row_c][i] > shade){
        gpio_put(b1, 1);
      }
      else{
        gpio_put(b1, 0);
      }
      if (akafuji[0][row_c + 16][i] > shade){
        gpio_put(b2, 1);
      }
      else{
        gpio_put(b2, 0);
      }
      // write to row buffer
      clock_func(clk);
    }
}

文字も自分でフォント作るよりも、既存のフォントで文字作ってそれをイメージ化してbmpデータとするのが現実的に思える

P.S.

いきなりですが、ゴミ対策はアドレス選択変更前に表示オフを追加すれば良い、つまりrow選択アドレス変える時には表示を一旦オフにしろという原則通り、

// select row address 
gpio_put(oe, HIGH);

 

iPhoneでlive撮影して、平均化してしまうと輝度低下して冴えない写真になるのは致し方なしだね

 

admin

ラズピコでArduino IDEの実行速度を見てみるのと改善方法

Arduinoはコンパイル時にcppに落とされてからコンパイルされるので、基本はコンパイラ言語ですが、学習用にかなり冗長な作りになっているようなので、実行速度が問題になるケースも多そうです

というわけで簡単にベンチマーク

元はLEDブリンクのソースに計測用のコードを追加しています

<mainのinoファイル>

ただし時間測定時には、loop1()のwhile処理(loop0()からのメッセージ受信)はコメントアウト

#include "common.h"

// constants won't change. Used here to set a pin number:
const int ledPin = LED_BUILTIN;  // the number of the LED pin

// Variables will change:
int ledState = LOW;  // ledState used to set the LED

// Generally, you should use "unsigned long" for variables that hold time
// The value will quickly become too large for an int to store
unsigned long previousMillis = 0;  // will store last time LED was updated

// constants won't change:
const long interval = 500;  // interval at which to blink (milliseconds)

uint32_t message_s = 0;
uint32_t message = 999;

void setup() {
  // set the digital pin as output:
  pinMode(ledPin, OUTPUT);
}

void loop() {
  // send message to core1
  rp2040.fifo.push(message_s);
  message_s += 1;

  unsigned long currentMillis = millis();

  if (currentMillis - previousMillis >= interval) {
    // save the last time you blinked the LED
    previousMillis = currentMillis;

    // if the LED is off turn it on and vice-versa:
    if (ledState == LOW) {
      ledState = HIGH;
    } else {
      ledState = LOW;
    }
    // set the LED with the ledState of the variable:
    digitalWrite(ledPin, ledState);
  }
}

void setup1(){
  pinMode(0, OUTPUT);  
}

void loop1(){
  while (rp2040.fifo.available()>0){
    message = rp2040.fifo.pop();
    message *= 10;
  }
  for (int i = 0; i < 8; i++){
    for (int j = 0; j < 8; j++){ if (disp_data[i][j] > 7){
          dest_data[i][j] = disp_data[i][j];
        }
        else{
          dest_data[i][j] = 0;
        }
    }
  }

  clock_func();
}

<common.hファイル:byte型の二次元配列を2個確保しています>

データの中身に取り立てて意味はありません


// display data
byte disp_data[8][8] = {
   {0,1,1,2,3,5,8,13},
   {13,8,5,3,2,1,1,0},
   {0,1,1,2,3,5,8,13},
   {13,8,5,3,2,1,1,0},
   {0,1,1,2,3,5,8,13},
   {13,8,5,3,2,1,1,0},
   {0,1,1,2,3,5,8,13},
   {13,8,5,3,2,1,1,0}
};

byte dest_data[8][8] = {
};

<clock.inoファイル>

gpioへの書き込みが極端に遅いので、c++の関数を呼び出せるということなので切り替えた(digitalWrite() -> gpio_put())、パルス幅確保のためにハイレベルを5回書き込んでます



void clock_func(){
  //digitalWrite(0, HIGH);
  gpio_put(0, 1);
  gpio_put(0, 1);
  gpio_put(0, 1);
  gpio_put(0, 1);
  gpio_put(0, 1);
  //digitalWrite(0, LOW);
  gpio_put(0, 0);
}

実行速度の測定結果

測定はgpio0のポイントをオシロのプローブであたって時間測定

・digitalWrite()使用時

・gpio_put()使用時

考察

ループの外でGPIO書き込みを行っているのに、全体の処理時間を3割程度長くしてしまうArduinoの標準書き込み関数は時間がクリティカルな場所では使えない、配列の処理に関してはcに比較したら遅いだろうけれども許容範囲か、ただしコードの記述には工夫が必要(配列の要素を一度変数に入れてから処理とかはやめた方が良い

やりたいことはLEDアレイ(HUB75で64*32)の点灯をコア1で行うようにしたいから、16階調処理だと一行のループ処理をおよそ69μs以内(50Hz以下になると人の目に見えるから、1000000/(60*15*16) =69.444μs )ぐらいで実行必要だからターゲットはそこだけど、なんとかなりそうな感触

 

admin

ラズピコのマルチコア使ったアプリケーションを考える

ラズパイzeroでもコアが一個しかないのに、picoには二個あります、その分クロック周波数は1GHz vs 133MHzですが、

で、マルチコアを使ったアプリとしてHUB75インターフェースのLEDパネルを駆動させるのが取っ付きやすそうに思う

なぜなら、LEDパネルは擬似的な点灯(だから静止画や動画撮影ではまともに見えない)で時間を保証(数十μsオーダー)して点滅させないといけないので、通信や表示データを用意する余裕はないだろうから、表示制御にはコア一個を割り振り、もう一個のコアでは通信と表示データを用意させる処理を実行させる

無論メモリは両方のコアで共通だから、完全に負荷分散ができる訳でもないけどシングルコアよりはかなり余裕があるはず、上はメモリやペリフェラル含めた全体図、下はコア部分だけに着目したブロック図でAHB-Liteがコアの分離とアクセス制御をやっているように見える

データの受け渡しは、表示データの完全性の保証は必要ないからFIFOは制御用のデータ受け渡しに使い、表示データは共通のRAMエリアで2面持たせて、切り替え使用するようにすればいいだろう

 

admin

ラズピコ wでのマルチコアの使い方(Arduino IDE)

ラズピコはマルチコアになっていて、MicroPythonでもマルチコアを使えるけれど、実行速度考えたらArdunino言語を使った方が高速だろうから、

https://ameblo.jp/pta55/entry-12798012318.html

で見つけたやり方を参考に、Lチカアプリをマルチコア使って他のコアにメッセージを送ってみた

ここでは固定値message_sをfifoで送信していますが、受信側で使われるglobal変数はloop1のfifo受信処理で書き換えられています(最初のブレークポイントではグローバル変数の初期値999ですが、その後のブレークでは1になります(fifoバッファーのデータ有無チェックしてるのに初期値になるのは変!

https://arduino-pico.readthedocs.io/en/latest/multicore.html

には、

int rp2040.fifo.available()

Returns the number of values available to read in this core’s FIFO.

とありますが

// constants won't change. Used here to set a pin number:
const int ledPin = LED_BUILTIN;  // the number of the LED pin

// Variables will change:
int ledState = LOW;  // ledState used to set the LED

// Generally, you should use "unsigned long" for variables that hold time
// The value will quickly become too large for an int to store
unsigned long previousMillis = 0;  // will store last time LED was updated

// constants won't change:
const long interval = 500;  // interval at which to blink (milliseconds)

uint32_t message = 999;

void setup() {
  // set the digital pin as output:
  pinMode(ledPin, OUTPUT);
}

void setup1(){
}

void loop() {
  // here is where you'd put code that needs to be running all the time.

  // check to see if it's time to blink the LED; that is, if the difference
  // between the current time and last time you blinked the LED is bigger than
  // the interval at which you want to blink the LED.
  unsigned long currentMillis = millis();

  if (currentMillis - previousMillis >= interval) {
    // save the last time you blinked the LED
    previousMillis = currentMillis;

    // if the LED is off turn it on and vice-versa:
    if (ledState == LOW) {
      ledState = HIGH;
    } else {
      ledState = LOW;
    }

    // set the LED with the ledState of the variable:
    digitalWrite(ledPin, ledState);

    // send message via FIFO
    uint32_t message_s = 0;
    rp2040.fifo.push(message_s);
  }
}

void loop1(){
  if (rp2040.fifo.available()>0){
    message = rp2040.fifo.pop();
    message += 1;
  }
}

マルチコアの使い方は、

① コアごとの動作はsetup/setup1、loop/loop1で指定する

② コアプロセス間の通信はfifo(rp2040.fifo)を使って行う、データ幅は4バイトで長さは8らしい(ソフト的なfifoなら任意に設定できるはずなのでこれはハードレジスタ)、受信するデータはglobal変数で定義しないといけない、送信側はlocal変数でもglobal変数でも大丈夫

デバッガで動かしてみると、マルチコア動作が確認できるのと、sync.hなるファイルが自動で生成されています、これはステップイン機能(停止したポイントの関数の中も追える)ですね

 

画面を見ればわかるようにコア二個分のデバッグ情報を見ることができます

 

admin

 

Raspberry piのDebug Probeを使う(Arduino IDE)

中身はラズピコのDebug Probeを使ってみる、もう一枚ラズピコでファームインストでも良いけど、かさばらないでスマートだから専用品の方が良い

Debug Probeの公式ページは、

https://www.raspberrypi.com/documentation/microcontrollers/debug-probe.html

<動作環境>

M1 MacBook Air/Arduino IDE 2.3.3

USBケーブルはswdの場合にはどちらもダウンロードはデバッガ経由なのでターゲットは電源供給だけで問題ないそうです、実際にはどちらからのダウンロードも使うから最初からデータ転送できるケーヌルにしとけば良いと思いますが、macからの見え方は、

Arduino IDEのToolsの設定は、デバッガーポートが認識されている状態では以下のような選択と見え方です

一点ハマったのは、デバッグ用のglobeボードの捺印のあるコネクタにswd信号はつながっていない事、wでない普通のボードはつながっているのかもしれないけど(ケーブルの色はオレンジ:SWCLK、黒:GND、黄色:SWDIOに対応してます)

でPCB上のviaにピン立てて受けにしてケーブル接続

swdインターフェースがアクティブならば、オレンジと黄色のledが点灯状態になって、Arduino IDEのデバッグも機能します

P.S. 2024/12/1

デバッガーのswdコネクタの半抜け注意

何故か急に繋がらないなと思ったら、コネクタが半抜け状態、この状態では最初にorange/yellow ledが点灯してすぐに消える(エラーになる)

奥まで押し込んだら回復、

<追記:2024/12/24>

デバッグできない(一度止めるとエラー出た)ので、何かと調べるとシリアルポートの設定をこのようにしないといけなかった

% ls /dev/cu.*
/dev/cu.Bluetooth-Incoming-Port	/dev/cu.usbmodem11102
/dev/cu.ESP32			/dev/cu.usbmodem11201

となっているから、デバッグ用にシリアルポートは正しく設定しましょう、このポートはmacのリブートなどで変わるんだろうね

 

admin

ラズピコ wでのLチカから始める

すでにもう1年以上経過しましたが、

ラズパイPico WのRust

のフォローで、embassyを使ったサンプルプログラムでLチカを動かしてみた、とはいっても、ほぼ

https://qiita.com/Azi/items/422c654bb476e0abf118

の手順に則っています、Cargo.tomlのライブラリ版数を最新にしたのと、ソースファイルは通常通りsrcディレクトリ直下に配置しています

全体的に感じるのは、Lチカだけなのに環境構築は作業項目多いし、コードの分量も結構あるということで、MicroPyhtonやArduino言語に比較すると事前の手間がかかるということ

ディレクトリ構成はこんな感じで、config.tomlのrunnerはデバッガ使わないときには修正必要です

build.rsの役割は、

//! This build script copies the `memory.x` file from the crate root into
//! a directory where the linker can always find it at build time.
//! For many projects this is optional, as the linker always searches the
//! project root directory -- wherever `Cargo.toml` is. However, if you
//! are using a workspace or have a more complicated build setup, this
//! build script becomes required. Additionally, by requesting that
//! Cargo re-run the build script whenever `memory.x` is changed,
//! updating `memory.x` ensures a rebuild of the application with the
//! new memory settings.

memory.xはテキストファイルで、picoのメモリの使い方(マッピング)を指定するファイルに見えます

pico用のサンプルプログラムは、

https://github.com/embassy-rs/embassy/tree/main/examples/rp

に他の機能用のサンプルも存在するのでやってみるか

気づきは、① ビルドのオプションで–releaseを指定してもバイナリサイズは変わらないということ、組み込み用のバイナリだとそうなるのかもしれない、② targetディレクトリのサイズが巨大であること、今回の事例でも2.3GB程度の容量になっています

いずれにしろサンプルプログラム動かしてみるのはまだしも、実際にオリジナルのコードを書いたときにはデバッグ環境が必須だから、そこも当たり始めないと組み込みでは使い物にはならない

一番ポピュラーなのはラズパイ財団から出ているデバッグプローブを使うことだろう

https://www.switch-science.com/products/8708

 

admin

systemdサービス起動時の遅延時間設定

Rust版の震度計のアプリを起動時にssd1306の画面が乱れたままで復旧しないことがある、システム起動後の起動では問題ないからspiの初期化が一番怪しいけれども、ともかくもハードウェアに関連するだろうことは間違いない

でsystemdの起動を遅らせれば良いだろうから、そのための設定をググってもなかなか当たらないからPerplexity Labsに聞いてみると、ExecStartPre=/bin/sleepで時間設定すれば良いと言われたのでやってみたら正解

まあ、seismic起動前にsleepで30秒待てと言っているだけなので、ExecStartPreは本来実行したいコマンドの前に実行する処理を記述しているだけなのですが

$ sudo systemctl daemon-reload

$ sudo systemctl enable seismic.service

で設定を有効化して、電源オフ後の再起動では問題ないようです、30秒というのはsshでログインしようとしてログインが可能になるタイミングからさらに10秒近く経過ですが、この時点では全てのサービスがレディになると考えればよさそうです、本来的には時間待ちではなくてどれかのサービス起動後に起動というのが正しそうですが

[Unit]
Description = measure 

[Service]
ExecStartPre=/bin/sleep 30s 
ExecStart=/home/pi/rust/seismic
Restart=no
Type=oneshot

[Install]
WantedBy=multi-user.target

Python版では起動に失敗していたので、serviceファイルで待ち時間設定ではなく、コードの最初で20秒sleep入れてたけど、やり方としてはserviceファイル記述がはるかにスマート

 

admin