2025年11月12日

ラズピコでナイトライダー ナイト2000のスキャナ作り #3

これまでRaspberry Pi Picoでナイト2000のスキャナを再現して来ました
が、どうもプログラムが無駄に長い気がしてあちこちの文献を漁っては、
もっと簡潔にまとめられないか試行錯誤しておりました。

しかし実際には上手く動作させられず思い通りの動きにならなかったので、
ここはいっそGrokさんに頼ってみようかとプログラムをぶん投げてみた所、
いともあっさり簡潔かつ完璧に作り直してくれたので、これをラズピコへ
転送して完成とします。

RPKittScanner_06.png

回路はこれまでと同じです。31 ADC0/33 GND/35 ADC_VREFに10kΩの
可変抵抗を付け、往復速度が可変出来るようになっていますが、最大の時に
視認出来ない速度で往復してしまうので、その辺はプログラムを変更すると
良いかと( (Potvalue / 16384) * 0.1 の辺り)。

※seesaaブログは行頭のスペースが削除されてしまうため、「 」で
入れているので、もしかしたらインデントがおかしいかも知れません。適宜
修正してください。テキストファイルはこちら

from machine import ADC, Pin, PWM
from time import sleep

# LEDの配列を作成
leds = [PWM(Pin(i)) for i in range(8)]
for led in leds:
  led.freq(1000) # LED周波数 1kHz

Speedpot = ADC(Pin(26))
value1 = 65535 # フル点灯
value2 = 20000
value3 = 8000
value4 = 3000
value5 = 1000 # 最低残光レベル(通過後オフ)
value0 = 0
LightUp = 0.05
LightDn = 0.05

# FullyLightup: すべてのLEDを同時に点灯・消灯(滑らかに拡張)
def set_all_leds(value):
  for led in leds:
    led.duty_u16(value)

# アップ: 0からvalue1まで10ステップで滑らかに増加
num_up_steps = 10
for step in range(num_up_steps + 1):
  v = int(value1 * step / num_up_steps)
  set_all_leds(v)
  sleep(LightUp)
sleep(0.1) # 最大輝度で待機

# ダウン: value1から0まで8ステップで滑らかに減少
num_down_steps = 8
for step in range(num_down_steps + 1):
  v = int(value1 * (num_down_steps - step) / num_down_steps)
  set_all_leds(v)
  sleep(LightDn)
sleep(0.2) # 消灯後待機

# レベル移行マップ(v5後オフで完全消灯)
level_map = {
  value1: value2,
  value2: value3,
  value3: value4,
  value4: value5,
  value5: value0 # 尾通過後オフ
}

# メインループ: 状態を逐次更新して滑らかな往復を実現
led_values = [value0] * 8 # 現在のLED明るさ
head_pos = 0 # ヘッド位置(LED1=0, LED8=7)
direction = 1 # 移動方向(右=1, 左=-1)

while True:
  Potvalue = Speedpot.read_u16()
  Interval = (Potvalue / 16384) * 0.1

# すべてのLEDをdim(移行)
new_values = [level_map.get(v, value0) for v in led_values]

# 新しいヘッドをフル点灯
new_values[head_pos] = value1

# LEDに適用
for i in range(8):
  leds[i].duty_u16(new_values[i])

  sleep(Interval)

# 状態更新
  led_values = new_values

# ヘッド移動(端で方向反転)
next_pos = head_pos + direction
  if next_pos < 0 or next_pos > 7:
    direction = -direction
    next_pos = head_pos + direction
  head_pos = next_pos



作り直してもらうにあたって、以下の点を修正しました。

・電源ON時の全点灯/全消灯をもっと滑らかに
・往復時の尾(残光)を4つ→5つに

かなり実物の点灯に近くなったと思います。いや~プログラムが難し過ぎて
簡潔にまとめるのを若干諦めかけていたんですが、Grokさん凄いな!

