2025年3月2日日曜日

マイコン内蔵LEDの接続

インタフェース2月号のラズパイで作り学ぶDockerコンテナの再開。
ページ内で、マイコン内蔵RGBLEDテープを使うところがあり、

秋月電子で、探していたところ、それっぽいものがあった。
https://akizukidenshi.com/catalog/g/g116029/
で、物が届いた後に気が付いた。あれ、これって表面実装部品じゃないですか?
ヒートガンはあるけど、ペーストはんだもない。 というわけで、2.5ミリ間隔の部分にはんだごてでのはんだ付け。 ワイヤストリッパーもないので、被覆をはさみで切りこみを入れたあとねじって引き抜いて、
作業シートの上にセロハンテープで固定して、
老眼鏡をかけてはんだづけ。
以下のページをみて、ピンアサインと配線例を確認し、 https://www.peace-corp.co.jp/data/WS2812B-V5_V1.0_EN.pdf
このページのコードを流用して、 https://howtojp.com/a/sensors/Raspberry-Pi-WS2812B-control import time import board import neopixel # WS2812Bの設定 pixel_pin = board.GP18 # デジタルピン18に接続した場合 num_pixels = 2 # LEDの数 ORDER = neopixel.GRB # カラーオーダー pixels = neopixel.NeoPixel( pixel_pin, num_pixels, brightness=0.2, auto_write=False, pixel_order=ORDER ) # 色の定義 red = (255, 0, 0) green = (0, 255, 0) blue = (0, 0, 255) # 全てのLEDを順番に点灯 for i in range(num_pixels): pixels[i] = red pixels.show() time.sleep(1) pixels[i] = green pixels.show() time.sleep(1) pixels[i] = blue pixels.show() time.sleep(1) で、動かした結果こんな感じ。
このとき2番目のLEDがなぜか暗かったけど、配線をし直したら明るくなった。 そのほか、BME280もピンヘッダとジャンパをはんだ付けしなきゃならない。インタフェースの特集記事への着手への道のりは先は長そう?

2025年3月1日土曜日

picoのHIDデバイス化備忘録

いろいろ至らない部分があるが、ほかにやりたいことがあるので、こちらはいったん終了。
作業メモだけ残す。

配線:
ボタン1~10をGNDと、GP0,GP1,GP2,GP3,GP4,GP28,GP27,GP26,GP5,GP6に接続
ロータリーエンコーダ1を、GP17,GND,GP16に接続。
ロータリーエンコーダ2を、GP19,GND,GP18に接続。
SSD1302の、GND,VCC,SCL,SDAを、GND,3.3V,GP15,GP14に接続。

ライブラリファイル配置。
PIのボタンを押しながらPCに接続する。
PIのドライブにflash_nuke.uf2をコピーしてストレージを初期化。
PIのドライブにadafruit-circuitpython-raspberry_pi_pico2-ja-9.2.3.uf2をコピーしてcircuitpy環境にする。
adafruit-circuitpython-bundle-py-20250201\examples\framebuf\font5x8.bin をcircuitpyドライブ直下にコピー。
adafruit-circuitpython-bundle-py-20250201\libの
adafruit_framebuf.mpy,adafruit_ssd1306.mpy,adafruit_hid/をcircuitpyのlibフォルダにコピー。
pico_MicroPython_misakifont-main.zip\pico_MicroPython_misakifont-main\の
misakifontフォルダをcircuitpyのlibフォルダにコピー。
mode.iniをcircuitpyドライブ直下に作成。内容は0の1バイトのみ。

circuitpyドライブ直下のboot.pyを以下の内容に差し替える。

import os
import storage
import board
import digitalio

switch = digitalio.DigitalInOut(board.GP6)

switch.direction = digitalio.Direction.INPUT
switch.pull = digitalio.Pull.UP

if switch.value:
    #NormalMode
    storage.disable_usb_drive()
    usb_cdc.disable()
else :
  #Maintenance
    pass            


circuitpyドライブ直下のcode.pyを以下の内容に差し替える。


import digitalio
import busio
import adafruit_ssd1306
import time
import usb_hid
import json
from board import *
from adafruit_hid.keyboard import Keyboard
from adafruit_hid.keyboard import Keycode
from adafruit_hid.keyboard_layout_us import KeyboardLayoutUS
from adafruit_hid.consumer_control import ConsumerControl
from adafruit_hid.consumer_control_code import ConsumerControlCode
from adafruit_hid.mouse import Mouse
from misakifont import MisakiFont
i2c = busio.I2C(GP15, GP14)
display = adafruit_ssd1306.SSD1306_I2C(128, 64, i2c, addr=0x3C)
mode=0
try:
    with open('mode.ini', 'r') as file:
        mode=int(file.read())
    with open('mode.ini', 'w') as file:
        file.write("{}".format(mode))
except Exception as e:
    display.text("MAINTENANCE MODE", 16, 30, True, font_name="font5x8.bin", size=1)
    display.show()
#    raise e


cc = ConsumerControl(usb_hid.devices)
keyboard = Keyboard(usb_hid.devices)
layout=KeyboardLayoutUS(keyboard)
mouse = Mouse(usb_hid.devices)

s1 = digitalio.DigitalInOut(GP17)
s1.direction = digitalio.Direction.INPUT
s1.pull = digitalio.Pull.UP
s2 = digitalio.DigitalInOut(GP16)
s2.direction = digitalio.Direction.INPUT
s2.pull = digitalio.Pull.UP
n_s1=o_s1=s1.value
n_s2=o_s2=s2.value

s3 = digitalio.DigitalInOut(GP19)
s3.direction = digitalio.Direction.INPUT
s3.pull = digitalio.Pull.UP
s4 = digitalio.DigitalInOut(GP18)
s4.direction = digitalio.Direction.INPUT
s4.pull = digitalio.Pull.UP
n_s3=o_s3=s3.value
n_s4=o_s4=s4.value
mf = MisakiFont()
sw=[]
n_sws=[True,True,True,True,True,True,True,True,True,True]
o_sws=[True,True,True,True,True,True,True,True,True,True]

isDisplay=False

def show_bitmap(x,y,fd):
    for row in range(0,7):
        for col in range(0,7):
            display.pixel(x+col,y+row,1 if (0x80>>col) & fd[row] else 0)


def DisplayMenu(menuNo):
    global isDisplay,strs,i2c,display
    i2c.deinit()
    i2c = busio.I2C(GP15, GP14)
    display = adafruit_ssd1306.SSD1306_I2C(128, 64, i2c, addr=0x3C)

    display.fill(0) # 表示内容消去
    y=1
    for str in strs[menuNo]:
        i=0
        for c in str:
            d = mf.font(ord(c))
            show_bitmap(i,y,d)
            i+=8
        y+=10
    display.show()
    isDisplay=True


strs=(
    ("――モード1 スタンダード――",
     "1.コピー 2.カット",         #Ctrl+C,Ctrl+X
     "3.はりつけ4.すべてせんたく",   #Ctrl+V,Ctrl+A
     "5.やり直し6.再実行",       #Ctrl+Z,Ctrl+Y
     "7.デスク 8.ウィンドウ閉じる", #Win+D,Ctrl+W
     "上ボリューム下Ctrl+ホイール",),
    ("――モード2 YouTube――",
     "1.停止再生 2.ミュート",      #k,m
     "3.10秒進む4.10秒もどる",  #l,j
     "5.前フレーム 6.次フレーム",    #.,,
     "7.全画面8.小ウィンドウ",      #f,i
     "上.ボリューム 下.明るさ",),
    ("――モード3 Winキー ――",
     "1.ゲームバー 2.文字起こし", #G,H
     "3.設定画面 4.Winロック",   #I,L
     "5.画面設定6.クリップりれき",       #P,V
     "7.拡大表示 8.縮小表示",   #+,-
     "上マウス左右 下マウス上下",),
    ("――モード4 メッセージ送信 ――",
     "1.Hello 2.Bye", 
     "3.OK    4.NG", 
     "5.login 6.logout",
     "7.prev  8.next",
     "上マウス左右 下マウス上下",),
    )
