Raspberry Pi OS の最近の環境だと従来の RPi.GPIO は古くて、内部的に /dev/gpiomem まわりの制御がうまくいかず、代わりに新しい rpi-lgpio ベースの実装を入れるとちゃんと動く、 ということらしい。詳しいことは おじさんには分からない。
GPIO割り込みのチャタリング防止のbouncetime設定はあまり期待通りにはなってないようで、ソフトで論理が切り替わる時に、無視する時間を設定して、その時間内の論理変化はすべて無視するようにした。
from luma.core.interface.serial import spi
from luma.lcd.device import st7789
from PIL import Image, ImageDraw, ImageFont
import RPi.GPIO as GPIO
import time
GPIO.setwarnings(False)
# --- GPIO ピン番号設定(例: A=17, B=18) ---
encoder_a = 14
encoder_b = 15
# --- 初期設定 ---
GPIO.setmode(GPIO.BCM)
GPIO.setup(encoder_a, GPIO.IN, pull_up_down=GPIO.PUD_UP)
GPIO.setup(encoder_b, GPIO.IN, pull_up_down=GPIO.PUD_UP)
# SPI設定
serial = spi(port=0, device=0, gpio_DC=24, gpio_RST=25,bus_speed_hz=52000000)
device = st7789(serial, width=320, height=240, rotate=0)
# 描画用キャンバス
image = Image.new("RGB", (320, 240), "black")
draw = ImageDraw.Draw(image)
# フォント設定
font_path = "/usr/share/fonts/opentype/noto/NotoSansCJK-Regular.ttc"
font_bold = "/usr/share/fonts/opentype/noto/NotoSansCJK-Bold.ttc"
font40 = ImageFont.truetype(font_bold, 40)
font32 = ImageFont.truetype(font_bold, 32)
font28 = ImageFont.truetype(font_path, 28)
font28_bold = ImageFont.truetype(font_bold, 28)
font20 = ImageFont.truetype(font_bold, 20)
#global
ch_no = 0
volume_level = 10
bit_rate = 128
station_name = ""
streamtitle = "Poppin'party(戸山香澄(愛美)、花園たえ(大塚紗英)、牛込りみ(西本りみ)、山吹沙綾(大橋彩香)、市ヶ谷有咲(伊藤彩沙)) - 開けたらDream!"
encorder_flag = False
counter = 0
encoder_old = GPIO.HIGH
encoder_count = 0
encorder_rapTime = 0
chattering_ignore_time = 5 # ms単位
Station_data = (
#name , url
["181.FM - Power 181 (Top 40) US", "http://www.181.fm/stream/pls/181-power.pls"],
["181.FM - UK top 40 New York", "http://www.181.fm/stream/pls/181-uktop40.pls"],
["181.FM - Rock 40 (Rock & Roll)", "http://www.181.fm/stream/pls/181-rock40.pls"],
["Antenne Bayern - Top 40 Germany", "http://mp3channels.webradio.antenne.de/top-40"],
["100hitz - Top 40 Antelope, Ca, US", "http://pureplay.cdnstream1.com/6025_128.mp3?"],
["Top Hits US" , "https://api.onlyhit.us/tophits"],
["Jazz Radio Classic Jazz France", "http://jazz-wr01.ice.infomaniak.ch/jazz-wr01-128.mp3"],
["181.FM - Fusion Jazz", "http://www.181.fm/stream/pls/181-fusionjazz.pls"],
["181.FM - Classical Jazz", "http://listen.181fm.com/181-classicaljazz_128k.mp3"],
["Radio Art - Jazz Piano", "https://live.radioart.com/fJazz_piano.mp3"],
["Jazz Radio Piano Jazz FR", "http://jzr-piano.ice.infomaniak.ch/jzr-piano.mp3"],
["100 GREATEST JAZZ LOUNGE BAR CA", "https://cast1.torontocast.com:4640/stream"],
["Jazz London Radio", "http://radio.canstream.co.uk:8075/live.mp3"],
["Epic Lounge - PIANO & JAZZ BAR Germany", "https://stream.epic-lounge.com/piano-jazz-bar?ref=radiobrowser"],
["Radio Art - Mellow Piano Jazz Greece", "https://live.radioart.com/fMellow_piano_jazz.mp3"],
["SOLO PIANO by Epic Piano Germany ", "https://stream.epic-piano.com/solo-piano?ref=radiobrowser"],
["Music Radio FR","https://radio.musicradio.ai/listen/musicradio.ai/radio.mp3"],
)
#Station_dataの数を調べる
Station_data_max = len(Station_data) - 1
# 折り返し関数
def wrap_text(text, font, draw, max_width):
lines = []
line = ""
for char in text:
test_line = line + char
w = draw.textbbox((0, 0), test_line, font=font)[2]
if w <= max_width:
line = test_line
else:
lines.append(line)
line = char
if line:
lines.append(line)
return lines
def show_ch():
global ch_no , font40
ch = "Ch:"+str(ch_no)
draw.rectangle((0, 0, 139, 48), fill="black")
draw.text((0, 0), ch, font=font40, fill="yellow")
def show_vol():
global volume_level ,font32
vol = "Vol:"+str(volume_level)
draw.rectangle((140, 0, 259, 48), fill="black")
draw.text((140, 10), vol, font=font32, fill="cyan")
def show_bitrate():
global bit_rate ,font20
bitr = str(bit_rate) + "K"
draw.rectangle((240, 0, 320, 52), fill="black")
draw.text((260, 0), bitr, font=font20, fill="magenta")
draw.text((260, 20), "bps", font=font20, fill="magenta")
def show_station():
global font28_bold,ch_no
draw.rectangle((0, 58, 320, 127), fill="black")
station = Station_data[ch_no][0]
lines = wrap_text(station, font28_bold, draw, max_width=315)
y = 54
for line in lines:
draw.text((0, y), line, font=font28_bold, fill="lime")
bbox = font28.getbbox(line)
line_height = bbox[3] - bbox[1]
y += line_height + 2 # 行間0
def show_streamtitle():
global font28
draw.rectangle((0, 129, 320, 240), fill="black")
stream = streamtitle
lines = wrap_text(stream, font28, draw, max_width=315)
y = 129
for line in lines:
draw.text((0, y), line, font=font28, fill="white")
bbox = font28.getbbox(line)
line_height = bbox[3] - bbox[1]
y += line_height # 行間0
def millis():
return int(time.monotonic() * 1000)
def encoder_callback(channel):
global encoder_old, encoder_count, encorder_rapTime
if GPIO.input(encoder_a) == GPIO.LOW:
if encoder_old == GPIO.HIGH: # 前回はHighだった?
if (millis() - encorder_rapTime) > chattering_ignore_time:
if GPIO.input(encoder_b) == GPIO.LOW:
encoder_count = 1 # CW
else:
encoder_count = -1 # CCW
encoder_old = GPIO.LOW
else:
if encoder_old == GPIO.LOW: # 前回はLowだった?
encorder_rapTime = millis()
encoder_old = GPIO.HIGH
# 割り込み設定 (立下り/立上り両方)
GPIO.add_event_detect(encoder_a, GPIO.BOTH, callback=encoder_callback)
def encorder_check_and_action():
global encoder_count, ch_no
if encoder_count != 0:
ch_no += encoder_count
encoder_count = 0
# --- 範囲チェック ---
if ch_no < 0:
ch_no = Station_data_max
elif ch_no > Station_data_max:
ch_no = 0
show_ch()
show_station()
device.display(image)
#イメージの作成
draw.line((0, 56, 320, 56), fill="orange", width=2)
draw.line((0, 128, 320, 128), fill="orange", width=2)
show_ch()
show_vol()
show_bitrate()
show_station()
show_streamtitle()
device.display(image)
# 表示
try:
while True:
encorder_check_and_action()
except KeyboardInterrupt:
GPIO.cleanup()
今回も一筋縄ではいかなかった。
エンコーダーひとつで苦労するとは、なかなか奥が深い。
逃げ出したくなる気分になる
「おいらも逃げ出すニャ」
あんたのスピードなら、すぐに捕まえられるけどね