ラズパイ5追加のセットアップ(1)

    とりあえず動作確認したラズパイ5ですが、目的用途のための動作確認などを、
    1. 起動媒体

まず、間に合わせ使ったsdカードがメチャクチャ遅い、ドラレコから抜いてきたからほぼ寿命終わりらしい

かなり昔のusbメモリにインストして立ち上げた方がはるかに高速、なので追加でSDカード(UHS-1規格)購入してインストすると体感かなりサクサク

shutdown ~ reboot(ログオン完了)時間は、

Shutdown ~ reboot時間
どちらもヘッドレス、
古いmicro SD : 1:40 min/ 2:20 min(媒体終わってるよね)
Stick USB : 1:10 min

SDカード(UHS-1):40 sec(デスクトップモードなのに)

という結果、ついでにsdカードのベンチマークは、

UHS-1カードのPiBenchmarks結果

$ sudo curl https://raw.githubusercontent.com/TheRemote/PiBenchmarks/master/Storage.sh | sudo bash

     Category                  Test                      Result     
HDParm                    Disk Read                 90.82 MB/sec             
HDParm                    Cached Disk Read          90.94 MB/sec             
DD                        Disk Write                32.5 MB/s                
FIO                       4k random read            6317 IOPS (25268 KB/s)   
FIO                       4k random write           845 IOPS (3382 KB/s)     
IOZone                    4k read                   29989 KB/s               
IOZone                    4k write                  3288 KB/s                
IOZone                    4k random read            30032 KB/s               
IOZone                    4k random write           3268 KB/s                

                          Score: 2530                             

ラズパイ5ではおそらく平均的な値だろう

 

2. Pythonのインスト

ラズパイではpyenvを使うことが推奨というかほぼマストです

https://zenn.dev/technicarium/articles/00b32d390e82ec

がわかりやすかったのでこのサイトとPerplexityでPyenvと現状安定版の最終3.13.1をインスト、必要なライブラリは都度

 

3. text2speechでスピーカ機能も確認

音声の入出力にUSB接続のマイクとスピーカーを使いますが、その機能確認含めて、ロワーケースはssd用HAT(見た目ロワ側なのでHATじゃなくてHAB、そのそも上側にはGPIOコントロールのカスタムボードが追加されるし)に置き換えてます

フリーのopen_jtalkのインストと動作確認

open-jtalkインスト
$ sudo apt-get update
$ sudo apt-get install -y open-jtalk open-jtalk-mecab-naist-jdic htsengine libhtsengine-dev hts-voice-nitech-jp-atr503-m001