#command 1:kbd.press&release,2:cc.press&release,3:layout.write,4:Mouse.X,5:Mouse.Y,6:MouseWheel,7:Ctrl+MouseWheel,8:MenuChange,9:MenuDisplayOn/OFF
btn_cmds=((
    (1,(Keycode.LEFT_CONTROL,Keycode.C,)),(1,(Keycode.LEFT_CONTROL,Keycode.X,)),
    (1,(Keycode.LEFT_CONTROL,Keycode.V,)),(1,(Keycode.LEFT_CONTROL,Keycode.A,)),
    (1,(Keycode.LEFT_CONTROL,Keycode.Z,)),(1,(Keycode.LEFT_CONTROL,Keycode.Y,)),
    (1,(Keycode.WINDOWS,Keycode.D,)),(1,(Keycode.LEFT_CONTROL,Keycode.W,)),
    (8,),(9,)),
   (
    (1,(Keycode.K,)),(1,(Keycode.M,)),(1,(Keycode.L,)),(1,(Keycode.J,)),
    (1,(Keycode.PERIOD,)),(1,(Keycode.COMMA,)),(1,(Keycode.F,)),(1,(Keycode.I,)),
    (8,),(9,)),
   (
    (1,(Keycode.WINDOWS,Keycode.G,)),(1,(Keycode.WINDOWS,Keycode.H,)),(1,(Keycode.WINDOWS,Keycode.I,)),(1,(Keycode.WINDOWS,Keycode.L,)),
    (1,(Keycode.WINDOWS,Keycode.P,)),(1,(Keycode.WINDOWS,Keycode.V,)),(1,(Keycode.WINDOWS,Keycode.KEYPAD_PLUS,)),(1,(Keycode.WINDOWS,Keycode.KEYPAD_MINUS,)),
    (8,),(9,)),
   (
    (3,"Hello"),(3,"Bye"),(3,"OK"),(3,"NG"),(3,"login"),(3,"logout"),(3,"prev"),(3,"next"),(8,),(9,))
   )
#1:NormalWheel,2:Ctrl+Wheel,3:Volume,4:Brightness,5:MouseX,6:MouseY
rotally_cmds=((3,2),(3,4),(5,6),(5,6))


def buttonAction(button):
    global mode
    global isDisplay
    global btn_cmds
    global display
    global keyboard,cc,layout
    if btn_cmds[mode][button][0] == 1:
        keyboard.press(*(btn_cmds[mode][button][1]))
        keyboard.release(*(btn_cmds[mode][button][1]))
    elif btn_cmds[mode][button][0] == 2:
        cc.press(*(btn_cmds[mode][button][1]))
        cc.release(*(btn_cmds[mode][button][1]))
    elif btn_cmds[mode][button][0] == 3:
        layout.write(btn_cmds[mode][button][1])

    elif btn_cmds[mode][button][0] == 8:
        mode= (mode +1 )%4
        DisplayMenu(mode)
        with open('mode.ini', 'w') as file:
            file.write("{}".format(mode))
    elif btn_cmds[mode][button][0] == 9:
        if isDisplay :
            display.fill(0)
            display.show()
            isDisplay=False
        else :
            DisplayMenu(mode)

#1:NormalWheel,2:Ctrl+Wheel,3:Volume,4:Brightness,5:MouseX,6:MouseY
def rotallyAction(kind,direction):
    global mode
    global rotally_cmds
    global keyboard,cc,mouse
    if rotally_cmds[mode][kind] == 1:
        mouse.move(0,0,direction)
    elif rotally_cmds[mode][kind] == 2:
        keyboard.press(Keycode.LEFT_CONTROL)
        mouse.move(0,0,direction)
        keyboard.release(Keycode.LEFT_CONTROL)
    elif rotally_cmds[mode][kind] == 3:
        if direction > 0 :
            cc.send(ConsumerControlCode.VOLUME_INCREMENT)
        else :
            cc.send(ConsumerControlCode.VOLUME_DECREMENT)
    elif rotally_cmds[mode][kind] == 4:
        if direction > 0 :
            cc.send(ConsumerControlCode.BRIGHTNESS_INCREMENT)
        else :
            cc.send(ConsumerControlCode.BRIGHTNESS_DECREMENT)
    elif rotally_cmds[mode][kind] == 5:
        mouse.move(direction,0,0)
    elif rotally_cmds[mode][kind] == 6:
        mouse.move(0,direction,0)

for _gpio in GP0,GP1,GP2,GP3,GP4,GP28,GP27,GP26,GP5,GP6:
    s = digitalio.DigitalInOut(_gpio)
    s.direction = digitalio.Direction.INPUT
    s.pull = digitalio.Pull.UP
    sw.append(s)
    try:
        while True:
            nn_s1 , nn_s2= s1.value , s2.value
            if (nn_s1 != n_s1 or nn_s2 != n_s2) :
                n_s1,n_s2 =nn_s1,nn_s2
            elif(n_s1 != o_s1 or n_s2 != o_s2) :
                if n_s1 ^ o_s2 == 0 :
                    rotallyAction(0,1)
                else:
                    rotallyAction(0,-1)
                o_s1,o_s2=n_s1,n_s2

            nn_s3 , nn_s4= s3.value , s4.value
            if (nn_s3 != n_s3 or nn_s4 != n_s4) :
                n_s3,n_s4 =nn_s3,nn_s4
            elif(n_s3 != o_s3 or n_s4 != o_s4) :
                if n_s3 ^ o_s4 == 0 :
                    rotallyAction(1,1)
                else:
                    rotallyAction(1,-1)
                o_s3,o_s4=n_s3,n_s4
            for i in range(10):
                n_sws[i] = sw[i].value
                if ((o_sws[i]==True)  and (n_sws[i]==False)):
                    buttonAction(i)
                o_sws[i]=n_sws[i]
                time.sleep(0.0002)
    except KeyboardInterrupt:
    #        raise
            pass
    except Exception as e:
            pass


2025年2月24日月曜日

Raspberrypi picoでのHIDキーアサイン画面表示案

キーアサインと画面表示の案。
一番左の黒のボタンはモードや画面の切り替えに使うとして、8個のキーとロータリーエンコーダのキーアサイン案
from misakifont import MisakiFont
import busio
import adafruit_ssd1306
from board import *
i2c = busio.I2C(GP15, GP14)
display = adafruit_ssd1306.SSD1306_I2C(128, 64, i2c, addr=0x3C)
display.fill(0) # 表示内容消去

"""
 フォントのビットマップ表示
"""
def show_bitmap(x,y,fd):
    for row in range(0,7):
        for col in range(0,7):
            display.pixel(x+col,y+row,1 if (0x80>>col) & fd[row] else 0)

