🐺

PicoRubyで赤外線リモコンを受信する

に公開

はじめに

PicoRubyで電子工作をするとき、何かの動作確認をするのであればPCとUSBケーブルで接続して、irb上で操作、あるいはシェルから.rbファイルを実行すればよい。でも何かしら作品として作るのであれば、もっと簡単に操作できるようにしたい。あるいはロボット、プラレールといった動くものの制御に使う場合はケーブル接続だと動きがかなり制限されてしまうので、ケーブルなしで操作したいところだ。

Raspberry Pi Pico WHならWifiやBluetoothが使えるけれど、接続設定が必要だったり操作するためのアプリを作る必要がある。そこでもっと手軽な方法として赤外線リモコンを使ってみよう。

材料

材料 買い方・備考
赤外線リモコン受信モジュール 秋月電子で買ったPL-IRM0101 (1個110円) とOSRB38C9AA (2個で100円) で動作を確認。値段が倍くらい違うけど(とは言っても110円)どちらも同じように動いた。動作温度の範囲や受信距離が違ったりするようだ(なぜか安いやつの方が長い)。
赤外線リモコン 動作を確認したのは秋月電子で買った白いリモコン と、Amazonで買ったELEGOO Arduino R3スターターキットに付いてた黒いリモコン。秋月電子で売ってるこれと同じものだと思うけど、実際に試したわけではないから分からない。

やること

赤外線リモコンのフォーマットはいくつか種類があるが、国内の家電のほとんどはNEC、家製協(家電製品協会)、SONY方式のどれからしい。上記に挙げたリモコンは両方NEC方式。ArduinoやMicroPythonではライブラリが用意されているので簡単に使えるが、前回記事同様PicoRuby用は多分ないので自分でやってみよう。ということで今回の目的はNEC方式で送られたデータを受信するコードをPicoRubyコードを書くことだ。

こういうときはArduino用ライブラリのコードを参考にすればよいかと思ったが、見てみるとなかなかに複雑で、簡単には解読できそうになかった。ウェブ上を探せばリモコンのフォーマットについて説明記事はいくつか見つかるが、結構難しくてすぐには理解できそうにない。あるいは割り込みやMicroPythonのwait_for_edge関数を使っていたりして、PicoRubyに直接は移植できそうにない。

と思っていたら、Arduinoでライブラリを使わず、かつ特殊な命令を使わずにNECフォーマットをデコードするコードがあるページを発見。このロジックならPicoRubyにも簡単に移植できそうだ。

配線

配線は以下。PL-IRM0101、OSRB38C9AAどちらも同じで、黒い玉みたいな受光部に向かってみたとき、左からVout, GND, Vccなので、それぞれGPIOのどれか、GNDのどれか、3V3 OUTに繋ぐ。ここではGP17を使った。

コードと動作確認

コードはGitHubに置いた。上に挙げたサイトのコードを参考にRubyに書き起こしたものなので、詳細については元記事を見てほしい。

このirrecv.rb/home以下に置き、またテスト用に以下のようなスクリプトも作る。

irrecv_test1.rb
load 'irrecv.rb'

ir_pin = GPIO.new(17, GPIO::IN)

$data_code_to_key_name_h = {
  # White
  0xD8 => "POWER",
  0xF8 => "A",
  0x78 => "B",
  0x58 => "C",
  0xB1 => "UP_LEFT",
  0xA0 => "UP",
  0x21 => "UP_RIGHT",
  0x10 => "LEFT",
  0x20 => "CENTER",
  0x80 => "RIGHT",
  0x11 => "DOWN_LEFT",
  0x00 => "DOWN",
  0x81 => "DOWN_RIGHT",

  # Black
  0x45 => "POWER",
  0x46 => "VOL+",
  0x47 => "FUNC/STOP",
  0x44 => "FAST_BACK",
  0x40 => "PLAY",
  0x43 => "FAST_FORWARD",
  0x07 => "DOWN",
  0x15 => "VOL-",
  0x09 => "UP",
  0x16 => "0",
  0x19 => "EQ",
  0x0D => "ST/REPT",
  0x0C => "1",
  0x18 => "2",
  0x5E => "3",
  0x08 => "4",
  0x1C => "5",
  0x5A => "6",
  0x42 => "7",
  0x52 => "8",
  0x4A => "9",
}