女性の声のインスト
$ wget https://sourceforge.net/projects/mmdagent/files/MMDAgent_Example/MMDAgent_Example-1.7/MMDAgent_Example-1.7.zip
$ unzip MMDAgent_Example-1.7.zip
$ sudo cp -r ./MMDAgent_Example-1.7/Voice/* /usr/share/hts-voice/

合成音声(text2speech)のサンプルコード(Perplexityで作成)

#
# pactl set-sink-volume @DEFAULT_SINK@ +10%      音量アップ
#
import subprocess
import os
from datetime import datetime

def jtalk(text):
    open_jtalk = ['/usr/bin/open_jtalk']
    mech = ['-x', '/var/lib/mecab/dic/open-jtalk/naist-jdic']
    htsvoice = ['-m', '/usr/share/hts-voice/nitech-jp-atr503-m001/nitech_jp_atr503_m001.htsvoice']
    speed = ['-r', '1.0']
    outwav = ['-ow', 'out.wav']
    cmd = open_jtalk + mech + htsvoice + speed + outwav
    
    try:
        proc = subprocess.Popen(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
        stdout, stderr = proc.communicate(input=text.encode('utf-8'))
        
        if proc.returncode != 0:
            print(f"Error in open_jtalk: {stderr.decode('utf-8')}")
            return
        
        if not os.path.exists('out.wav'):
            print("Error: out.wav was not generated.")
            return
        
        # Raspberry Pi での音声再生
        subprocess.call(['aplay', 'out.wav'])
    except Exception as e:
        print(f"An error occurred: {e}")

if __name__ == '__main__':
    current_time = datetime.now().strftime("%H時%M分")
    text = f"現在の時刻は{current_time}です。"
    jtalk(text)

 

4. マイク機能の確認

$ arecord -l
**** List of CAPTURE Hardware Devices ****
card 2: SF558 [SF-558], device 0: USB Audio [USB Audio]
  Subdevices: 1/1
  Subdevice #0: subdevice #0

のように見えているので、Linuxの基本コマンドで確認

// record
$ arecord -d 5 -r 12000  out.wav
// play
$ aplay out.wav

後ペリフェラルではカメラ必要だけど、それはリサーチの後だな、それにステッピングモータとか駆動させるとACアダプタ5Aで足りるのかな?

 

admin

 

raspberry pi 5のインストなど

ラズパイの現行ハイエンド(8GB品)を購入、今までラズパイ1、zero、picoしか購入履歴ないから史上最高性能、ヒートシンクつけたら見ることないから取り付け前の写真

最終的にはssdで高速化と高信頼性化しますが、とりあえずsdカードでインスト(ほぼ動作確認)してみた

osはssdへの書き込み考慮して、必要ないけどデスクトップバージョンをインスト、sshは書き込み時点のオプション選択でオンにしておかないと手も足も出ないから忘れずに、同時にWi-Fiの設定もしておきます、こちらは最悪有線LAN使えばいいけども

ラズパイの基板保護(特に裏面のチップ部品)のために拾ってきたstlファイルでロワーカバー造形、sdカードとの干渉部分は追加で加工しないと後で泣く(sdカード持ち上げ方向の力ではんだ剥離します

sdカード差込部

デスクトップ版のosインストしても、モニターもキーボードも持っていないからvncでデスクトップ画面をmacから操作します

https://qiita.com/ktamido/items/82ed2f5bd324d4721096

x11を有効化してmac標準のvncではつながらなかったからvnc viewerをインスト、設定だろうけどスクショ撮れなかったからカメラで撮影の画面

 

admin

 

 

HUB75 LEDアレイの表示画像を任意に変更できるようにした

固定した画像ファイルを選択するだけではイマイチだから、webサーバー(ラズパイzero)にブラウザ経由で任意の画像をアップロードして、サーバー側で画像の圧縮処理、rgb抽出を行いTCPでラズピコに送信して表示させるようにした

構成はwebアプリをgolangで作成し、ラズピコ側のコードも対応して変更、途中でデータロスはできないからUDPはあり得なくてTCPは前提

コード生成はPerplexityが無償でもgolangでは使えるコードが出てくるので活用、多分コードの生産性からいったら倍以上にはなる

<web application code>

https://github.com/chateight/golang/tree/master/image

<raspberry pi pico code>

https://github.com/chateight/hub75_led_array_image_upload/tree/main

ラズパイzeroでの実行速度は150KBぐらいのイメージファイルでLEDアレイに表示されるまで2秒ちょっとといったところ(Wi-Fi転送は過去の経験で1MB/secは出るからほぼイメージ処理の時間)だけど実用的には問題ないレベル

ラズピコ側でTCP通信するのにどうやるのかを検索してもイマイチだったので、それもPerplexityからの回答で、主要なコードを抜き出すとこんな感じ、

void setup(){
  IPAddress staticIP(192, 168, 1, 200); 
  IPAddress gateway(192, 168, 1, 1);
  IPAddress subnet(255, 255, 255, 0);

  WiFi.config(staticIP, gateway, subnet);

  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.println("Connecting to WiFi...");
  }
  server.begin();
}

void loop() {
  client = server.accept(); // TCP start
  // if network is active
  if (client) {
    while (client.connected()) {
      if (client.available()) {
        // receive binary data
        int bytesRead = 0;
        unsigned long startTime = millis();
        
        // Read all available data within a 500ms window
        while (client.available() && (millis() - startTime < 500) && bytesRead < MAX_BUFFER) { 
      receivedData[bytesRead] = client.read(); 
      bytesRead++; 
     }
        
        // response to the server
        const char* response = "Resp from Pico W!";
        client.write(response, strlen(response));
      }
  }

今更ながらコード作成でもLLMはもはや必需品で、用途ごとに要求されるものが違うからそれは人間社会と同じかもしれない

 

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

ラズパイのGPIO割り込み検出とsystemd設定について

ラズパイにseismicサービスを組み込む時に関連したメモ

① 現状Rust(rppal)ではGPIOで割り込みを検出する手段は提供されていない様子

作ればいいんだろうけど、今の所クレートは存在していないから、従来通りそこだけはPythonのサービスを起動、以下のソースで個別にサービス定義して起動時に実行させておく

#
# wait switch push interrupt and issue shutdown command
#
import time
import datetime
import os
import sys
import RPi.GPIO as GPIO
import subprocess

# Shut down sw is assigned to GPIO17
# GPIO initialize
SHUTDOWN = 17

GPIO.setwarnings(False)
GPIO.setmode(GPIO.BCM)
GPIO.setup(SHUTDOWN, GPIO.IN)
GPIO.setup(SHUTDOWN, GPIO.IN, pull_up_down=GPIO.PUD_UP)

def handle_sw_input():
# wait key inout event
    def switch_callback(gpio_pin):
        subprocess.call('sudo shutdown -h now', shell=True)
#
    GPIO.add_event_detect(SHUTDOWN, GPIO.FALLING,bouncetime=500)
    # when the sw was pushed, call the 'call back routine' 
    GPIO.add_event_callback(SHUTDOWN, switch_callback) 
    return

handle_sw_input()

while True:
    time.sleep(100)

② 今更ですが。/lib/systemd/systemのserviceファイルを変更した時には、以下の処理が必要(既存ファイルの書き換え替えが反映されなかった)

・サービスを登録する(編集したときの反映時にも必要)

$ sudo systemctl daemon-reload

$ sudo systemctl enable seismic.service

 

admin

Rustのクロスコンパイル環境でCrossを使う

結構以前からあるツールのようですが、DockerあるいはオープンソースのコンテナであるPodmanなどのコンテナ使ってコンテナ内にターゲットのイメージを持ってきてその中でコンパイルしてくれるツールです

<環境>

・コンパイル環境:M1 Mac

・ターゲット:Raspberry pi zero W

Dockerの場合にはroot権限でなくユーザ権限でないと使えないというし、Padmanならば最初からユーザー権限ということでPodmanをインストして使ってみました

https://podman.io/docs/installation

Podmanを使うかDockerを使うかは~/.zshrcに環境変数の指定が必要で、

https://docs.rs/crate/cross/latest

export CROSS_CONTAINER_ENGINE=podman

を指定します

 

Podmanを起動状態で、

% CROSS_CONTAINER_OPTS="--platform linux/amd64" cross build --target arm-unknown-linux-gnueabihf

を実行すると、imageをダウンロードしてその上でコンパイルします

% CROSS_CONTAINER_OPTS="--platform linux/amd64" cross build --target arm-unknown-linux-gnueabihf

Trying to pull ghcr.io/cross-rs/arm-unknown-linux-gnueabihf:0.2.5...
Getting image source signatures
Copying blob sha256:2c7e00e2a4a7dccfad0ec62cc82e2d3c05f3a4f1d4d0acc1733e78def0556d1e

~~~途中省略~~~

Writing manifest to image destination
   Compiling hello v0.1.0 (/project)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 2.45s

このようにコンパイルできて、定番のHello Worldソースからビルドした実行ファイルをラズパイ zero Wに転送すると実行できました

なぜCROSS_CONTAINER_OPTS="--platform linux/amd64"必要なのかは、

https://github.com/cross-rs/cross/issues/1214

からの情報ですがM1上のクロスだよと指定が必要のようで、指定しないとtargetで使うimage見つからないよと言われます

 

admin

ラズパイ立ち上げ時のアプリ自動起動と発生した問題

Raspberry PIでブート時にアプリを自動で立ち上げる方法は、以前は/etc/rc.localに記述という方法もありましたが、今は

https://www.raspberrypirulo.net/entry/systemd

にあるように/lib/systemd/system/配下にxxx.serviceファイルで条件記述して、

$ sudo systemctl start xxx.service

を使うのが推奨だし安定して使えるかと思いますが、震度計で記述したときに問題が起きたのでその状況と回避方法を

/lib/systemd/system/seismic.serviceで以下の内容を記述して、

[Unit]

Description = measure

[Service]

ExecStart=/usr/bin/python3 /home/pi/python/seismic.py

Restart=no

Type=oneshot

[Install]

WantedBy=multi-user.target

ラズパイブート時のseismic.serviceの起動で,

Sep 27 20:44:06 rasp-z python3[298]:   File "/home/pi/python/seismic.py", line 47, >

Sep 27 20:44:06 rasp-z python3[298]:     spi.open(0,0)

Sep 27 20:44:06 rasp-z python3[298]: FileNotFoundError: [Errno 2] No such file or d>

こんなエラーが出て起動できない、起動後の実行

$ sudo systemctl start seismic.service

だと問題ない、ということはタイミング問題じゃないかということでseismic.pyの最初で時間待ち(20秒)させてやったら、問題なく起動できました。

本来はサービスの起動条件をseismic.serviceファイルのパラメータで設定して対応すべきことかと思いますが、

<systemdの解説記事>

https://office54.net/iot/linux/systemd-unit-create

 

admin

地震計を作ってみる(その3)

取り敢えずOLCDに震度測定値を表示できるようにした。あとラズパイのシャットダウンがネットワーク未接続でも対応できるようにシャットダウンボタンを追加、

現時点でのコード

<seismic.py>

#
# core code is as follow
# https://github.com/p2pquake/rpi-seismometer/blob/master/seismic_scale.py
#
import time
import datetime
import math
import socket

import spidev
import os
import sys

import RPi.GPIO as GPIO
import subprocess

# FPS制御 -----
# ターゲットFPS
target_fps = 200

start_time = time.time()
frame = 0

# initial skip
skip = 7

# Shut down sw is assigned to GPIO17
# GPIO initialize
SHUTDOWN = 17

GPIO.setwarnings(False)
GPIO.setmode(GPIO.BCM)
GPIO.setup(SHUTDOWN, GPIO.IN)
GPIO.setup(SHUTDOWN, GPIO.IN, pull_up_down=GPIO.PUD_UP)

# wait for spidev driver ready
time.sleep(20)

def handle_sw_input():
# wait key inout event
    def switch_callback(gpio_pin):
        subprocess.call('sudo shutdown -h now', shell=True)
#
    GPIO.add_event_detect(SHUTDOWN, GPIO.FALLING,bouncetime=250)
    # when the sw was pushed, call the 'call back routine' 
    GPIO.add_event_callback(SHUTDOWN, switch_callback) 
    return

handle_sw_input()

# SPIセンサ制御 -----
spi = spidev.SpiDev()
spi.open(0,0)
spi.max_speed_hz = 1000*1000

def ReadChannel(channel):
    adc = spi.xfer2([(0x07 if (channel & 0x04) else 0x06), (channel & 0x03) << 6, 0])
    data = ((adc[1] & 0x0f) << 8 ) | adc[2] return data # reset the seisamic intensity with open("/home/pi/python/value.txt", "w") as file: file.write("0") file.close() # 加速度データ制御 ----- # A/Dコンバータ値 -> ガル値 係数
ad2gal = 1.13426
# 0.3秒空間数
a_frame  = int(target_fps * 0.3)

# 地震データ -----
adc_values = [[1] * target_fps, [1] * target_fps, [1] * target_fps]
rc_values   = [0, 0, 0]
a_values = [0] * target_fps * 5

adc_ring_index = 0
a_ring_index = 0

# リアルタイム震度計算 -----
while True:
    # リングバッファ位置計算
    adc_ring_index = (adc_ring_index + 1) % target_fps
    a_ring_index = (a_ring_index + 1) % (target_fps * 5)

    # 3軸サンプリング
    for i in range(3):
        val = ReadChannel(i)
        adc_values[i][adc_ring_index] = val
   
    # フィルタ適用及び加速度変換
    axis_gals = [0, 0, 0]
    for i in range(3):
        offset = sum(adc_values[i])/len(adc_values[i])
        rc_values[i] = rc_values[i]*0.94+adc_values[i][adc_ring_index]*0.06
        axis_gals[i] = (rc_values[i] - offset) * ad2gal

    # 3軸合成加速度算出
    composite_gal = math.sqrt(axis_gals[0]**2 + axis_gals[1]**2 + axis_gals[2]**2)

    # 加速度リングバッファに格納
    a_values[a_ring_index] = composite_gal

    # 0.3秒以上継続した合成加速度から震度を算出
    seismic_scale = 0
    min_a = sorted(a_values)[-a_frame]
    if min_a > 0:
      seismic_scale = 2 * math.log10(min_a) + 0.94

    # 0.1秒おきに出力
    if frame % (target_fps / 1) == 0:
            if seismic_scale > 0.5:
                if skip > 1:
                    skip -= 1
                else:
                    with open("/home/pi/python/value.txt", "w") as file:
                        file.write(str(round(seismic_scale, 1)))
                        file.close()
                    #print(datetime.datetime.now(), "scale:" , round(seismic_scale, 2), " frame:", frame)

    # 次フレームの開始時間を計算
    frame += 1
    next_frame_time = frame / target_fps

    # 残時間を計算し、スリープ
    current_time = time.time()
    remain_time = next_frame_time - (current_time - start_time)

    if remain_time > 0:
        time.sleep(remain_time)

    # フレーム数は32bit long値の上限あたりでリセットしておく
    if frame >= 2147483647:
        start_time = current_time
        frame = 1

表示(disp1.py)にはvalue.txtファイルを介在してデータ渡してます、時間情報と合わせてsqlite3に格納するようにする予定

<disp1.py>

import time
import board
import digitalio
from PIL import Image, ImageDraw, ImageFont
import adafruit_ssd1306

# Define the Reset Pin
oled_reset = digitalio.DigitalInOut(board.D4)

# Change these
# to the right size for your display!
WIDTH = 128
#HEIGHT = 32  # Change to 64 if needed
HEIGHT = 64  # Change to 64 if needed
BORDER = 5

# Use for I2C.
i2c = board.I2C()
oled = adafruit_ssd1306.SSD1306_I2C(WIDTH, HEIGHT, i2c, addr=0x3C, reset=oled_reset)

# Use for SPI
# spi = board.SPI()
# oled_cs = digitalio.DigitalInOut(board.D5)
# oled_dc = digitalio.DigitalInOut(board.D6)
# oled = adafruit_ssd1306.SSD1306_SPI(WIDTH, HEIGHT, spi, oled_dc, oled_reset, oled_cs)

# Clear display.
oled.fill(0)
oled.show()

# Create blank image for drawing.
# Make sure to create image with mode '1' for 1-bit color.
image = Image.new("1", (oled.width, oled.height))

# Get drawing object to draw on image.
draw = ImageDraw.Draw(image)

# Load default font.
font = ImageFont.truetype("fonts-japanese-gothic.ttf", 32)
#font = ImageFont.load_default()

# Draw Some Text
while True:
    with open("/home/pi/python/value.txt", "r") as file:
        text = file.read()
    image = Image.new("1", (oled.width, oled.height))
    draw = ImageDraw.Draw(image)
    #text = str(mag)
    (font_width, font_height) = font.getsize(text)
    draw.text(
        (oled.width // 2 - font_width // 2, oled.height // 2 - font_height // 2),
        text,
        font=font,
        fill=255,
    )

# Display image
    oled.image(image)
    oled.show()
    time.sleep(1)

震度は対数メモリでの尺度だから、震度七の強烈さはよく実感できます。

 

admin

地震計を作ってみる(その2)

部品実装と配線、配線チェックと単体の機能試験まで、

① 回路図:ライブラリが存在しないパーツは適宜置き換え、三端子、加速度センサー、OLECD、ライブラリあったのはラズパイI/FとADCだけという結果

アナログ電源はノイズ除去のためにL/Cでπ型のフィルタ構成にしています、ラズパイやM5StackなどのADCではデジタルノイズ混入しまくりだし、精度も10ビットしか取れないので震度計算の目的には使えないでしょう

 

P.S. 2024/10/1 回路図誤記修正(MCP3004電源)

 

② 実装と配線

アナロググランドは一点アース、この程度の周波数だとそれがベストだろう

③ 機能確認

・OLCD

https://qiita.com/tkarube/items/6808538012cba499d5e2

CircuitPythonのライブラリを使うのが今は推奨だが、動くだけなら旧ライブラリでも動くようだ

・加速度センサー

https://note.com/upyc101/n/nd3a1d606adf2

静止状態で一万回読み出して、最大値と最小値は、

python ./main.py
1259 1262
2045 2048
1843 1847

z, y, x座標の値ですが、z軸の値は重力加速度が加算(モジュールを裏返しにしている)されます。ガル値の計算では測定値からオフセットは差し引いていますが

S/Nでフルスケールに対してノイズレベルはおよそ60dB(およそ2/2000)はとれてるからまあまあではないか、

サンプルの最大・最小値を求めるために、リンクのコードのmain.pyは以下に変更、

import sub1

value_array = [
	[4000, 0],
	[4000, 0],
	[4000, 0]
]
adc = sub1.adc()    # クラス adc のインスタンスを作成

for j in range(10000):
	for i in range(3):
		c = adc.rdadc(i) 
		if c > value_array[i][1]:
			value_array[i][1] = c
		if c < value_array[i][0]:
			value_array[i][0] = c 
print(value_array[0][0], value_array[0][1])
print(value_array[1][0], value_array[1][1])
print(value_array[2][0], value_array[2][1])

 

admin

地震計を作ってみる

日本はいつも地震がどこかで発生しているから、自宅の揺れぐらいは観測してみたい。

https://greensoybean.hatenablog.com/search?q=地震計

を参考に作ってみる、最終的にはコードはRustにしてしまうつもりだけど、

地震計用のラズパイzeroはだいぶ前に調達済みで、地震計用の加速度計、三端子レギュレータ、ユニバ基板とコネクタ、ADコン、OLEDは部品調達して、基盤においてみた。

次のステップはアナロググランドとデジタルグランドをセパレーションして配線すること。

 

admin