strs=(
    ("――モード1 スタンダード――",
     "1.コピー 2.カット",         #Ctrl+C,Ctrl+X
     "3.はりつけ4.すべてせんたく",   #Ctrl+V,Ctrl+A
     "5.やり直し6.再実行",       #Ctrl+Z,Ctrl+Y
     "7.デスク 8.ウィンドウ閉じる", #Win+D,Ctrl+W
     "上ボリューム下Ctrl+ホイール",),
    ("――モード2 YouTube――",
     "1.停止再生 2.ミュート",      #k,m
     "3.10秒進む4.10秒もどる",  #l,j
     "5.前フレーム 6.次フレーム",    #.,,
     "7.全画面8.小ウィンドウ",      #f,i
     "上ボリューム下Ctrl+ホイール",),
    ("――モード3 Winキー ――",
     "1.ゲームバー 2.文字起こし", #G,H
     "3.設定画面 4.Winロック",   #I,L
     "5.画面設定6.クリップりれき",       #P,V
     "7.拡大表示 8.縮小表示",   #+,-
     "上マウス左右 下マウス上下",),
    )
mf = MisakiFont()
y=1
for str in strs[2]:
    i=0
    for c in str:
        d = mf.font(ord(c))
        show_bitmap(i,y,d)
        i+=8
    y+=10
display.show()


表示はこんな感じ。

KeyboardLayout.writeを使って文字列を送出することもできるけど、使う場面がないかな。

ロータリーエンコーダのよみとり(エッジ変化の読み取り)

前回までロータリーエンコーダのON,OFFの状態
を見ていたが、今回はOFF→ON(立ち上がり)ON→OFF(立ち下り)
の変化を見てみることにする。
→状態変化をイベントとして受け取ることにより、circuitpythonが
(バタつきなく)正しく読み取っていることを期待するとともに、
よくわからない待ち時間の考慮をなくす。

その前にロータリーエンコーダの仕組みを復習。
ロータリーエンコーダは、それを回すと、
...OFF→ON→ON→OFF→OFF→ON..と状態を4つで
1周期を繰り返すスイッチが2個あり、
それぞれのスイッチは他方より4分の1周期ずれているもの。

前回はスイッチを回したときのスイッチの状態に着目して、
一方のスイッチの状態ともう片方の直前の状態をを比較したときに、
同じであれば順方向、逆であれば逆方向となる都合の良い動きに着目し、
それらを排他的論理和を取ることにより方向を調べていた。

今回はスイッチを回したときのスイッチの変化に着目して、
同時に両方のスイッチは変化しない(スイッチの変化は1個づつ)、
自身の今回の変化(立ち上がり、立ち下り)の方向と
相手側の前回の変化の方向を比較し、
自身がSW1なら過去のSW2の方向変化が同じであれば順方向、違えば逆方向
自身がSW2なら過去のSW1の方向変化が同じであれば逆方向、違えば順方向
となる都合の良い動きに着目する。
前回は起動直後の状態をとれたので、最初からデータを信用できたが、
今回はプログラム起動直前の状態変化は取れないため、
最初の2カウント分の読み取りは破棄する。

ま、このページのものを参考にして
https://learn.adafruit.com/key-pad-matrix-scanning-in-circuitpython/keys-one-key-per-pin

自分用に書き換えたのが以下のプログラム
# SPDX-FileCopyrightText: 2022 Dan Halbert for Adafruit Industries
#
# SPDX-License-Identifier: MIT

import board
import keypad

KEY_PINS = (
    board.GP0,
    board.GP1,
    board.GP2,
    board.GP3,
    board.GP4,
    board.GP28,
    board.GP27,
    board.GP26,
    board.GP5,
    board.GP6,
    board.GP16, #RE1S1
    board.GP17, #RE1S2
    board.GP18, #RE2S1
    board.GP19, #RE2S2
)

keys = keypad.Keys(KEY_PINS, value_when_pressed=False, pull=True)
waitcount=2
isRE1Sw1Raise=False
isRE1Sw2Raise=False
while True:
    event = keys.events.get()
    if event:
        # A key transition occurred.
        #print(event)

        if event.pressed:
            # Turn the key blue when pressed
            if event.key_number == 10 :
                isRE1Sw1Raise=False
                if waitcount > 0:
                    waitcount -=1
                    continue
                if isRE1Sw1Raise == isRE1Sw2Raise : print("UP")
                else : print("DOWN")
            elif event.key_number == 11 :
                isRE1Sw2Raise=False
                if waitcount > 0:
                    waitcount -=1
                    continue
                if isRE1Sw1Raise != isRE1Sw2Raise : print("UP")
                else : print("DOWN")

        # This could just be `else:`,
        # since event.pressed and event.released are opposites.
        if event.released:
            if event.key_number == 10 :
                isRE1Sw1Raise=True
                if waitcount > 0:
                    waitcount -=1
                    continue
                if isRE1Sw1Raise == isRE1Sw2Raise : print("UP")
                else : print("DOWN")
            elif event.key_number == 11 :
                isRE1Sw2Raise=True
                if waitcount > 0:
                    waitcount -=1
                    continue
                if isRE1Sw1Raise != isRE1Sw2Raise : print("UP")
                else : print("DOWN")


結果、クリックなしのロータリーエンコーダでゆっくり目に回すといい感じに読み取るが、
速く回すと、逆方向の検知がされてしまう。

クリックありの方で、コメントのprintを有効にして、その他のprintを無効にした結果、以下のようにまったく使い物にならず。

















期待していたのは、11と10が交互に出るものなんだけどそうなっていない。
10が多いということは、11を拾えていないのか10がバタついているのか、その両方なのかよくわからない。

IncrementalEncoderでクリックありのものはdivisorを4にクリックなしのものはdivisorを2で使うのがよいのかなぁ。


2025年2月23日日曜日

Raspberry Pi Picoに接続したSSD1302に美咲フォントで表示


https://github.com/Tamakichi/pico_MicroPython_misakifont
からcodeボタン→Download ZIPでダウンロードした後、
misakifontフォルダをCIRCUITPYドライブのlibにコピーした後、
sample_misaki.pyをSSD1302に表示させるため以下のようにちょこっと修正して、


from misakifont import MisakiFont
import busio
import adafruit_ssd1306
from board import *
i2c = busio.I2C(GP15, GP14)
display = adafruit_ssd1306.SSD1306_I2C(128, 64, i2c, addr=0x3C)
display.fill(0) # 表示内容消去

"""
 フォントのビットマップ表示
"""
def show_bitmap(x,y,fd):
    for row in range(0,7):
        for col in range(0,7):
            display.pixel(x+col,y+row,1 if (0x80>>col) & fd[row] else 0)

str="こんにちは世界!"
mf = MisakiFont()
i=0
for c in str:
    d = mf.font(ord(c))
    show_bitmap(10+i,30,d)
    i+=8
display.show()



表示させるとこんな感じ。
フォントの大きさの自由度はあまりないけど、これでいいんじゃないかな?

raspberry pi picoでSSD1302に日本語を表示させてみた。

raspberry pi picoでSSD1302に漢字を表示させたい。
というわけで以下の順でやってみた。

1.フォントを探す
2.イメージのバイナリデータをファイル化
3.漢字コードからバイナリファイルの場所を算出するインデックスを作成
4.文字コード変換1(ShiftJIS→JIS変換)
5.文字コード変換2(UTF8→ShiftJIS変換)
6.文字画像確認その1(コンソール出力)
7.文字画像確認その2(SSD1302への出力)
8.文字列を表示する。

1.フォントを探す
https://www.mgo-tec.com/blog-entry-ssd1306-shinonome.html を見てみると東雲フォント
というものがあるみたいなので、今回はこれを(shnmk16.bdf)を使ってみる。

