- 最終更新日
- 記事公開日
ラズパイPicoでテレビを操作(リモコンの赤外線通信を解析して送受信)
赤外線通信で受信(リモコン → テレビの通信データを解析&コピー)
まずは、リモコン → テレビの通信で「どのような赤外線通信フォーマットが使用されているか?」を解析する必要があります。
一応、NEC・家製協(AEHA)・SONYといった基本フォーマットが存在していますが、実際に解析してみないことには、どのフォーマットが採用されているかは分かりません。
ちなみに、私のテレビ(三菱電機 REAL LCD-40MZW300)は、さきほどの3つの基本フォーマットにも当てはまらない三菱独自のフォーマットが採用されていました。
配線・回路図
使用パーツ
- Raspberry Pi Pico
- 赤外線リモコン受信モジュール(OSRB38C9AA)- 秋月電子通商
プログラム(MicroPython)
# アナログ-デジタル変換のライブラリを読み込む
from machine import ADC
# 時間関連のライブラリを読み込む
import utime
# ADC0(31Pin)をアナログ-デジタル変換の入力ピンに設定
adc = ADC(0)
# センサーの状態(HIGHか?LOWか?)
st = 1
# カウンタ
cnt = 0
# 稼働時間の計測開始
start = utime.ticks_us()
# 永久ループ
while True:
# read_u16() ・・・アナログ値を読込み[0-65535]の整数を返す関数
# 16bit・2進数の最大値:1111111111111111 ⇒ 10進数の最大値:65535
# 電圧の最大値3.3Vを「65535」に置き換え
sens = adc.read_u16()* (3.3 / 65535)
# 赤外線を受信している(センサーにかかる電圧が1Vより小さいとき)
if(sens < 1.0):
# センサーの状態がLOW→HIGHに切り替わった瞬間の処理
if (st == 1):
# センサーの状態がLOWを示す
st = 0
# カウントアップ
cnt += 1
# 稼働時間の計測終了
end = utime.ticks_us()
# 稼働時間の開始と終了の差を表示
tics = utime.ticks_diff(start,end)
# 計測終了時間を次の稼働時間の開始時間に設定
start = end
# 周期の計測結果を出力
print(tics)
# 赤外線を受信していない(センサーにかかる電圧が1Vより大きいとき)
else:
# センサーの状態がHIGH→LOWに切り替わった瞬間の処理
if (st == 0):
# センサーの状態がHIGHを示す
st = 1
# カウントアップ
cnt += 1
# 稼働時間の計測終了
end = utime.ticks_us()
# 稼働時間の開始と終了の差を表示
tics = utime.ticks_diff(end, start)
# 計測終了時間を次の稼働時間の開始時間に設定
start = end
# 周期の計測結果を出力
print(tics)
# 100個のデータを取得したら計測終了
if (cnt >= 100):
# 永久ループを抜ける
break
技術解説
リモコンのボタンを押すと、リモコンの発信部(赤外線LED)から、以下のような信号がテレビの受信部に送られます。
このパターンを読み取るため『赤外線受信モジュール』を使用します(※以下、センサーと略します)
センサーに向かってリモコンのボタンを押したとき、
「センサーの電圧がどのように変化するか?」
「HIGH状態の時間、LOW状態の時間」
をラズパイPicoで読み取っていきます。
※リモコンのボタンを押すのは一瞬で大丈夫です。
ボタンを押している時間が長いと、同じ信号が繰り返し送信されてしまいます。
解析する上で特に問題はないのですが、データは短い方が扱いやすいです。
赤外線受信モジュールの電圧を読み取る
センサーが赤外線を受信していないときの電圧は「1.5V」くらい(電源3.3Vの場合)
赤外線を受信すると「0.1V以下」に変化します。
※凄いスピードで点滅しているので、テスターで測ったときは1.5V → 1.3Vくらいの変化しか確認できません。
ただし「1.5V」という値は、マイコンによっては「HIGH」とでも「LOW」とでも取れる微妙な数値です。
そこで、ADC(アナログ→デジタル変換機能)を使い、ラズパイPicoで正確な電圧を読み取れるようにしてから、処理の振り分けを行っています。
ADCについてはこちらの記事を参考にしてください↓
これでセンサーの状態を読み取る準備ができました。
赤外線受信モジュールのHIGH・LOW時間を読み取る
次は、センサーが「HIGH状態の時間」と「LOW状態の時間」を計測していきます。
プログラムでは、HIGH→LOWに切り替わった瞬間、LOW→HIGHに切り替わった瞬間を検知して、その状態が続いた時間を記録しています。
ただし、状態が変わった瞬間をできるだけ早く検知しなければ、正確な情報とはなりません。
そのために、できるだけ短い間隔でループ処理を行うことが重要です。
ところが、ADCのサンプリングレートだけで100usほどの時間を取られます。
その他、変数を使ったり、途中で計算式が入るたびに実行速度が遅くなります。
よって、計測結果はある程度の誤差を含んでいるものと考えて下さい。
後で、エクセルを使い目視で調整していきます。
時間の「マイクロ秒」を取得する関数について
時間の計測には、「utime.ticks」関数を使っています。
# 稼働時間の計測開始
start = utime.ticks_us()
# 稼働時間の開始と終了の差を表示
tics = utime.ticks_diff(end, start)
utime — BBC micro:bit MicroPython 1.0.1 ドキュメント
※Pythonでマイクロ秒単位の時間を取得する場合は、timeやdatetimetimeライブラリを使うのが一般的なようですが、どちらも使えませんでした。
time.time_ns() → 1609466911000000000(秒以下が全て0)
import datetime → ImportError: no module named datetime(モジュールが入ってないよ)
受信した赤外線の結果を出力
100個のデータを取得したら、読み取りが終了します。
私の三菱テレビリモコンの場合は、以下のような結果となりました↓
-10289654
551
-1933
308
-2105
366
-2095
367
-898
272
-961
295
-909
303
-2148
372
-830
371
-891
294
-2151
369
-894
307
-911
301
-906
308
-899
377
-903
311
-907
308
-27799
符号のない数値:リモコンからHIGHの信号が出力されていた時間(センサーの状態がLOWになっていた時間)
「ー」が付いた数値:リモコンから何も信号が出ていなかった時間(センサーの状態がHIGHになっていた時間)
これで、赤外線通信の受信解析は終了です。
赤外線通信で送信(テレビの操作)
リモコンから解析したデータの誤差を調整。
そのデータを元に、ラズパイPicoで赤外線LEDを制御して、テレビの操作を行います。
配線・回路図
使用パーツ
- Raspberry Pi Pico
- 5mm赤外線LED(OSI5FU5111C-40)- 秋月電子通商
プログラム(MicroPython)
# GPIOピン制御・PWM制御のライブラリを読み込む
from machine import Pin, PWM
# 時間関連のライブラリを読み込む
import time
# PWM制御を行うピンの設定
ir = PWM(Pin(17, Pin.OUT))
# PWM制御の動作周波数を設定
# 一般的なリモコンで使用されている周波数 = 38KHz
f = 38000
ir.freq(f)
# PWM制御の周期(波が上下に1回振動するのにかかる時間)
# 周期 = 1/周波数(38kHz)= 26μs
# PWMのデューティー比(HIGHとLOWの比率)
# 一般的なリモコンのデューティー比 = HIGH:1/3、LOW:2/3
# 16bitの最大値 = FFFF(16進数)= 65535(10進数)
# 65535 * 0.3333 = 21845(10進数)= 5555(16進数)
dty = 0x5555
# 解析したデータが信号を一つ送るのに掛かっていた時間 = 300us
# 300usの長さの信号になるようにPWM信号を繰り返す
# 300us/26us = 11.5回
# しかしtime.ticksで赤外線通信の稼働時間を計測した結果、
# 理想のフレーム長(53ms~55ms)に足りない
# よって稼働時間が53ms以上になるように「20」と調整した
adj = 20
# 上位8bitは共通コード(11100010)
# 下位8bitは操作ボタン(01000000 = 電源ON/OFF
data =[1,1,1,0,0,0,1,0,0,1,0,0,0,0,0,0]
# 赤外線通信の稼働時間を計測開始
start = time.ticks_us()
# 16bitのコードを読み込む
for bit in data:
# コードの値が「1」のときの処理
if(bit == 1):
# デューティー比1/3がHIGHのPWM信号を送信(adj回分)
for i in range(1, 1*adj):
ir.duty_u16(dty)
# LOWのPWM信号を送信(7×adj回分)
for i in range(1, 7*adj):
ir.duty_u16(0)
# コードの値が「0」のときの処理
else:
# デューティー比1/3がHIGHのPWM信号を送信(adj回分)
for i in range(1, 1*adj):
ir.duty_u16(dty)
# LOWのPWM信号を送信(7×adj回分)
for i in range(1, 3*adj):
ir.duty_u16(0)
# ストップビット(データの終端を示す信号)
for l in range(1,1*adj):
ir.duty_u16(dty)
# トレーラー(通信の終わりを示す信号)
for l in range(1,1*adj):
for i in range(1,97):
ir.duty_u16(0)
# 赤外線通信の稼働時間を出力(53000us以上になっていればOK)
print(time.ticks_diff(time.ticks_us(), start))
技術解説
まずは結論から。
私の三菱テレビの場合、下記のように信号を送ることで電源のON・OFFに成功しました。
コマンド | データビット |
---|---|
電源オン・オフ | 1110001001000000 |
音量アップ | 1110001001000100 |
音量ダウン | 1110001001010100 |
エクセルを使って正確なデータに復元する
センサーで解析したデータから、リモコンだけが持っている正確なデータの復元を行います。
まずは、データをエクセルに貼りつけ。
※先頭の値が大きな数字は省いてください(最初にリモコンのボタンが押されるまでの時間です)
この数値は、リモコンから信号が出ていたとき・出ていなかったときの時間です。
300 / 900 / 2100付近の数値が多く、300の倍数がベースとなっていそうです。
そこで300で割って、小数点を四捨五入した整数が【B列】になります。
=ROUND(A2/300,0)
B列には「2,-6,1,-7,1,-7,1,-3,1,-3・・・1,93」という数字が並びました。
この中で規則性があるのは「1,-7」「1,-3」
この2つの数字を一セットとして、以下のように2進数に置き換えます。
1,-7 ⇒ 「1」
1,-3 ⇒ 「0」
※どちらが0か1かの決まりはありませんが、今回のプログラムは上記の置き換えで動くようになっています。
しかし、先頭の「2,-6」という数字は「1,-7」「1,-3」のどちらにも当てはまっていません。
この数字に関しては、「2,-6」⇒「1,-7」の誤差とみなしました。
リモコンのボタンを長押しすると、同じ信号が繰り返し出力されるようになっているのですが、2回目以降、先頭の数値は「1,-7」となっていたからです。
仕様なのか?センサーの精度が悪いのか?分かりませんが、一応、これで問題なく動いています。
最後の「1,93」という数字は、ストップビットとトレーラー信号です。
1 ⇒ ストップビット(データの終端を示す信号)
93 ⇒ トレーラー(通信の終わりを示す信号)
ということで、三菱製のテレビリモコンが一回の赤外線通信で送信するデータの数は36個。
以下のようなフォーマットで構成されていることが分かりました。
共通コード(8bit)+ 操作コード(8bit)+ ストップ(1bit)+ トレーラー
もちろん、どれか1つでもデータが欠けると正常に動きません。
そして、最後はフレーム長(一回の赤外線通信にかける時間)について。
エクセルに貼りつけた【A列】のデータを全て足して割り出します。
私の三菱テレビの場合は、数個のサンプルを準備して試した結果、5200us~53000usに収まりました。
プログラムを実行すると、データ送信後に赤外線通信の稼働時間が表示されます。
もし数字が大幅に違う場合は、adjの値を調整してください。
adj = 20
送信テスト
赤外線が正常に出力されているかどうかは、スマホを動画撮影モードにして、赤外線LEDを覗き込めば確認できます。
一瞬だけ「ピカピカッ」と紫の光が点滅します。
なお、赤外線到達距離は約30cmでした。
届く範囲が短いので、チェックの際は赤外線LEDをできるだけテレビの受信部に近づけてください。
赤外線が届く距離を伸ばすには、
- トランジスタを使って赤外線LEDへ流す電流を増幅する
- 赤外線LEDの数を増やす
- 指向性が狭いタイプの赤外線LEDに変える
など、方法は色々あります。
エクセルが使えないので送信することができません
エクセルなしで送信したいのですがどうしたら良いでしょうか?
エクセルがどうしても必要ならエクセルの使い方とかも詳しくのせてくれませんか?