何の注釈もないただの命令の羅列で「LEDを往復点灯させるプログラム」と
だけ書いて渡したのに、可変抵抗で速度が変わるとか全点灯するとか細かな
部分まで解析して、あっという間にまとめ上げてくれるんだから。

まぁ、自分の知識は全く増えないんですけどね…😏

さすがに1回では無理で、提案されたプログラムをThonnyに貼り付けては
試し、おかしな所を指摘するという形で何度か作り直してもらっていますが、
それでもここまでの形になるまで30分もかからなかったんじゃないかな。


という訳で、ラズパイでナイト2000のスキャナ作りはこれで完成って事に
しようと思います。多分Grok使わなかったら、何年も完成しなかったかも。



完成形は動画でどうぞ(26秒 音声なし)。今回は速度可変はせず、ほど良い
速度で往復させています。かなりそれっぽくなったのではないでしょーか。
良い感じです。

電球バージョンとの違いは残光でしょうか。電球の場合はゆっくり往復の時
残光は短くなり、速く往復では残光が長くなります。しかしこのPWM制御の
場合は必ず残光が5個になるので、ゆっくり動かしていると若干不自然では
あります。

ラズピコ本体で¥1,000ほど、LEDとトランジスタ、抵抗を各8個揃えても
¥1,000もしないでしょうから、¥2,000弱で作れます。LEDをアンバー色に
交換してK.A.R.R.仕様にするのも楽しいですね。

違反になるので付けませんが、これ付けて走ってみたいなぁ~…。ちなみに
公道を走行する車両の場合、すぐに点灯出来る状態になっているとアウト
です。
走行中だけスイッチを切っていればおkという話ではなく、光源となるもの
(LEDや電球)を抜いた状態にしてあり、そもそも配線が繋がっていないとか、
そこまでしないと車検にも通りません。

マイケル・ナイト気分で走ってみたい…。



posted by ゆう at 2025年11月12日| Comment(0) | ラズピコ | このブログの読者になる | 更新情報をチェックする

2025年11月08日

ラズピコでナイトライダー ナイト2000のスキャナ作り #2

先日から作り始めたラズピコでのナイト2000スキャナ作り、もうちょっと
段階的に進めて行く予定でしたが、何となくそれっぽいものが出来上がって
しまったので、ひとまずの完成という事にしておきます。

RPKittScanner_01.jpg

回路は前回作ったものと変わりません。まぁマイコンを使った電子工作は、
プログラムがメインですからね…。

前回は8個のLEDを往復させる所まで作りました。脳内では可変抵抗を使い
往復速度の可変、電源を入れた時の全点灯、各LEDに残光を付ける、という
形で段取りを考えていたのですが、個別にやっていると訳が分からなくなり
そうな気がしたので、一気にバーっと作ってしまった訳です。

結果、プログラムは下記のようになりました(長いです)。#から始まる行は
コメントアウトです。一応分かる範囲で注釈を入れて置きました(赤字)。

from machine import ADC,Pin,PWM
from time import sleep

led1 = PWM(Pin(0)) #0~7ピンをPWM出力に設定
led2 = PWM(Pin(1))
led3 = PWM(Pin(2))
led4 = PWM(Pin(3))
led5 = PWM(Pin(4))
led6 = PWM(Pin(5))
led7 = PWM(Pin(6))
led8 = PWM(Pin(7))
led1.freq(1000) #各LEDのPWM周波数を1kHzに設定
led2.freq(1000)
led3.freq(1000)
led4.freq(1000)
led5.freq(1000)
led6.freq(1000)
led7.freq(1000)
led8.freq(1000)

Speedpot = ADC(Pin(26)) #26ピンADC0を変数Speedpotに設定


value1 = 65535 #最大輝度(デューディ比100%)
value2 = 20000 #残光高輝度(デューティ比30%)
value3 = 8000 #残光中輝度(デューティ比12%)
value4 = 3000 #残光低輝度(デューティ比4.5%)
value0 = 0 #消灯(デューティ比0%)
LightUp = 0.05 #全点灯 明るくなる間隔
LightDn = 0.2 #全点灯 暗くなる間隔