2.イメージのバイナリデータをファイル化
どうせメモリ上にフォントイメージを載せられないので、一字毎イメージファイルを読むことにする。
バイナリデータは1文字32バイトで保存。
そのためのshnmk16.bdfからバイナリファイルを抽出するスクリプトを作って流してみる。

def readandwritefontsfile():
    isImageArea=False
    code=0
    image=b''
    with open('shnmk16.bdf') as f:
        with open('fontImage.bin','wb') as wf:
            for str in f:
                if str[:9]=="STARTCHAR" :
                    code=int(str[10:14],16)
                elif str[:6]=="BITMAP" :
                    image=b''
                    isImageArea=True
                elif str[:7]=="ENDCHAR" :
                    wf.write(image)
                    isImageArea=False
                elif isImageArea:
                    image+=bytes.fromhex(str[:4])
    
readandwritefontsfile()

で作成されたfontImage.binのファイルサイズが、220,128バイト。
CircuitPyドライブには載せられるが、メモリ上にこのイメージを載せるのは容量的に少し不安がある。
なので、表示の都度このファイルから1文字ずつ読み出すことにする。

3.漢字コードからバイナリファイルの場所を算出するインデックスを作成
shnmk16.bdf内で定義しているコードはJISのよう。
最初の文字コード(10進数)8481からで、使われていない文字はそのコードを飛ばしていて、
フォントコードが飛び番になっている。

確認方法として、以下を実施
・「shnmk16.bdf」をキーワードENCODINGでgrepし、
・出てきた行をExcelに貼り付け、
・区切り位置ウィザードで半角スペースを区切り
・E2セルに=D2-D1-1の計算式を入れ、E列末までコピーし、値貼り付けにより計算式を数値にして
・オートフィルタでE列として、マイナス値が1件を確認したのち、値0のもののみ表示し、表示された行を行削除し、
・オートフィルタを解除して、F1セルに=E1、F2セルに=F1+E2の計算式を入れ、F2セルをF列末までコピーして
・D列(コード)、E列(飛び番)、F列(累積)を抽出して確認
確認したところ、以下のコード値で直前のコードから+1より大きい数字に飛んでいる。コード値と飛び番数は以下。
コード	飛び番	累積
8737	162	162
8762	11	173
8778	8	181
8796	11	192
8818	7	199
8830	4	203
9008	177	380
9025	7	387
9057	6	393
9249	166	559
9505	173	732
9761	170	902
9793	8	910
10017	200	1110
10065	15	1125
10273	175	1300
12321	2016	3316
12577	162	3478
12833	162	3640
13089	162	3802
13345	162	3964
13601	162	4126
13857	162	4288
14113	162	4450
14369	162	4612
14625	162	4774
14881	162	4936
15137	162	5098
15393	162	5260
15649	162	5422
15905	162	5584
16161	162	5746
16417	162	5908
16673	162	6070
16929	162	6232
17185	162	6394
17441	162	6556
17697	162	6718
17953	162	6880
18209	162	7042
18465	162	7204
18721	162	7366
18977	162	7528
19233	162	7690
19489	162	7852
19745	162	8014
20001	162	8176
20257	162	8338
20513	205	8543
20769	162	8705
21025	162	8867
21281	162	9029
21537	162	9191
21793	162	9353
22049	162	9515
22305	162	9677
22561	162	9839
22817	162	10001
23073	162	10163
23329	162	10325
23585	162	10487
23841	162	10649
24097	162	10811
24353	162	10973
24609	162	11135
24865	162	11297
25121	162	11459
25377	162	11621
25633	162	11783
25889	162	11945
26145	162	12107
26401	162	12269
26657	162	12431
26913	162	12593
27169	162	12755
27425	162	12917
27681	162	13079
27937	162	13241
28193	162	13403
28449	162	13565
28705	162	13727
28961	162	13889
29217	162	14051
29473	162	14213
29729	162	14375

これをもとに以下の関数で入力code(SJISコード)を、その値により補正値を変え、
最終的にイメージファイルの読み込みアドレスを算出する
具体的には、
・G2セルに="elif code <"&D2&" :(Alt+Enter)    code-="&E1 計算式を入れG列末までコピーして、
・G列をコピーし、テキストエディタに貼り付け、正規表現置換で\r?\nを\r\nに置換。
あとは前後を整備して、以下の関数を作る。
def binfileindex(code):
    if code < 8737 :
        pass
    elif code <8762 :
        code-=162
    elif code <8778 :
        code-=173
    elif code <8796 :
        code-=181
    elif code <8818 :
        code-=192
    elif code <8830 :
        code-=199
    elif code <9008 :
        code-=203
    elif code <9025 :
        code-=380
    elif code <9057 :
        code-=387
    elif code <9249 :
        code-=393
    elif code <9505 :
        code-=559
    elif code <9761 :
        code-=732
    elif code <9793 :
        code-=902
    elif code <10017 :
        code-=910
    elif code <10065 :
        code-=1110
    elif code <10273 :
        code-=1125
    elif code <12321 :
        code-=1300
    elif code <12577 :
        code-=3316
    elif code <12833 :
        code-=3478
    elif code <13089 :
        code-=3640
    elif code <13345 :
        code-=3802
    elif code <13601 :
        code-=3964
    elif code <13857 :
        code-=4126
    elif code <14113 :
        code-=4288
    elif code <14369 :
        code-=4450
    elif code <14625 :
        code-=4612
    elif code <14881 :
        code-=4774
    elif code <15137 :
        code-=4936
    elif code <15393 :
        code-=5098
    elif code <15649 :
        code-=5260
    elif code <15905 :
        code-=5422
    elif code <16161 :
        code-=5584
    elif code <16417 :
        code-=5746
    elif code <16673 :
        code-=5908
    elif code <16929 :
        code-=6070
    elif code <17185 :
        code-=6232
    elif code <17441 :
        code-=6394
    elif code <17697 :
        code-=6556
    elif code <17953 :
        code-=6718
    elif code <18209 :
        code-=6880
    elif code <18465 :
        code-=7042
    elif code <18721 :
        code-=7204
    elif code <18977 :
        code-=7366
    elif code <19233 :
        code-=7528
    elif code <19489 :
        code-=7690
    elif code <19745 :
        code-=7852
    elif code <20001 :
        code-=8014
    elif code <20257 :
        code-=8176
    elif code <20513 :
        code-=8338
    elif code <20769 :
        code-=8543
    elif code <21025 :
        code-=8705
    elif code <21281 :
        code-=8867
    elif code <21537 :
        code-=9029
    elif code <21793 :
        code-=9191
    elif code <22049 :
        code-=9353
    elif code <22305 :
        code-=9515
    elif code <22561 :
        code-=9677
    elif code <22817 :
        code-=9839
    elif code <23073 :
        code-=10001
    elif code <23329 :
        code-=10163
    elif code <23585 :
        code-=10325
    elif code <23841 :
        code-=10487
    elif code <24097 :
        code-=10649
    elif code <24353 :
        code-=10811
    elif code <24609 :
        code-=10973
    elif code <24865 :
        code-=11135
    elif code <25121 :
        code-=11297
    elif code <25377 :
        code-=11459
    elif code <25633 :
        code-=11621
    elif code <25889 :
        code-=11783
    elif code <26145 :
        code-=11945
    elif code <26401 :
        code-=12107
    elif code <26657 :
        code-=12269
    elif code <26913 :
        code-=12431
    elif code <27169 :
        code-=12593
    elif code <27425 :
        code-=12755
    elif code <27681 :
        code-=12917
    elif code <27937 :
        code-=13079
    elif code <28193 :
        code-=13241
    elif code <28449 :
        code-=13403
    elif code <28705 :
        code-=13565
    elif code <28961 :
        code-=13727
    elif code <29217 :
        code-=13889
    elif code <29473 :
        code-=14051
    elif code <29729 :
        code-=14213
    else :
        code-=14375
    return (code-8481)*32

