M5シリーズの赤外線リモコンの実装はArduino環境ではたくさんあったが、Micropythonでのやり方は
あまり書いてなかったので自分用も兼ねてメモ
環境
- M5StickC plus
- M5 UI Flow v1.7.2
結論
esp32.RMT
モジュールを使う
https://docs.micropython.org/en/latest/library/esp32.html?highlight=rmt#esp32.RMT
esp32.RMT
公式ドキュメントに記載されている通り、赤外線リモコン用に用意されているモジュール。
ソースの周波数が80MHz固定、送信用機能しかないなど、まだベータ版としての位置づけみたい。
clock_div
でRMT.write_pulse
での時間分解能を指定。
分解能は1 / (source_freq / clock_div)
で計算できる。
RMT.write_pulses(pulses, start)
でpulses
にON/OFFの時間の
リストまたはタプルを渡してあげることで、指定したキャリアのパルスが発生する。
start
はpulse
の最初がON/OFFのどちらか。
# To use carrier frequency
r = esp32.RMT(channel=0, pin=Pin(18), clock_div=80, carrier_freq=38000, carrier_duty_percent=33)
r # RMT(channel=0, pin=18, source_freq=80000000, clock_div=80, carrier_freq=38000, carrier_duty_percent=33)
# The channel resolution is 1us (1/(source_freq/clock_div)).
r.write_pulses((1, 20, 2, 40), start=0) # Send 0 for 1us, 1 for 20us, 0 for 2us, 1 for 40us
実装と確認
我が家の エアコン(Panasonic) で試してみた。
Panasonicは家製協のフォーマットであるので、下記を参考に実装してM5 UI Flowから書き込み1して動作したことを確認。
https://github.com/r45635/HVAC-IR-Control/blob/master/Protocol/Panasonic%20HVAC%20IR%20Protocol%20specification.pdf
リモコンがどんなコードを送信しているかなどの解析は割愛。
https://github.com/gensyu/micropython_ir_send
# MicroPython esp32 AEHA 赤外線リモコン送信
import time
T = 436
HEADER_HIGH_US = 3400
HEADER_LOW_US = 1750
def reverce_8bit(data_8bit: int) -> int:
"""8bit を反転させる
Parameters
----------
data_8bit : int
8bit数値
Returns
-------
int
ビット反転後の数値
"""
data_8bit = ((data_8bit & 0b01010101) << 1) | ((data_8bit & 0b10101010) >> 1)
data_8bit = ((data_8bit & 0b00110011) << 2) | ((data_8bit & 0b11001100) >> 2)
return (data_8bit & 0b00001111) << 4 | data_8bit >> 4
def cal_parity(customer_code: list) -> int:
"""パリティ計算
Parameters
----------
customer_code : int
カスタマーコード
Returns
-------
int
パリティ計算結果
"""
parity = 0b0000
for i in customer_code:
upper_4bit = i >> 4
lower_4bit = i & 0b00001111
parity = parity ^ upper_4bit ^ lower_4bit
return parity
def cal_sum(data_lsb: list) -> int:
"""チェックサム計算
Parameters
----------
data_lsb : list
データ配列
Returns
-------
int
チェックサム値
"""
val = 0
for i in data_lsb:
val = (val + i) % 256
return val
def encode_ir_data(customer_code_lsb: list, data_lsb: list) -> str:
"""カスタマーコード、データを0, 1文字列に変換
Parameters
----------
customer_code : List[int]
カスタマーコード
data : List[int]
送信データ
Returns
-------
str
0,1文字列
"""
bit_str = ""
customer_code_msb = [reverce_8bit(i) for i in customer_code_lsb]
for d in customer_code_msb:
bit_str += "{0:08b}".format(d)
bit_str = bit_str + "{:04b}".format(cal_parity(customer_code_msb))
data_msb = [reverce_8bit(i) for i in data_lsb]
check_sum = cal_sum(customer_code_lsb + data_lsb)
data_msb.append(reverce_8bit(check_sum))
for i, d in enumerate(data_msb):
if i == 0:
bit_str += "{:04b}".format(d)
else:
bit_str += "{:08b}".format(d)
return bit_str
def generate_frame(bit_code: str) -> list:
"""01文字列からON/OFFの時間に変換
Parameters
----------
bit_code : str
0,1文字列
Returns
-------
list
ON/OFF時間の配列
"""
pulse_width_data = [HEADER_HIGH_US, HEADER_LOW_US]
for bit in bit_code:
if bit == "0":
pulse_width_data.extend([T, T])
else:
pulse_width_data.extend([T, 3 * T])
pulse_width_data.extend([T])
return pulse_width_data
def send_ir_data(rmt, customer_code, *data):
if len(data) == 0:
raise TypeError("data引数がありません")
for d in data:
bit_code = encode_ir_data(customer_code, d)
frame = generate_frame(bit_code)
rmt.write_pulses(frame, start=1)
while not (rmt.wait_done()):
time.sleep_us(T)
time.sleep_us(8000)
if __name__ == "__main__":
CUSTOMER_CODE = [0x02, 0x20]
DATA1 = [0xE0, 0x04, 0x00, 0x00, 0x00]
DATA2 = [
0xE0,
0x04,
0x00,
0x41,
0x30,
0x80,
0xAF,
0x00,
0x00,
0x06,
0x60,
0x00,
0x00,
0x80,
0x00,
0x06,
]
ir_led = RMT(0, pin=Pin(9), clock_div=80, carrier_freq=38000)
send_ir_data(ir_led, CUSTOMER_CODE, DATA1, DATA2)
-
RMTチャンネル割当て後、deinit()せずに同じチャンネルに割当するとエラー測れるので、書込み毎にリセットする必要あり ↩