#Interval = 0.07
#Potvalue = Speedpot.read_u16()
#Interval = (Potvalue / 16384) *0.1
#print(Interval)

#FullyLightup
led1.duty_u16(value4) #電源ON時の全点灯
led2.duty_u16(value4)
led3.duty_u16(value4)
led4.duty_u16(value4)
led5.duty_u16(value4)
led6.duty_u16(value4)
led7.duty_u16(value4)
led8.duty_u16(value4)
sleep(LightUp)
led1.duty_u16(value3)
led2.duty_u16(value3)
led3.duty_u16(value3)
led4.duty_u16(value3)
led5.duty_u16(value3)
led6.duty_u16(value3)
led7.duty_u16(value3)
led8.duty_u16(value3)
sleep(LightUp)
led1.duty_u16(value2)
led2.duty_u16(value2)
led3.duty_u16(value2)
led4.duty_u16(value2)
led5.duty_u16(value2)
led6.duty_u16(value2)
led7.duty_u16(value2)
led8.duty_u16(value2)
sleep(LightUp)
led1.duty_u16(value1)
led2.duty_u16(value1)
led3.duty_u16(value1)
led4.duty_u16(value1)
led5.duty_u16(value1)
led6.duty_u16(value1)
led7.duty_u16(value1)
led8.duty_u16(value1)
sleep(0.3)

led1.duty_u16(value2) #全点灯を消灯する
led2.duty_u16(value2)
led3.duty_u16(value2)
led4.duty_u16(value2)
led5.duty_u16(value2)
led6.duty_u16(value2)
led7.duty_u16(value2)
led8.duty_u16(value2)
sleep(LightDn)
led1.duty_u16(value3)
led2.duty_u16(value3)
led3.duty_u16(value3)
led4.duty_u16(value3)
led5.duty_u16(value3)
led6.duty_u16(value3)
led7.duty_u16(value3)
led8.duty_u16(value3)
sleep(LightDn)
led1.duty_u16(value4)
led2.duty_u16(value4)
led3.duty_u16(value4)
led4.duty_u16(value4)
led5.duty_u16(value4)
led6.duty_u16(value4)
led7.duty_u16(value4)
led8.duty_u16(value4)
sleep(LightDn)
led1.duty_u16(value0)
led2.duty_u16(value0)
led3.duty_u16(value0)
led4.duty_u16(value0)
led5.duty_u16(value0)
led6.duty_u16(value0)
led7.duty_u16(value0)
led8.duty_u16(value0)

sleep(0.2)

while True:

Potvalue = Speedpot.read_u16() #可変抵抗の値を読み取ってPotvalueに入れる
Interval = (Potvalue / 16384) *0.1 # Potvalueの値を16,384で割って×0.1したものをIntervalに入れる

#Outbound #往路点灯
#LED1
led1.duty_u16(value1)
led5.duty_u16(value0)
led4.duty_u16(value4)
led3.duty_u16(value3)
led2.duty_u16(value2)
sleep(Interval)
Potvalue = Speedpot.read_u16()
Interval = (Potvalue / 16384) *0.1
led1.duty_u16(value0)

#LED2
led2.duty_u16(value1)
led1.duty_u16(value2)
led4.duty_u16(value0)
led3.duty_u16(value4)
sleep(Interval)
Potvalue = Speedpot.read_u16()
Interval = (Potvalue / 16384) *0.1
led2.duty_u16(value0)

#LED3
led3.duty_u16(value1)
led1.duty_u16(value3)
led2.duty_u16(value2)
sleep(Interval)
Potvalue = Speedpot.read_u16()
Interval = (Potvalue / 16384) *0.1
led3.duty_u16(value0)