d=binfileindex(29400)


4.文字コード変換1(ShiftJIS→JIS変換)
pythonの標準でのJISコード変換は見つからなかったので、SJIS→JISの変換関数を作る。
def sjis2jis(indata):
    h_data=int.from_bytes(indata)>>8
    l_data=int.from_bytes(indata) & 0xff
    if h_data<=0x9f :
        if l_data < 0x9f :
            h_data=(h_data<<1) - 0xe1
        else :
            h_data=(h_data<<1) - 0xe0
    else :
        if l_data < 0x9f :
            h_data =(h_data << 1 ) -0x161
        else:
            h_data =(h_data << 1 ) -0x160
    if l_data < 0x7f :
        l_data -= 0x1f
    elif l_data < 0x9f:
        l_data -=0x20
    else:
        l_data -=0x7e

    return h_data << 8 | l_data

5.文字コード変換2(UTF8→ShiftJIS変換)
circuitpythonでは、「'あ'.encode('shift_jis')」が使えずUTF-8で表示されてしまう。
なので自前で変換テーブルを作成する。元ネタとして、以下に簡易的なUTF-8→ShiftJISの変換の情報があるので
https://ameblo.jp/fc2miha/entry-12876272336.html
上の情報から、以下の情報を辿って、
https://drive.google.com/file/d/1dMNG8bcM58w7uHRdBbqsP2R1HGAU9K2z/view?usp=sharing
UTF-8→SJISの変換テーブル(list.dat)をダウンロードする。
このlist.datを入力にした、3バイトのUTF-8コードと2バイトのShiftJISのペアの5バイト*変換文字分のデータを作る。
次のプログラムでutf8tosjis.binを作り、

import json
def readandwriteconvfile():
    flg_firstline=True
    dic=dict()
    with open('utf8tosjis.bin','wb') as wf:
        with open('list.dat','r',encoding='utf-8') as f:
                for str in f:
                    if flg_firstline :
                        flg_firstline=False
                        continue
                    str_utf8code,str_sjiscode,_=str.split(',')
                    wf.write(int(str_utf8code,16).to_bytes(3,byteorder='big'))
                    wf.write(int(str_sjiscode,16).to_bytes(2,byteorder='big'))
readandwriteconvfile()

出来上がった変換テーブル(utf8tosjis.bin)ファイルをcircuitpyドライブ直下におく。
サイズは、46,795バイト。どうせイメージを読み込むのにファイルアクセスがあるので
こちらを高速化してもあまり意味がないが、今回は変換テーブルを起動時にファイルから読み込み
メモリ上に載せておくことにする。
  (ただ辞書にしてjson.dumpやjson.loadをすると、すごく時間がかかるのでメモリ一括読み込みし、
  キーをハッシュアクセスではなく、ループで検索することにする。)
dic=dict()
allbyte=b''
with open('utf8tosjis.bin','rb') as f:
    allbyte=f.read()

で、変換表を使ったutf8文字列→sjis文字列への変換関数を作る。
def utf8str2sjisstr(utf8str):
    retval=b''
    for utf8char in utf8str:
        utf8=utf8char.encode()
        i=0
        while i>8
    l_data=indata & 0xff
    if h_data<=0x9f :
        if l_data < 0x9f :
            h_data=(h_data<<1) - 0xe1
        else :
            h_data=(h_data<<1) - 0xe0
    else :
        if l_data < 0x9f :
            h_data =(h_data << 1 ) -0x161
        else:
            h_data =(h_data << 1 ) -0x160
    if l_data < 0x7f :
        l_data -= 0x1f
    elif l_data < 0x9f:
        l_data -=0x20
    else:
        l_data -=0x7e

    return h_data << 8 | l_data


str_mainte='メンテナンス中'
putString(utf8str2sjisstr(str_mainte),28,8)


表示はこんな感じ。
  
utf8→sjis変換部分やユーザから見えなくてもよいので、putStringの中から呼べばよかったかも。 ま、泥臭いし半角対応はできていないので、今回作ってみたものの使わないかな? 8x8の美咲フォントを使う方は、いろいろ整備されているみたいだし。

2025年2月16日日曜日

CircuitPythonにロータリーエンコーダー用のモジュールがあった。

https://docs.circuitpython.org/en/latest/shared-bindings/rotaryio/index.html
circuit pythonにロータリーエンコーダー専用のモジュールがあるのなら自作しないで使うべきでした。
で、このページをみたらクリックタイプの動きがわかりました。
秋月で買った100円のクリックタイプのロータリーエンコーダーは1クリック進むとOFF,OFF,ON,ONの1サイクル分進んでいた、と。

rotaryio.IncrementalEncoderを呼び出すときに指定する。divisorがデフォルトで4で、
これはdedents(引っ掛かり)が、(ON,ON,OFF,OFFの)1サイクルあたりに1回しかこない場合に指定するようです。
ということは、1クリック分動かすと1サイクル分うごくものがある、と、
ロータリーエンコーダをGPIO1,GND,GPIO2に接続し、
上記のcircuitpythonのページのサンプルをraspberrypipico用にピンを少し書き換えて、以下のプログラムを動かしてみました。 import rotaryio import time from board import * enc = rotaryio.IncrementalEncoder(GP1, GP2) last_position = None while True: position = enc.position if last_position == None or position != last_position: print(position) last_position = position 結果、ロータリーエンコーダを回すと+1もしくは-1をした値を返してくれた。 もう簡単に試せるクリックなしのロータリーエンコーダを持っていないので試していないのだけど、 クリックなしのロータリーエンコーダーの場合は、IncrementalEncoderの第三引数に1を入れればよいのだと思う。 2025/2/20 追記: クリックなしロータリーエンコーダーでIncrementalEncoderモジュールを使ったところ、第三引数なし(divisor=4)だと誤りなく拾えるが、 回す量に対する読み取り頻度が体感1/4になってしまい、第三引数に1を設定すると読み取り頻度が向上するが、逆方向読み取りも多々発生する。 1周期1カウントや2カウントを拾うのなら使えそうだが、1周期4カウント拾うのは汎用的なものだと無理があるのかな?

2025年2月9日日曜日

ロータリーエンコーダの読み取り改善を目指して

ロータリーエンコーダは、回したときに状態誤検知が発生することがある。
原因としては以下が考えられる。
①:読み込み間隔が長いため状態変化の取りこぼしが発生していること
②:状態変化時のバタつきを検知していること

前回は、以下のように状態読み取り間隔を十分長くとる(0.01秒間隔)ことにより、②の誤検知頻度を低くすることにしてみた。

import digitalio
import board
import time

s1 = digitalio.DigitalInOut(board.GP17)
s1.direction = digitalio.Direction.INPUT
s1.pull = digitalio.Pull.UP
s2 = digitalio.DigitalInOut(board.GP16)
s2.direction = digitalio.Direction.INPUT
s2.pull = digitalio.Pull.UP
n_s1=o_s1=s1.value
n_s2=o_s2=s2.value
while True:
    n_s1 , n_s2= s1.value , s2.value
    if(n_s1 != o_s1 or n_s2 != o_s2) :
        if n_s1 ^ o_s2 == 0 :
	        print("UP")
        else:
	        print("DOWN")
        o_s1,o_s2=n_s1,n_s2
    time.sleep(0.01)
    