def data_code_to_key_name(data_code)
  if $data_code_to_key_name_h.has_key? data_code
    return $data_code_to_key_name_h[data_code]
  end
  return "UNKNOWN"
end

def print_decoded_data(decoded)
  puts "Decoded data"
  puts "  Leader code"
  printf "    HIGH: %.2f\n", decoded.leader_code_time_high
  printf "     LOW: %.2f\n", decoded.leader_code_time_low
  puts "  Repeat code: #{decoded.is_repeat_code}"
  if !decoded.is_repeat_code
    custom0 = decoded.raw_custom_code[0]
    custom1 = decoded.raw_custom_code[1]
    sum_custom =custom0 + custom1
    printf "  Raw custom code: %02x %02x -> %02x\n", custom0, custom1, sum_custom
    data0 = decoded.raw_data_code[0]
    data1 = decoded.raw_data_code[1]
    sum_data = data0 + data1
    printf "    Raw data code: %02x %02x -> %02x\n", data0, data1, sum_data
    key_name = data_code_to_key_name data0
    puts "  Key: #{key_name}"
  end
end

loop do
  decoded = IRRecv::recv ir_pin, 1000
  if decoded && !decoded.is_repeat_code
    print_decoded_data decoded
  end
end

これを実行して、白リモコンのPOWERボタンに対してカスタムコード: 0x10EF, データコード: 0xD827が出れば成功だ。各ボタンに対応するコードは秋月電子のページにあるデータシートに載っている。以下一部抜粋

https://x.com/warumizu/status/1952063295709700443

NEC方式の仕様とライブラリ仕様

irrecv.rbで受信するにはIRRecv::recvメソッドを使う。引数は受信するピンと、タイムアウト値(ミリ秒)だ。つまりこのメソッドを呼ぶと何かしらデータを受け取る、あるいはタイムアウトするまでブロックされる。受信できた場合はIRRecv::DecodedDataのインスタンスが返り、タイムアウトの場合はnilが返る。

DecodedDataについてだが、NEC方式のリモコンからは通常カスタムコードとデータコードの2つが来るので、それぞれraw_custom_coderaw_data_codeというメンバに格納されている。それぞれ整数2つを持つArrayだ。カスタムコードはメーカー識別コードのようなもので16ビットからなると説明しているサイトが多い。が、白リモコンのデータシートには1バイト目が「メーカー識別コード」、2バイト目は「メーカー識別コード(反転)」となっていて、これが正しければ実質1バイトだ。白リモコンから来るカスタムコードは0x10, 0xEFであり、確かに足すと0xffになる。

データコードは8ビットだが、その後にやはり反転したバイトが来る。先の例ではD827のD8がデータコード、27は反転した値だ。

また、リモコンのボタンを押しっぱなしにするとリモコンからはリピートコードが送られ、カスタムコードとデータコードは来ない。この場合はis_repeat_codetrueになり、raw_custom_coderaw_data_codenilになる。

その他、leader codeやstop bitが来るが、これはタイミングを表すものなので、リモコンの操作を単に受け取りたいだけなら気にする必要なない。

データシートから引用

ビジーループと割り込み

今回作ったIRRecv::recvは処理をブロックするため受信待ち状態のときに他のことをすることはできない。GPIOに何か信号が来たときに呼ばれる割り込み処理のようなことがしたいところで、Arduinoで作っている例を見るとattachInterruptを使っているのを見かえる。PicoRubyでは汎用的な割り込み処理が実装されていないようなので(自分が知らないだけかも)、今回はビジーループで実装した。

プラレールと組み合わせてみる

リモコンの受信ができればプラレールにモータードライバ、前回やったDFPlayer Miniを組み合わせて走行・停止の操作 + 発車メロディーを鳴らせるプラレールを作ったりできる。

https://x.com/warumizu/status/1951976616101347333

Discussion