#LED4
led4.duty_u16(value1)
led1.duty_u16(value4)
led2.duty_u16(value3)
led3.duty_u16(value2)
sleep(Interval)
Potvalue = Speedpot.read_u16()
Interval = (Potvalue / 16384) *0.1
led4.duty_u16(value0)

#LED5
led5.duty_u16(value1)
led1.duty_u16(value0)
led2.duty_u16(value4)
led3.duty_u16(value3)
led4.duty_u16(value2)
sleep(Interval)
Potvalue = Speedpot.read_u16()
Interval = (Potvalue / 16384) *0.1
led5.duty_u16(value0)

#LED6
led6.duty_u16(value1)
led2.duty_u16(value0)
led3.duty_u16(value4)
led4.duty_u16(value3)
led5.duty_u16(value2)
sleep(Interval)
Potvalue = Speedpot.read_u16()
Interval = (Potvalue / 16384) *0.1
led6.duty_u16(value0)

#LED7
led7.duty_u16(value1)
led3.duty_u16(value0)
led4.duty_u16(value4)
led5.duty_u16(value3)
led6.duty_u16(value2)
sleep(Interval)
Potvalue = Speedpot.read_u16()
Interval = (Potvalue / 16384) *0.1
led7.duty_u16(value0)

#LED8
led8.duty_u16(value1)
led4.duty_u16(value0)
led5.duty_u16(value4)
led6.duty_u16(value3)
led7.duty_u16(value2)
sleep(Interval)
Potvalue = Speedpot.read_u16()
Interval = (Potvalue / 16384) *0.1
led8.duty_u16(value0)

#Return #復路点灯
#LED7
led7.duty_u16(value1)
led5.duty_u16(value0)
led6.duty_u16(value4)
led8.duty_u16(value2)
sleep(Interval)
Potvalue = Speedpot.read_u16()
Interval = (Potvalue / 16384) *0.1
led7.duty_u16(value0)

#LED6
led6.duty_u16(value1)
led7.duty_u16(value2)
led8.duty_u16(value3)
sleep(Interval)
Potvalue = Speedpot.read_u16()
Interval = (Potvalue / 16384) *0.1
led6.duty_u16(value0)

#LED5
led5.duty_u16(value1)
led6.duty_u16(value2)
led7.duty_u16(value3)
led8.duty_u16(value4)
sleep(Interval)
Potvalue = Speedpot.read_u16()
Interval = (Potvalue / 16384) *0.1
led5.duty_u16(value0)

#LED4
led4.duty_u16(value1)
led5.duty_u16(value2)
led6.duty_u16(value3)
led7.duty_u16(value4)
led8.duty_u16(value0)
sleep(Interval)
Potvalue = Speedpot.read_u16()
Interval = (Potvalue / 16384) *0.1
led4.duty_u16(value0)

#LED3
led3.duty_u16(value1)
led7.duty_u16(value0)
led6.duty_u16(value4)
led5.duty_u16(value3)
led4.duty_u16(value2)
sleep(Interval)
Potvalue = Speedpot.read_u16()
Interval = (Potvalue / 16384) *0.1
led3.duty_u16(value0)

#LED2
led2.duty_u16(value1)
led6.duty_u16(value0)
led5.duty_u16(value4)
led4.duty_u16(value3)
led3.duty_u16(value2)
sleep(Interval)
Potvalue = Speedpot.read_u16()
Interval = (Potvalue / 16384) *0.1
led2.duty_u16(value0)


本来プログラムというのは個々の命令の機能や使い方をしっかりと把握した
上で、それらを使って組んで行くというのが常套手段なんでしょうが…。
さすがに BASIC しか学んだ事のない自分にとっては超難解なので、様々な
ブログやプログラム例を漁っては部分的に抜き出し、パラメータや用法等を
変えて上手く動くように継ぎ接ぎで作るしかありませんね。

microPython を使いこなしている人から見たら「何これ酷い🤣」と言われ
そうですが、今の自分はこれが限界です😇

