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書き込みをしてみて、エラーになったらメンテナンスモードと判断しようかな。

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

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

裏面はこんな感じ
立体交差、曲線配線、冗長な配線距離、はんだの痕など、指さして笑われそう。 今回もいろいろありました。 ロータリーエンコーダの固定足2個用にユニバーサル基板の3穴分を切らなければいけない(両足分の2か所)が、切ってロータリーエンコーダを配置してみたところ、間隔が狭すぎて使いづらい。 上の方のロータリーエンコーダの上の方の足を基板外に出すことにより距離しつつ再度の基板切りを回避。 裏面配線のことを忘れてピン配置図と同じようにしてくみ上げ終わってから、PCで確認したところ動作せず、結構途方にくれる。 GPIOの番号を0番から順に利用しようと思って、立体交差を受け入れていたのなのだけれど、もうそんなことを言ってられない。 ほとんどGPIOなので、GPIO番号の変更で逃げられない部分の再配線。 最初はロータリーエンコーダをクリックありとクリックなしを配置していたのだけど、動かしてみたらクリックありのロータリーエンコーダーの挙動がよくわからない。 結局2つともクリックなしロータリーエンコーダにしてみた。 ロータリーエンコーダを外すときに基板の穴が再利用できなくなったので、仕方なく配置場所を変更。(再度基板切りが発生) カッターナイフで削って加工しており、1か所切るのに30分ぐらいかかっているので、やり直しは結構ショック。 使った部品は、秋月で買ったものは [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にどんな挙動をさせるかを決めることと モード変更時のインタフェースを決めることかな?