このとき、低速で動かすとほとんど誤検知が発生しないが、高速で動かすと誤検知が複数回連続で見られる。
これは、高速操作時の誤検知は①で低速時の誤検知は②で、②は読み取り間隔が長いためほとんど遭遇しないのだと思う。

上のプログラムのtime.sleep行をコメントアウトすると、ゆっくり動かしても高速に動かしても誤検知が見られる。
連続した誤検知はたまにしか発生しないが、3行同時に変化が表示されることがある。
こちらは、①は発生していないが、たまに②が3回連続で発生していると思う。


まずは、timesleepを小さい量から徐々に大きくし、低速回転時にたましか誤検知が発生せず連続誤検知が1回のみのtimesleep値を探す。

0.00001とした場合、操作途中に12回連続でUP/DOWNを繰り返すことがある。→この間隔ではバタつきが12回分以上は発生していることがある。
 0.0001とした場合、操作途中に 4回連続でUP/DOWNを繰り返すことがある。→この間隔ではバタつきが4回以上は発生していることがある。
  0.001とした場合、操作途中に 2回連続でUP/DOWNを繰り返すことがある。→この間隔ではバタつきが2回以上は発生していることがある。
  0.002とした場合、操作途中に 1回連続でUP/DOWNを繰り返すことがある。→この間隔ではバタつきが1回以上は発生していることがある。

というわけで、0.002秒を採用し、運悪く誤検知のタイミングで読み取ってしまった場合に備え、最初のプログラムを以下のように少し加工してみた。


import digitalio
import board
import time

s1 = digitalio.DigitalInOut(board.GP17)
s1.direction = digitalio.Direction.INPUT
s1.pull = digitalio.Pull.UP
s2 = digitalio.DigitalInOut(board.GP16)
s2.direction = digitalio.Direction.INPUT
s2.pull = digitalio.Pull.UP
n_s1=o_s1=s1.value
n_s2=o_s2=s2.value
while True:
    nn_s1 , nn_s2= s1.value , s2.value
    if (nn_s1 != n_s1 or nn_s2 != n_s2) :
        n_s1,n_s2 =nn_s1,nn_s2
    elif(n_s1 != o_s1 or n_s2 != o_s2) :
        if n_s1 ^ o_s2 == 0 :
	        print("UP")
        else:
	        print("DOWN")
        o_s1,o_s2=n_s1,n_s2
    time.sleep(0.002)


加工した部分time.sleepの値の他に、以下の部分を
    n_s1 , n_s2= s1.value , s2.value
    if(n_s1 != o_s1 or n_s2 != o_s2) :
を以下に変更したこと。
    nn_s1 , nn_s2= s1.value , s2.value
    if (nn_s1 != n_s1 or nn_s2 != n_s2) :
        n_s1,n_s2 =nn_s1,nn_s2
    elif(n_s1 != o_s1 or n_s2 != o_s2) :


変更の狙いは以下が成り立つとして、
・0.002秒の間隔なら2回目の読み取り時には正しい読み取りができているはず。
・人の操作は、0.002x2よりも十分遅い。(間隔の2回分の時間が開いたとしても、人の次の操作と誤検知期間よりは短い。)
変更内容は、
0.002秒間隔でロータリーエンコーダを読み取るが、状態変更後の初回の読み取り時には状態変更を受け入れず、
次の読み取りで初回読み取りと同じ値であれば、先の読み取りは正常として状態変更を受け入れる。
次の読み取りで初回読み取りと違う値であれば、先の読み取りは誤検知として読み飛ばしたうえで今回の読み取りを初回読み取りとする。

ただ、ごくたまに誤検知をする。
原因はわからないが、以下かなぁ。
・状態変更後の誤検知期間の時間長は一定(0.002秒)ではないのか
・ダイヤルを次の目盛りまで回す操作が0.004秒よりも速いのか
・読み取り頻度が短すぎるのか
・そのほかの要因なのか

やってないのでわからないのだけど、0.01μFのセラミックコンデンサを並列つなぎすると劇的に改善するものなのかな?

circuitpythonでストレージ非表示、データ永続化、



boot.pyをこんな感じにすると、GPIO6につけたスイッチを押してUSB接続すると、switch.valueがFalseとなり通常のドライブが見れる状況。
スイッチを押さずに起動すると、PCにpicoのドライブが表示されない。


import os
import storage
import board
import digitalio

switch = digitalio.DigitalInOut(board.GP6)

switch.direction = digitalio.Direction.INPUT
switch.pull = digitalio.Pull.UP

if switch.value:
  #NormalMode
  storage.disable_usb_drive()
else :
  #Maintance
    pass


ドライブが表示されないなどPCからPICOのストレージに書き込みができない場合には、circuitpythonからストレージに書き込み可能となる。
これを使って状態の永続化をする。

pythonはjson形式文字列をそのまま辞書として保持できるみたいなので、こんな感じで辞書変数のdataをdata.jsonのファイルで出力して、



import json

# 永続化したいデータ
data = {
    "name": "Taro",
    "age": 30,
    "city": "Tokyo"
}

# JSONファイルにデータを書き込む
with open('data.json', 'w', encoding='utf-8') as file:
    json.dump(data, file)



data.jsonからデータを読み取り、更新してファイルに出力するのはこんな感じ。


import json

# JSONファイルからデータを読み込む
with open('data.json', 'r', encoding='utf-8') as file:
    data = json.load(file)

print(data)
data['age']+=1
# JSONファイルにデータを書き込む
with open('data.json', 'w', encoding='utf-8') as file:
    json.dump(data, file)



boot.pyからcode.pyの情報連携のやり方がわからず、PCから書き込み可能モードだとboot.pyからもファイル出力ができなさそう。
なので、data書き込みをしてみて、エラーになったらメンテナンスモードと判断しようかな。

2025/3/2 追記:JSONのやり取りはすごく遅いので、普通のファイル入出力の方がよさそう。

raspberry pi pico2を使ったUSB入力デバイス、部品配置

HIDの記事について、ユニバーサル基板に配置してみた。
ついでにディスプレイをつけてみた。

