haru5ya が 2025年09月14日18時14分14秒 に編集
コメント無し
本文の変更
# はじめに 紐スイッチLED照明を作成するときにRaspberry Pi PicoのMicroPythonで赤外線リモコン出力をしようとしたら適当なライブラリがないというのが、今回の発端となっております。 [紐スイッチで簡単操作!赤外線を使ったLED電灯のオンオフ制御](https://elchika.com/article/b3a19287-7661-4ba0-b2a4-af7da1735350/) ソフトウェアで作るのも芸がないし面白くないということで、その時ハマっていたgreenpak(以後gpk) SLG46826で作ってみようと思い立ちました。 書き込みタイミングでpicoからgpkへデータを送れば赤外線リモコンフォーマットに変調して送れるのではないかという、思いつきから作成することにしました。 データを用意してgpkに渡すところまでできれば、pico以外のRaspberry Piなんかでも赤外線リモコンになりますね。 ただ、今のところNECフォーマットしか対応していませんので、悪しからず。変調の周波数を変えるだけだからSONYフォーマットもいけるような気がします。 赤外線リモコンの送信フォーマットについては詳細を書きませんので、下記のURLを参照してください。 [赤外線リモコンの通信フォーマット](https://elm-chan.org/docs/ir_format.html) ## 開発環境 - Windows11 - Go Configure Software HUB(gpk開発ツール) - Thonny(MicroPythonエディタ) # 回路構成 ## Raspberry Pi PicoとSLG46826、赤外線LEDの回路図  ## 使用部品リスト | 種別 | 名称 | 個数 | 備考 | |:--|:--|:--|:--| | マイコン | Raspberry Pi Pico | 1 | | | 赤外線LED | L53F3BT Vf:1.2V,If:20mA | 1 | | | 抵抗 | 2.7k | 2 | i2cのプルアップ用 | | 抵抗 | 1k | 1 | トランジスタ Base | | 抵抗 | 100 | 1 | 赤外線LED駆動用 | | トランジスタ | SS8050| 1 | 赤外線LED駆動用 | | greenpak | SLG46826G | 1 | | SLG46826は、秋月電子とオレンジピコショップで購入できますが、TSSOPパッケージ(0.65mmピッチ20ピン)のSLG46826Gしかありません。ピン幅狭いので、半田付けをする場合はご注意ください。 [秋月電子通商GreenPAKSLG46826G](https://akizukidenshi.com/catalog/g/g118384/) ## gpk SLG46826Gの中の回路図