while True: 以前が初期設定と電源ON時の挙動、以降が往復ループです。
LEDをひとつ点灯させるごとに毎回可変抵抗の値を読みに行っているのは、
速度変更をなるべくリアルタイムに反映するため。ループの先頭に1個だけ
だと1往復するまで速度が変わらないので、超絶ゆっくりにするとちっとも
速度が反映されずイライラします(笑)。

配列変数の使い方などまるで分からない事だらけなので、本来ならループ等
使って簡潔にまとめられそうな所を、全部バラで書いています。
この辺りはもっと勉強して、使い方を頭に叩き込まなければならないので、
それはおいおいやって行くとします。


このプログラムをラズピコに転送します。

RPKittScanner_02.png

転送時にファイル名を main.py とする事で、PCに接続してThonnyから制御
しなくても勝手にプログラムを読み込み実行してくれるので、電源さえ供給
してやればスタンドアロンで動きます。




動画にしてみました(31秒 音声なし)。

電源ON→全点灯→往復動作で速度可変、となっています。

ラズピコは一応CPUがデュアルコア 133MHzで動いているので、こんなにも
雑で力業なプログラムでも重くならず動いてくれますが、本当は残光の表現
を6段階にしたかった…(現状は4段階)。

でも、ただでさえゴチャゴチャしているのに、この上さらに残光を増やすと
手に負えなくなりそうな気がしたので、とりあえずの4段階。
自分としては、ICで作った回路のように電解コンで残光を表現した方が自然
というか、電球らしさが出ていて好みなのですが。


今後プログラム技術が身に付いたら、もっと単純化する予定~。



posted by ゆう at 2025年11月08日| Comment(0) | ラズピコ | このブログの読者になる | 更新情報をチェックする

2025年11月03日

ラズピコでナイトライダー ナイト2000のスキャナ作り #1

11月に入りやや寒くなって来ましたが、DTMerの皆さまにおかれましては
全裸DTMが捗っておられる事とお慶び申し上げます。

さて、ここ最近ナイト2000のスキャナを作っておりますが、電子回路版が
ひと段落ついた所で、次は先日購入したRaspberry Pi Pico(以下 ラズピコ)
で同じようなものを作ってみようと思います。
実は元々ICの回路が上手く動かず悩んでいた時に、「いっそラズピコ辺りで
作った方が楽なんじゃね?🤔」と思って買ったもの。

果たして、プログラミング言語はBASICしか触った事のない自分にそんな
物が作れるのでしょうか…?
今回は手始めに、基本動作(8つのLEDが往復点灯する)をやってみる事に。


RPKittScanner_01.jpg

なかなか取っ付きにくい気がして暫く放置していたラズピコ。

RPKittScanner_02.png

ラズピコのピンアサインは、データシートによるとこうなっているようです。
入出力が豊富なので、使い方次第ではかなりの戦力になりそうですね。成程、
こりゃみんな好んで使う訳だわい。

この中のGP0~GP7までを出力端子としてLEDを光らせる訳ですが、これら
GPIO端子は数mAしか取れないようです。CPUである RP2040 と直結されて
いるので、直接LEDを駆動するのは止めた方が良いかな。


となると各LEDはトランジスタでスイッチングする事になりますが、LEDと
ラズピコで電源を個別に用意しなければならないのか…面倒だな。

RPKittScanner_03.jpg

…と思っていたのですが、基板を良く見るとUSBの VBUS と40ピン VBUS
直結している模様。ならラズピコをUSBで動かし、同時に40ピンからLEDの
電源も取れるよね~。この構造は有難い。LED1個当たり最大20mA流したと
して、全点灯でも160mA。USB2.0でも規格内です。


RPKittScanner_04.png

てな訳でこんな回路に。LEDの電流制限抵抗に240Ωを各個入れてあります。
全てまとめて1個の抵抗でも事足りるとは思いますが、今後起動時に全点灯
させたり残光の処理なども考えているので、それを踏まえ個別にしました。
電源電圧5V、LEDのVfが1.72Vなので、13.7mA流れます。