裏面はこんな感じ
立体交差、曲線配線、冗長な配線距離、はんだの痕など、指さして笑われそう。 今回もいろいろありました。 ロータリーエンコーダの固定足2個用にユニバーサル基板の3穴分を切らなければいけない(両足分の2か所)が、切ってロータリーエンコーダを配置してみたところ、間隔が狭すぎて使いづらい。 上の方のロータリーエンコーダの上の方の足を基板外に出すことにより距離しつつ再度の基板切りを回避。 裏面配線のことを忘れてピン配置図と同じようにしてくみ上げ終わってから、PCで確認したところ動作せず、結構途方にくれる。 GPIOの番号を0番から順に利用しようと思って、立体交差を受け入れていたのなのだけれど、もうそんなことを言ってられない。 ほとんどGPIOなので、GPIO番号の変更で逃げられない部分の再配線。 最初はロータリーエンコーダをクリックありとクリックなしを配置していたのだけど、動かしてみたらクリックありのロータリーエンコーダーの挙動がよくわからない。 結局2つともクリックなしロータリーエンコーダにしてみた。 ロータリーエンコーダを外すときに基板の穴が再利用できなくなったので、仕方なく配置場所を変更。(再度基板切りが発生) カッターナイフで削って加工しており、1か所切るのに30分ぐらいかかっているので、やり直しは結構ショック。 この削り作業で、カッターナイフの刃を2個分消費。 使った部品は、秋月で買ったものは [104398]細ピンヘッダー 1×20:¥30 2個 合計:¥60 [129604]Raspberry Pi Pico:¥880 [112031]0.96インチ 128×64ドット有機ELディスプレイ(OLED) 白色:1個:¥580 [129599]ロータリーエンコーダー(ノンクリックタイプ):¥100 2個 :¥200 [116278]ツマミ(ノブ) Dカット 指標付き S-42:¥30 2個:¥60 [102561]タクトスイッチ(大)10個セット:1パック:¥300 [103230]片面ガラスコンポジット・ユニバーサル基板 Bタイプ めっき仕上げ (95×72mm) 日本製:1枚:¥180 その他、配線用の線は買っていなかったので、 ホームセンターの園芸用の一番細い被膜付きの針金を買ってみた。10メートル250円。ただ、針金直径1.2mmは太すぎて加工困難なため使用できず。 結局ブレッドボード用のジャンパーワイヤーを5本ぐらい切って使ったのと、転がっているケーブル結束の針金入りビニル被膜から被膜をはいで利用。 配置について、raspberry pi picoやディスプレイが手の甲に隠れてしまう。 配置場所を変えたほうが良かったかも。ま、上下逆にして使っても、画面と、usbの面とつまみの位置が逆になるだけだから、気にならないといえば気ならない。 部品面では、安定させるために基板の裏5ミリぐらいスペーサーを入れて、同じ大きさの基板を底面にした方がよかったかも。 あと、精神安定上、ロータリーエンコーダ用にコンデンサはあった方がよいと思う。 さて、次はソフト部分。 今回も結局コンデンサーを使っていないので、ロータリーエンコーダーのバタつきをソフトウェア的にもう少し真面目に対応することと、 デバイスをPCにつないだ時に記憶媒体として表示されなくするのとデバイスに永続的なモードを持ちたいので、あるボタンを押しながら接続しないとcircuitpython側で マウントしないように加工することと、 操作時に、「メッセージ出力?」、「マウスホイール操作?」、「Shift,Ctrl,Altを押しながら?」などPCにどんな挙動をさせるかを決めることと モード変更時のインタフェースを決めることかな?

2025年2月2日日曜日

raspberry pi zero 2wの初期設定メモ

1.初期設定:
1.1.初期イメージ作成:
以下からPiImagerをダウンロードし、PCにインストール
https://www.raspberrypi.com/software/
PiImagerで、以下を設定しmicrosdカードに書き込む
・RASPBERRY PI ZERO 2 W,RASPBERRY PI OS(64-BIT),SDカードストレージを選択し、
・カスタマイズ設定で編集を選択し、
 ・一般タブで、ホスト名、ユーザ名、WIFI設定、ロケール設定を設定し
 ・サービスでSSHを有効にし、パスワード認証を使うを選択
イメージを書き込んだSDカードをraspberrypizero2wに刺して、raspberrypizero2wのマイクロUSBに通電。
・PCのコマンドプロンプトから、さっき設定したユーザ名とホスト名を使って"ssh [ユーザ名]@[ホスト名]"で接続。パスワードで接続。
sudo apt update
sudo apt full-upgrade
sudo rpi-update
sudo reboot

PCから"ssh [ユーザ名]@[ホスト名]"で接続。
sudo raspi-config を実行し、3 Interface Option から I2CとSPIを有効にしてリブート。

1.2.dockerインストール
http://docker.jp/PDF/docker-getting-started-guide-for-linux.pdf
と、
https://www.cqpub.co.jp/interface/download/2025/2/IF2502T2%E3%83%AA%E3%82%B9%E3%83%88.zip
のS3_list.txtを見ながら実施。

PCから"ssh [ユーザ名]@[ホスト名]"で接続。
curl -fsSL https://get.docker.com/ | sh


sudo usermod -aG docker $USER
exit でいったんログオフし、もう一度PCから"ssh [ユーザ名]@[ホスト名]"で接続。

id でdockerグループに所属を確認。

インストール結果確認。以下コマンドで「Hello from Docker!」が表示されることを確認。
docker run hello-world

メモリが少なくてchromiumもfirefoxも起動できないのであまり意味がないかもしれないけど、VNCサーバを起動するとするなら、
sudo raspi-config を実行し、3 Interface Option から VNCを有効にする。

そのほか、ssh接続にパスワードじゃなくて鍵を使うとすると、キーペアを作って公開キーをraspberry pi側に、秘密鍵をPC側に置いておく
キー作成
ssh-keygen -f keypair
公開鍵登録
umask 0077
mkdir ~/.ssh
cat keypair.pub >> ~/.ssh/authorized_keys
umask 0022
rm keypair.pub
PCから、"sftp [ユーザ名]@[ホスト名]"で接続。
以下のsftpコマンドで秘密鍵を取得
get keypair
rm keypair
exit

getした秘密鍵(keypair)をPCの%USERPROFILE%\.sshフォルダ(無ければ作る)にファイル移動。
%USERPROFILE%\.ssh\config ファイルに以下を追記
===
Host [raspberrypizero2wのホスト名]
    HostName [raspberrypizero2wのホスト名]
    User [raspberrypizero2wの接続ユーザー名]
    IdentityFile "[%USERPROFILE%を展開したフォルダ名]\.ssh\keypair"
===



インタフェース2月号によると、コンテナからGPIOなどを使うには、
1.ホスト側でGPIOを使っているユーザのID、グループIDを合わせる必要があることと
2.コンテナの/devをホスト側の/devにアクセスできるようにマウントする必要があることと
3.サービスの権限をつける必要がある
ようです。

1.は、Dockerfileで、イメージを作る時にイメージ側にgroupadd,useradd,をして、
  ホスト側のpiユーザである、ユーザID:1000,グループID:1000を設定して、
   USER命令で実行ユーザをUID,GIDが共に1000のユーザで起動するようにすること
2.は、compose.ymlで、services:(サービス名):の下のvolumes:で配列の一つに/dev:/devを指定すること
3.は、compose.ymlで、services:(サービス名):の下でprivileged: true を指定する必要があることです。

確かに、これをやってP.37のリスト3のDockerfile(ただしADDの行を除く)とリスト4のcompose.ymlを作成し、
[touch .env_file-develop]した後、docker compose up -d を実行して、
docker exec -it [コンテナハッシュ] /bin/bash
で入ると、プロンプトでログインユーザ名がrootではなくDockerfileでUSERで指定したユーザとなっていて、
pip install gpiozero
pip install Rpi
pip install lgpio
pip install pigpio
をやった後、以下のpythonスクリプトを実行したらGPIO18に接続したLEDが点滅した
===
from gpiozero import LED
from time import sleep

led = LED(18)

while True:
    led.on()
    sleep(0.5)
    led.off()
    sleep(0.5)
===



追記:imageにPI OS Liteを使っても同じように設定できた。
PI OS はrunlevel 5 (X環境)で動いているけど、PI OS Liteはrunlevel 3 (CUIのマルチユーザーモード)で動いているので、
Liteの方が消費リソースが少なくていい感じかも。

2025年2月1日土曜日

pipicoで可変抵抗器2

そういえばアナログスティックは2方向の可変抵抗器でした。
これを使ってUSBマウスのようにマウスカーソル操作してみます。

接続はこんな感じ
アナログスティックのGND,+5V,VRx,VRyをpicoのGND(38),3.3V(36),ADC1(32),ADC0(31)に接続して circuitpythonのコードはこんな感じ import time import usb_hid from adafruit_hid.mouse import Mouse from analogio import AnalogIn from board import * potentiometer1=AnalogIn(A0) potentiometer2=AnalogIn(A1) mouse = Mouse(usb_hid.devices) while True: x=-int((potentiometer1.value-65536/2)/65536*50) y=int((potentiometer2.value-65536/2)/65536*50) if abs(x)<2 : x=0 if abs(y)<2 : y=0 mouse.move(x,y,0) time.sleep(0.01)