| ピン番号 | 名称 | IO | 説明 | |:--|:--|:--:|:--| | 4 | 書き込みタイミング | OUT | H->Lになったとき、picoはi2cでpgenの上位、下位と交互に1バイトずつ送信データを書き込む | | 5 | pgen output | OUT | pgenの2バイトデータがシリアルデータになって出力される | | 6 | T | OUT | テスト用 | | 8 | 赤外線データ | OUT | 38kHzで変調された赤外線データが出力される | | 9 | 送信中フラグ | OUT | Hの時は赤外線データを送信中<br>書き込みタイミングがHになったらHになって、100msec後にLになる) | | 17 | i2cアドレス| IN | PULL UP, Hにするとi2cアドレス=8 | | 19 | 送信開始 | IN | L->H->Lのパルスを入力すると、赤外線データの送信を開始する | pgenはパターンジェネレータの略。i2cのアドレス0x92(下位),0x93(上位)で2バイトのデータとなります。 ## GreenPAKの動作概要 1. 送信開始ピンにHパルスが入力されたら、下記の処理を開始する。 2. i2cのpgenの上位から1バイトを上位からビット単位に38KHzで変調してPIN5に出力する。 3. 上位バイトが終わったら下位バイトのデータを送信する。(上位下位を繰り返し出力する。) 4. 上位下位、下位上位が切り替わるタイミングで、書き込みタイミングピンにパルスを出力する。 ### Go Configure Software HUBのデバッグ Go Configure Software HUBでは画面上でPINにシグナルと入れたり、出力結果をグラフとして出力することができます。 ただ、今回のようにpgenへ順次書き換えを行うことはできないので、すでにpgenに書き込まれているデータが出力するかを確認しました。 送信開始(PIN19)にHパルスを入力してpgenの2バイトデータを交互に出力するようにしてみました。シミュレーションという形で表示することができます。   ## M5Stackを使った書き込み手順 作成したgpkの設定は、下記のURLを参考にM5Stackで書き込みました。 [M5StickCやM5Stackを使ってGreenPAKに設計データを焼いてみた](https://elchika.com/article/e0de0e0d-b41a-4d93-9d8a-2013f501c906/) ### GreenPAK設定のエクスポートデータ ```binary :100000009109993EF59A0000B40E00DC0100000051 :10001000100200000000F00200305922F03E0020E3 :1000200002000040A6E55BC06B0100005800000024 :100030000000000000000000004088FEEF3FFDE4EB :100040000F000000000000000000000000000000A1 :1000500000000000000000000000000000000000A0 :10006000023030007030303000003080000000007E :1000700000303030000000000000000000000000F0 :1000800083000000071422300C0000000000000074 :1000900088F8115500FEFE00FE0008080000000070 :1000A00001000020013A024480521010005E00C29C :1000B0008200021801000200014200CE07208025C4 :1000C000082301002E0017010000110100000000AC :1000D0000000000000000000000000000000000020 :1000E0000000000000000000000000000000000010 :1000F000000000000000000000000000000000A55B :00000001FF ``` # ソフトウェア ## MicroPythonソースコード概要 ```python:gpk_ir_remocon.py # MIT License # # Copyright (c) 2025 haru5ya # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. from machine import Pin, I2C from time import sleep class FIFO: """ FIFO class""" def __init__(self, size=100): self.buf = [0] * size self.size = size self.head = 0 self.tail = 0 self.count = 0 def put(self, x): """ put data """ if self.count == self.size: raise OverflowError("FIFO full") self.buf[self.tail] = x self.tail = (self.tail + 1) % self.size self.count += 1 def get(self): """ get """ if self.count == 0: raise IndexError("FIFO empty") x = self.buf[self.head] self.head = (self.head + 1) % self.size self.count -= 1 return x def empty(self): """ empty """ return self.count == 0 def full(self): """ full """ return self.count == self.size def to_list(self): """FIFO内のデータをリストにして返す(古い順から)""" result = [] idx = self.head for _ in range(self.count): result.append(self.buf[idx]) idx = (idx + 1) % self.size return result class GpkIrRemocon: """greenpak ir remocon""" def __init__(self, i2c=None,slave_addr=0,write_timing_rise=2,i2c_timing=3): # I2C の初期化 if i2c is None: self.i2c = I2C(0, freq=100_000) else: self.i2c = i2c self.slave_addr = slave_addr self.pin_write_timing_rise=Pin(write_timing_rise, Pin.IN) self.pin_i2c_timing=Pin(i2c_timing, Pin.OUT) self.pin_i2c_timing.off() self.buffer_idx=0 self.byte_buffer=FIFO() self.q_write_time = [] self.pin_write_timing_rise.irq(trigger=Pin.IRQ_FALLING , handler=self.pin_handler) self.count = 0 def pin_handler(self, line): """ 書き込みの割り込み発生 """ self.write_pgen() def scan(self): """アドレススキャン""" # アドレススキャン print("Scanning I2C bus...(0x08->0x77)") devices = self.i2c.scan() if devices: print("Found devices:", [hex(dev) for dev in devices]) else: print("No I2C devices found.") return devices # ==== ダンプ関数 ==== def dump_device(self, start=0x00, length=256): """ 指定I2Cアドレスのレジスタを順番に読み出しダンプ表示 start: 開始レジスタアドレス length: 読み込む総バイト数 """ print(f"\n=== I2C Addr {hex(self.slave_addr)} ダンプ ===") for offset in range(start, start + length, 16): # 16バイト単位で表示 # 読みたいレジスタアドレスを送信 self.i2c.writeto(self.slave_addr, bytes([offset & 0xFF])) # 16バイト読み込み(最後は足りない場合あり) size = min(16, start + length - offset) data = self.i2c.readfrom(self.slave_addr, size) # 表示 hex_str = " ".join(f"{b:02X}" for b in data) print(f"{offset:04X}: {hex_str}") def run(self, byte_data): """リモコン処理スタート""" self.create_ir_bytea(byte_data) self.buffer_idx=0 self.write_enable_i2c(False) self.write_pgen() self.write_enable_i2c(True) while not self.byte_buffer.empty: pass self.write_enable_i2c(False) def write_pgen(self): """pgen 書き込み""" if self.byte_buffer.empty(): return offset = self.byte_buffer.get() val = self.byte_buffer.get() buf = bytes([(0x92 + offset) & 0xff,val & 0xff]) self.i2c.writeto(self.slave_addr, buf) print(f"send:{hex((0x92 + offset) & 0xff)} , {hex(val & 0xff)}") def write_enable_i2c(self, f:bool): """ enable i2c 書き込み virrual input[0]を0/1にする start data(PIN19)と同じ扱い """ if f: b = 0b1000_0000 else: b = 0b0000_0000 self.i2c.writeto(self.slave_addr,bytes([0x7a,b])) return 1 def create_ir_bytea(self, byte_data): """リモコンバイトデータ取得""" print("create_ir_bytea") bit_buffer=[] for b in byte_data: for j in range(7, -1, -1): if b & (1 << j): # bit = 1 bit_buffer.append(1) bit_buffer.append(0) bit_buffer.append(0) bit_buffer.append(0) else: # bit = 0 bit_buffer.append(1) bit_buffer.append(0) bit_buffer.append(1) # stop bit on bit_buffer.append(0) # stop bit off byte_count =0 b = 0 index = 1 i = 0 for v in bit_buffer: j = i % 8 b |= v << (7-j) i += 1 if j == 7: self.byte_buffer.put(index % 2) self.byte_buffer.put(b) index += 1 b=0 if b > 0: self.byte_buffer.put(index % 2) index += 1 self.byte_buffer.put(b) self.byte_buffer.put(index % 2) index += 1 self.byte_buffer.put(0) # EOL self.byte_buffer.put(index % 2) index += 1 self.byte_buffer.put(0) return byte_count ``` ```python:main.py # MIT License # # Copyright (c) 2025 haru5ya # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. from machine import Pin from time import sleep from gpk_ir_remocon import GpkIrRemocon BTN_1=6 BTN_2=7 led = Pin(25, Pin.OUT) send_val_ary = {"on_off":b'\x59\xA2\x20\xDF', "all": b'\x59\xA2\x12\xED', "min": b'\x59\xA2\x11\xEE' , "max": b'\x59\xA2\x21\xDE'} gpk = GpkIrRemocon(slave_addr=8,write_timing_rise=2,i2c_timing=3) gpk.scan() btn_1 = Pin(BTN_1, Pin.IN, Pin.PULL_UP) btn_2 = Pin(BTN_2, Pin.IN, Pin.PULL_UP) i=0 while True: if btn_1.value() == 0 : print("btn 1 on") led.value(1) gpk.run(send_val_ary["on_off"]) sleep(0.5) led.value(0) if btn_2.value() == 0 : print("btn 2 on") led.value(1) gpk.run(send_val_ary["all"]) sleep(0.5) led.value(0) sleep(0.1) ``` ## ソースの説明 ボタン1 or ボタン2が押されたら下記の要領で赤外線データを送信します。 1. 送信するバイトデータを用意する(send_val_ary ) 1. 赤外線リモコンのフォーマットで1(1000),0(10)に変換したビット配列を頭から8ビットごとにバイト配列に変換する 1. 1バイト目を書いて送信開始ピンにHパルスを書き込む 1. 書き込みタイミングがH->Lで割り込みが発生するので、次のバイトを書き込みます。(pin_handler) 1. 100msec経過したら送信終了する。(フレーム開始から次のリピートまでの時間を鑑みて100msecで終了とした。) ちなみに連続送信には対応していません。 えいやーで作っていますので多少不具合があるかもしれませんが、お許しください。 # TIPS ## gpkのi2cのアドレス gpkへ書き込むとき、消去→書き込みとするため、消去するとi2cのアドレスが0x00になってしまいます。Arduinoであれば、i2cアドレスが0x00でも動いていたのですが、MicroPythonののi2cのライブラリは0x00はアクセスできないことが発覚しました。 色々調べてみると、gpkのPIN17をi2cのアドレスに繋いでPIN17をPULLUPすればi2cのアドレスは0x08からとなりました。 これでなんとか動いてくれました。 ## PORは大事 最初、INPUTはgpk起動時にLOWで動くのかなと思ったのですが、途中でHIになるPINがありました。LOWスタートじゃないといけないロジックがHIになってしまい、困ったことになりました。 それで、サンプルの回路を見るとPORを使っているのがあったのでPORを使うと、LOWスタートできました。 ## ArduinoとMicroPythonは違う(当たり前) 元々はRaspberry Pi PicoではなくてM5Stackで制御していたので、Arduinoで動かしていました。それをMicroPythonに変換する時、i2cの書き込みで苦労しました。 ## gpkのデバッグにはデジアナかオシロスコープがあるとよい デバッグする時に赤外線データが正しく出力しているのかがわからなくて、デジアナまで引っ張り出して、実際のリモコンの信号の形と本機材の出力した信号の形を比較して、確認しました。 あわせて、デバッグ用の信号をgpkからPINに出力して、デジアナで内容を確認しました。 ハードルは高いですが、2チャンネルぐらいでしたらオシロスコープも安いのがありますので、これを機会に購入するのもありかもしれません。 電気信号は目に見えませんので、見える形にして確認するのが大事だと思います。 今回のプログラムの赤外線データをデジアナで取得した結果です。  - ir data: 赤外線リモコンで出力するデータ - ir recv: 赤外線受信機で受信した結果 - write timing: 書き込みタイミングのパルス # 今回の教訓と希望 ## 資料は残そう 実はgpk+M5Stack版が完成してから2ヶ月ぐらいおいてからRaspberry Pi Pico版を作ったためgpkの仕様を失念してしまい、思い出すのに時間がかかってしまいました。 開発完了したらメモレベルでいいので、資料を残すようにします。 ## 接続ピン数減らしたい i2cだけでgpkとのやり取りをしたかったのですが、書き込みタイミングのピンだけはどうしても必要だったので、一本線を繋ぐことにしました。 i2cだけでやり取りできると接続する本数が少なくていいのになと思っております。 # 利用上の注意 使う時は自己責任でお願いします。 # 謝辞 [AoiSayaさん、ありがとうございます。](https://elchika.com/user/AoiSaya/?page=0) [ChanNさんありがとうございます。](https://elm-chan.org/index_j.html)