RPKittScanner_05.jpg

完成。


しかしまぁ当然ながら、これだけでは望む動作はしない訳です。プログラム
しないとね~…。

ラズピコは言語に microPython か C/C++ が使えますが、両者を大まかに
比較した限りでは圧倒的に microPython の方が(言語の扱い的にも、環境を
整える手間的にも)楽そうなので、microPython で行きますか。

microPython は C/C++よりも実行速度が遅いらしいけど、ただLEDを8個
数ミリ秒おきに光らせるだけの回路、気にする必要は全くないでしょう。

という訳でインターネッツ上のLチカ記事を幾つか読んで、ごく簡単にLED
1~8を往復させる処理を書いてみました。
from machine import Pin
import time

led1 = Pin(0, Pin.OUT)
led2 = Pin(1, Pin.OUT)
led3 = Pin(2, Pin.OUT)
led4 = Pin(3, Pin.OUT)
led5 = Pin(4, Pin.OUT)
led6 = Pin(5, Pin.OUT)
led7 = Pin(6, Pin.OUT)
led8 = Pin(7, Pin.OUT)

Interval = 0.08

while True:
led1.value(1)
time.sleep(Interval)
led1.value(0)

led2.value(1)
time.sleep(Interval)
led2.value(0)

led3.value(1)
time.sleep(Interval)
led3.value(0)

led4.value(1)
time.sleep(Interval)
led4.value(0)

led5.value(1)
time.sleep(Interval)
led5.value(0)

led6.value(1)
time.sleep(Interval)
led6.value(0)

led7.value(1)
time.sleep(Interval)
led7.value(0)

led8.value(1)
time.sleep(Interval)
led8.value(0)

led7.value(1)
time.sleep(Interval)
led7.value(0)

led6.value(1)
time.sleep(Interval)
led6.value(0)

led5.value(1)
time.sleep(Interval)
led5.value(0)

led4.value(1)
time.sleep(Interval)
led4.value(0)

led3.value(1)
time.sleep(Interval)
led3.value(0)

led2.value(1)
time.sleep(Interval)
led2.value(0)


各命令は「Raspberry Pi Pico Lチカ」などで検索すると、細かく解説して
いるサイトが多数出て来るので、ここでは詳細は割愛します。
これも変数を使って BASIC で言うところの FOR ~ NEXT のような命令で
繰り返せば、短くまとめて単純化出来るとは思いますが、そこまでの知識が
ないので今はダラダラとLEDごとに書き連ねるだけ。
多分もっと簡潔に出来るはず。


Interval = 0.08

でLEDが点灯している時間を決めています。この値を変える事で往復速度を
変えられます。0.08で通常の速度、0.04にするとマイケルの命令で何か作業
を行う時のやや速め往復くらいになります。

なおプログラムの転送には定番の Thonny を使っています。

今回は往復点灯させるのが目的なので速度は固定ですが、いずれはスイッチ
操作か可変抵抗で自由に速度を変えられるようにする予定~。



このようになります(17秒 音声なし)。
残光が全くないので味気なく往復しますが、まず第一段階は成功。

残光の処理が難しそうですね~。ICで作った回路のように各LEDに電解コン
をぶら下げて済ますか、プログラム上でPWM処理をさせるか…。
PWMの場合、8→1と戻って来た時の折り返し部分の処理をどうすれば良い
のかが悩みどころ。プログラムはループしているので、折り返し時に残光が
途切れたり不自然な光り方をする気がしないでもない。

まぁ残光についてはぼちぼちネット記事などを参考に考えてみます。次回は、
スイッチか可変抵抗で往復速度を可変出来るようにしてみる予定~。


???「暇なんですね、マイケル…😏」



posted by ゆう at 2025年11月03日| Comment(0) | ラズピコ | このブログの読者になる | 更新情報をチェックする
最近の記事