最近のユニバーサル基板って切って小さくできるものなのかな?

久しぶり(約40年ぶり)にユニバーサル基板を買ってみた。
100円の小さ目のもの。
今住んでいるところの近くで電子工作用部品を扱っているところを知らないので、ネット通販。
raspberry pi picoやzero,ロータリーエンコーダやタクトスイッチを買ったときに一緒に購入。
(秋月で購入。全部で11,000円に届かなかったので送料が500円。picoは2つかったのだけど、zeroも2つ買っておけば、送料無料になっていたなぁ。)
で、納品書をよく読むと、片面ガラスコンポジット・ユニバーサル基板って書いてある。 これって、基板を切ったりできるものなのかな?

raspberry pi zero2wのピンヘッダはんだ付け終わり。あ!

やっちまったあ。

ピンヘッダをつける向きが逆だった! このピンヘッダの配置位置だとブレッドボードに直接させない。(刺したら1行目のピンと2行目のピンがショートしてしまう。) ケーブルで出してブレッドボードに指しても、ピンの表裏が逆なので列反転か行反転のいずれかを選ばなければならない。 2行20列なので、まだ行反転の方が扱える。(扱えないが。) というわけで、5VとGNDの表記のピンの逆側の行にピン接続をしてみて導通を確認。
こいつは外部機器を使わないサーバ的や用途として使い、もう1つpi zero 2wを買おうかなぁ。 最近だと仮想化が当たり前の感じだし、Interface2025年2月号にpi zeroもdocker化できそうなことを書いてあったので、こいつをdockerマシンにして、コンテナからGPIO制御をできるような実験だけにしようかな。 にしても、最近のコンピュータ雑誌は紙面だけでは説明しきれず、ダウンロードしないとスクリプトや結果表示がわからないっていうのは当たり前なのかな?すごく読みにくいんだけど。

2025年1月29日水曜日

pipicoで可変抵抗器

ロータリーエンコーダじゃなくて、1周しない普通のボリュームの可変抵抗器を使ってみた。
raspberry pi picoにはアナログ入力があるので、可変抵抗器の3本の足を、GND(3番ピン),3.3V(36番ピン),ADC0(31番ピン)につないで、
ネットに転がっているコードを参考に以下のコードにしてみた。

from board import *
import time
from analogio import AnalogIn

potentiometer=AnalogIn(A0)
while True:
    print(potentiometer.value)
    time.sleep(0.1)


読み取りはできてそうだけど、HIDの入力でよさそうなものは見つからない。

2025年1月26日日曜日

USBでHIDでロータリーエンコーダ

左手デバイスって言って、少しのボタンやスライドつまみや回転つまみなどがあるUSBデバイスっていうものがある。
これを試作してみた。

昔はアルドゥイーノのUSBHID対応機種(レオナルドとかマイクロ)を使うのが最有力候補だったけど、
今だとラズベリーパイピコ2の方が安くて普通にUSBHID対応しているので今回はこちらで作ってみた。

まずは、配線はこんな感じ。

スイッチ8個の片側をGPIO~GPIO7に、もう一方をGNDに接続。 ロータリーエンコーダ1の1,GND,2をGPIO14, GND, GPIO15に接続。 ロータリーエンコーダ2の1,GND,2をGPIO12, GND, GPIO13に接続。 ラズベリーパイピコ2の方は、ボタンを押しながらusbに接続し、 共有ドライブ直下に、circuitpythonのページでダウンロードした.uf2ファイルを格納してcircuitpythonを使えるようにして、 https://circuitpython.org/libraries から対応するadafruit-circuitpyhton-bundleをダウンロード/展開し、その中の"adafruit_hid"ディレクトリを circuitpythonの共有ドライブのlibフォルダにコピーしてadafruit_hidを使えるようにして、 以下の内容を共有フォルダ上のcode.pyファイルを上書きした後、一旦PCから外してもう一度つなげて入力ができることを確認。 import digitalio from board import * import time import usb_hid from adafruit_hid.keyboard import Keyboard from adafruit_hid.keyboard import Keycode from adafruit_hid.consumer_control import ConsumerControl from adafruit_hid.consumer_control_code import ConsumerControlCode s1 = digitalio.DigitalInOut(GP14) s1.direction = digitalio.Direction.INPUT s1.pull = digitalio.Pull.UP s2 = digitalio.DigitalInOut(GP15) s2.direction = digitalio.Direction.INPUT s2.pull = digitalio.Pull.UP n_s1=o_s1=s1.value n_s2=o_s2=s2.value s3 = digitalio.DigitalInOut(GP12) s3.direction = digitalio.Direction.INPUT s3.pull = digitalio.Pull.UP s4 = digitalio.DigitalInOut(GP13) s4.direction = digitalio.Direction.INPUT s4.pull = digitalio.Pull.UP n_s3=o_s3=s3.value n_s4=o_s4=s4.value sw=[] n_sws=[True,True,True,True,True,True,True,True] o_sws=[True,True,True,True,True,True,True,True] vol1map=[ConsumerControlCode.VOLUME_DECREMENT, ConsumerControlCode.VOLUME_INCREMENT] vol2map=[ConsumerControlCode.BRIGHTNESS_DECREMENT, ConsumerControlCode.BRIGHTNESS_INCREMENT] keymap=[Keycode.ONE, Keycode.TWO, Keycode.THREE, Keycode.FOUR, Keycode.FIVE, Keycode.SIX, Keycode.SEVEN, Keycode.EIGHT] for _gpio in GP0,GP1,GP2,GP3,GP4,GP5,GP6,GP7: s = digitalio.DigitalInOut(_gpio) s.direction = digitalio.Direction.INPUT s.pull = digitalio.Pull.UP sw.append(s) cc = ConsumerControl(usb_hid.devices) keyboard = Keyboard(usb_hid.devices) while True: n_s1 , n_s2= s1.value , s2.value if(n_s1 != o_s1 or n_s2 != o_s2) : if n_s1 ^ o_s2 == 0 : cc.send(vol1map[1]) else: cc.send(vol1map[0]) o_s1,o_s2=n_s1,n_s2 n_s3 , n_s4= s3.value , s4.value if(n_s3 != o_s3 or n_s4 != o_s4) : if n_s3 ^ o_s4 == 0 : cc.send(vol2map[1]) else: cc.send(vol2map[0]) o_s3,o_s4=n_s3,n_s4 for i in range(8): n_sws[i] = sw[i].value if ((o_sws[i]==True) and (n_sws[i]==False)): keyboard.send(keymap[i]) o_sws[i]=n_sws[i] time.sleep(0.001) ボタンの方は、多数のボタンの場合、普通はポート数を少なくするため以下のページのように入力と出力をグリッド上にするのですが、 https://www.elec-hobbyist.com/MicomMemo/Pgm_Method/Pgm_method_3.html 手元にダイオードがないため、各ボタン独立でポートを使いました。 ロータリーエンコーダの方は、切り替えのタイミングで短時間でON/OFFを繰り返すため、普通はセラミックコンデンサを入れて高周波成分を取り除くのですが、 https://sheep-me.me/2019/12/05/geidai_21/ 手元にセラミックコンデンサがないため、バタつきが収まるであろう時間を開けてスキャンするだけにしました。