赤外線リモコン信号を記録して送信する学習リモコンをラズパイゼロでつくる。

14 August 2019 — Written by Aki (Updated on 02 January 2020)
#赤外線リモコン#Raspberrypi#Rust

赤外線リモコン信号の解析アプリケーションのページはなぜかよく見られているようなので, そのページで解析していたリモコン信号を手に入れるデバイスを作ってみようかと思う。

注意 : あくまで実験が目的です。 今回活用する部品は今までの余り物を探して使います。

用意するもの

  • Raspberry pi Zero W (ラズパイゼロW) または WH
  • 電源アダプタ
  • USBケーブル
  • ケース
  • SDカード
  • 赤外線受信モジュール PL-IRM2161-XD1 または 3.3V電源で使える物
  • デカップリングコンデンサ
    0.1uFのセラミックコンデンサ
    100uFの電解コンデンサ(無くても動きますが)
  • ブレッドボード
  • ブレッドボードジャンパワイヤー
  • 赤外線LED
    部品箱から出てきたLUIR034
    これは波長850nmなので実は今回の応用に不適切な部品。
    手に入れるなら波長950nmの物を使ってください。
  • トランジスタとかFETとか, 赤外線LEDを駆動するデバイス
    自分は部品箱から出てきた反転バッファ TC4049BP を使っているが, 駆動回路は各自設計してください。

実験回路をブレッドボード上に作る

実験回路
実験回路
実験回路
実験回路
実験回路
実験回路
実験回路
実験回路
実験回路
実験回路

目的が実験なので雑です。常用しない事。自分でも知性を感じない回路だと思っている。

このあたりがよろしくない

  1. 赤外線LEDを「電源直結」にしているとか。
    (電流?電源自身のインピーダンスで制限されるでしょう)
  2. さらに手持ちの赤外線LEDを全部乗せるとか
    (電流バランス?よろしくないね)
  3. 1ゲートのNOTで供給電流がもの足りないから6ゲート束にするとか。
  4. さらにICを2階建てで12ゲートを束にするとか。

とはいえTC4049は “出力電流が大きく”とメーカーが言っているうえにゲートを並列にして電流を増やすのはよくあること。

TC4049BP/BF 、 TC4050BP/BF は 、 6 回路のバッファで TC4049BP/BF が反転型、TC4050BP/BF が非反転型です。 出力電流が大きく、1 個の TTL を直接駆動できるため、CMOS か ら TTL の接続に有用です。入力は、VDD に無関係に VSS + 18 V ま での電圧を加えることができるため、15 V、10 V 系の CMOS 論理 回路から 5 V 系の CMOS/TTL 論理回路へのレベル変換 IC としても 使用できます。 回路構造は、TC4049BP/BF が 3 段インバータ、TC4050BP/BF が 2 段インバータのため、理想的なスイッチング特性を示します。

TC4049BPデーターシートTC4049

皆さんはよいLED駆動回路を設計して
赤外線LEDには思い切りよく電流を流してやってください。

上の回路は単なる実験回路とはいえ雑すぎなので真似しない事。

ここから, まじめにLED駆動回路を設計しました。

まじめにトランジスタで赤外線LEDをドライブする回路を設計する。

赤外線送受信回路
赤外線送受信回路
Texas Instruments - Application Report SLAA644B

送信回路は図示の通りNPN トランジスタを エミッタ接地スイッチング回路 で赤外線LEDをドライブする。
部品は 秋月電子通商にて購入。

  • ここで使うラズパイゼロWにはラズパイ3用の2.5A出力が出来る電源をおすすめします.(電源が弱いとラズパイが停止するので)
  • TOSHIBA製 NPNトランジスタ 2SC1815-Y (10個80円 @ 8円) (通販コード I-04268)
  • Optosupply製 赤外線LED 940nm OSI5FU5111C-40 (5個100円 @ 20円) (通販コード I-03261)
  • パスコンまたの名をデカップリングコンデンサ 0.1uF50V (通販コード P-00090)
  • 10uF ~ 100uF 位のコンデンサ(電解でもセラミックでもよい)
  • 抵抗器

赤外線LED OSI5FU5111C-40
赤外線LED OSI5FU5111C-40
NPNトランジスタ 2SC1815-Y
NPNトランジスタ 2SC1815-Y
抵抗器 1/4W
抵抗器 1/4W

赤外線LEDのデーターシートを確認する

出典: 赤外線LED OSI5FU5111C-40のデータシート
出典: 赤外線LED OSI5FU5111C-40のデータシート

ItemVarValueUnitNote
DC Forward CurrentIF100mA絶対最大定格
Pulse Forward CurrentIFP1000mA絶対最大定格
DC Forward VoltageVF1.35V

トランジスタのデーターシートを確認する

出典: トランジスタ 2SC1815のデータシート
出典: トランジスタ 2SC1815のデータシート

項目記号単位付記
コレクタ・エミッタ間電圧VCEO50V絶対最大定格
コレクタ電流IC150mA絶対最大定格
コレクタ損失PC400mW絶対最大定格
コレクタ・エミッタ間飽和電圧VCE(sat)0.1VIC=100mA, IB=10mA
ベース・エミッタ間飽和電圧VBE(sat)1.0VIC=100mA, IB=10mA

回路の設計手法

  1. 赤外線リモコン信号の送信は赤外線LEDを 38kHz, 1/3 dutyのパルスで点灯することを念頭におき, 赤外線LEDのデーターシートを参考に
    今回は赤外線LEDに流す電流IFを100mAに決める.
  2. 赤外線LEDの順方向電流IF=100mAの条件でVF=1.35V(typ.)
  3. トランジスタの動作領域を決める
    出典: FAQ 1005881 : アナログとディジタルの違いは? - Renesas Electronics
    出典: FAQ 1005881 : アナログとディジタルの違いは? - Renesas Electronics
    赤外線LEDをドライブするエミッタ接地スイッチング回路の入力はディジタル信号(ON / OFFのみ)なので, 自ずと図に示されたディジタル領域を使うことになる。
    つまり, 遮断領域(OFF状態), 飽和領域(ON状態)
    FAQ 1006317 : トランジスタの動作領域である飽和領域、活性領域、遮断領域とはそれぞれどの様な動作状態を言うのですか? - Renesas Electronics
  4. トランジスタのコレクタ電流IC=100mAの条件でVCEが飽和するベース電流IBをグラフから読む.
    ここで言っているコレクタ飽和電圧VCE(sat)とは
    FAQ 1008933 : コレクタ飽和電圧(Collector Saturation Voltage) - Renesas Electronics
    出典: トランジスタ 2SC1815のデータシート
    出典: トランジスタ 2SC1815のデータシート
    ベース電流IB=5mA ~ 6mAで飽和し, その時のコレクタ飽和電圧VCE(sat)をグラフから読む.
    (0.1V ~0.3Vくらいだろうか)
  5. データーシートからVCE(sat) - IC 特性をグラフから読む.
    出典: トランジスタ 2SC1815のデータシート
    出典: トランジスタ 2SC1815のデータシート
    トランジスタのコレクタ電流IC=100mA, ベース電流IB= Ic/10 = 100/10 = 10mA の条件でVCE(sat)が0.1V.
    ベース電流IB=5mA ~ 6mAの条件でのコレクタ飽和電圧VCE(sat)はこのグラフからは分からないので, とりあえず VCE(sat)は0.3Vで設計を進める.
    ベース電流IBを10mA流せば0.1Vに落ちるのが分かっているので, これが問題になるなら後でベース電流を増やせば良いでしょう.
  6. トランジスタのコレクタ損失は
    コレクタ飽和電圧VCE(sat) = 0.3V, 電流I=100mAの条件で
    コレクタ損失PC = VCE(sat) I = 0.3 0.1 = 30mW(DC) = 10mW(1/3 Duty)
    2SC1815のコレクタ損失PC(絶対最大定格) は 400mWなのでまだ余裕がある.
  7. ベース電流IB=5mA ~ 6mAになるベース抵抗値を決める.
    ラズパイのGPIOはHigh時に3.3V, Low時に0Vとなるので
    ベース抵抗にかかる電圧V = 3.3 - 0.6 = 2.7V
    (シリコントランジスタのVBEは0.6~0.7, この条件ならデーターシートより最大値1.0V)
    ベース抵抗R = 2.7 / 5mA = 540Ωだから
    手持ちの抵抗器 510Ω を選択する.
  8. 赤外線LEDの電流制限抵抗値を決める.
    赤外線LEDの順方向電流IF=100mAになる抵抗R
    = (5.0 - 1.35 - 0.3)V / 0.1A = 33.5Ω
    抵抗で消費される損失電力(直流の場合) P = V * V / R = (5.0 - 1.35 - 0.3)2 / 33.5 = 0.335W

今回は1/3dutyなのでこの1/3になるが, 設計上は余裕をもって損失0.335W以上を許容する抵抗器を採用する.

結果, 赤外線LEDの電流制限抵抗は 33.3Ω, 許容電力0.75Wの抵抗とみなせる許容電力1/4Wの100Ω抵抗器3並列.

各部の電圧分担は図を参照.

赤外線LEDに1.35V 抵抗器に3.35V 負担させているので定数を変更すると赤外線LEDを直列に増やせるわけですが, その設計は読者の課題とする.(自分の手を動かすのは大事だとおもう)

設計したトランジスタ(2SC1815)スイッチング回路

2SC1815による赤外線LEDドライブ回路 - scheme-it

2SC1815で赤外線LEDを100mAでドライブする回路
2SC1815で赤外線LEDを100mAでドライブする回路

まじめにトランジスタで赤外線LEDをドライブする回路を設計する。(その2)

NPNトランジスタ 2SC1815のローサイドスイッチ回路を設計したので, ついでにPNPトランジスタ 2SA1015のハイサイドスイッチ回路を設計する.

PNPトランジスタ 2SA1015-Y
PNPトランジスタ 2SA1015-Y

2SC1815のコンプリメンタリトランジスタである2SA1015なので, そのまま回路を反転させるとよい.
と思った?

残念. この回路の場合は違います.

電源が +5V に接続されている回路に2SA1015を介してラズパイのGPIOをつなぐとラズパイが壊れます.
ラズパイのGPIOには3.3V以上の電圧をかけてはいけません. (5VトレラントI/Oのマイコンなら耐える)

ということで, GPIOを2SC1815のエミッタ接地スイッチング回路で受けて, 2SA1015のエミッタ接地スイッチング回路に接続します.
2SC1815の絶対最大定格 VCEOは50Vあるので十分耐えられる.

設計は2SC1815のやり方と同じ.

設計したトランジスタ(2SA1015)スイッチング回路

2SA1015による赤外線LEDドライブ回路 - scheme-it

2SA1815で赤外線LEDを107mAでドライブする回路
2SA1815で赤外線LEDを107mAでドライブする回路

以上の回路をブレッドボード上で試作して, エアコン, LEDシーリングライト, 扇風機相手に赤外線リモコン信号が2mは飛ぶ事を確認できた.

知っていたが, 赤外線LEDに100mAではおとなしすぎて飛距離が足らんな...

飛距離がほしい, もっと赤外線にパワーがほしい.

そもそも38kHz, 1/3duty のパルス点灯なんだから 赤外線LED OSI5FU5111C-40 はもっといける.

まじめにトランジスタで赤外線LEDをドライブする回路を設計する。(その3)

今度は赤外線LEDに流す電流IFを200mA超に決める.
NPNダーリントントランジスタ 2SD1866はhFEが最小値で1000, 最大値で10000あるから, ベース電流が少なく済んでラズパイのGPIOに優しい.

部品は 秋月電子通商にて購入。

  • ここで使うラズパイゼロWにはラズパイ3用の2.5A出力が出来る電源をおすすめします.(電源が弱いとラズパイが停止するので)
  • ROHM製 NPNダーリントントランジスタ 2SD1866 (10個150円 @ 15円) (通販コード I-11860)
  • Optosupply製 赤外線LED 940nm OSI5FU5111C-40 (5個100円 @ 20円) (通販コード I-03261)
  • パスコンまたの名をデカップリングコンデンサ 0.1uF50V (通販コード P-00090)
  • 10uF ~ 100uF 位のコンデンサ(電解でもセラミックでもよい)
  • 抵抗器

赤外線LED OSI5FU5111C-40
赤外線LED OSI5FU5111C-40
NPNダーリントントランジスタ 2SD1866
NPNダーリントントランジスタ 2SD1866

トランジスタのデーターシートを確認する

出典: トランジスタ 2SD1866のデータシート
出典: トランジスタ 2SD1866のデータシート

モーター・リレードライブ用 (60±10V, 2A) 2SD2212 / 2SD2143 / 2SD1866

  • 特長
    1) コレクタ・ベース間にツェナーDi を内蔵。
    2) L 負荷での逆起電力サージに強い。
    3) BE 間に抵抗を内蔵。
    4) ダンパーDi 内蔵。

    項目記号単位付記
    コレクタ電流IC2A(DC)絶対最大定格
    コレクタ電流IC3A(Pulse)絶対最大定格
    コレクタ損失PC1W絶対最大定格
    コレクタ・エミッタ飽和電圧VCE(sat)1.5V(Max)IC / IB = 1A/1mA

回路の設計手法

  1. 赤外線リモコン信号の送信は赤外線LEDを 38kHz, 1/3 dutyのパルスで点灯することを念頭におき, 赤外線LEDのデーターシートを参考に
    今回は赤外線LEDに流す電流IFを200mA超に決める.
  2. 赤外線LEDの順方向電流IF=200mA超の条件でもVF=1.35Vと勝手に決める.
  3. トランジスタのコレクタ電流IC=200mA超の条件でVCEが飽和するベース電流IBをグラフから読む.
    出典: トランジスタ 2SD1866のデータシート
    出典: トランジスタ 2SD1866のデータシート
    ベース電流IB=200uA以上と読める, 今回は500uA流してきっちり飽和させる.
    コレクタ電流IC=200mAでコレクタ飽和電圧VCEをグラフから読む.(0.7Vだね)
  4. トランジスタのコレクタ損失は コレクタ飽和電圧VCE(sat) = 0.7V, 電流I=200mA超の条件で
    コレクタ損失PC = VCE(sat) I = 0.7 0.2 = 140mW(DC) = 47mW(1⁄3 Duty)
    2SD1866のコレクタ損失PC(絶対最大定格) は 1Wなのでまだ余裕がある.
  5. 2SD1866はダーリントン接続トランジスタなのでベース電圧VBEをグラフから読む.
    出典: トランジスタ 2SD1866のデータシート
    出典: トランジスタ 2SD1866のデータシート
    コレクタ電流IC=200mAでのベース電圧VBEをグラフから読む.(1.4Vだね)
  6. ベース電流IB=500uAになるベース抵抗値を決める.
    ラズパイのGPIOはHigh時に3.3V, Low時に0Vとなるので, ベース抵抗にかかる電圧V = 3.3 - 1.4 = 1.9V,
    ベース抵抗R = 1.9V / 0.5mA = 3.8kΩ だから
    手持ちの抵抗器 3.9kΩ を選択する.
  7. 赤外線LEDの電流制限抵抗値を決める.
    赤外線LEDの順方向電流IF=200mA超になる抵抗R = (5.0 - 1.35 - 0.7)V / 0.2A = 16.75Ω以下, 今回は47Ωの4並列=11.75Ω
    抵抗で消費される電力(直流の場合) P = V * V / R = (5.0 - 1.35 - 0.7)2 / 11.75 = 0.740W
    今回は1/3dutyなのでこの1/3になるが, 設計上は余裕をもって損失0.740W以上を許容する抵抗器を採用する.

結果, 赤外線LEDの電流制限抵抗は 11.7Ω, 許容電力1.00Wの抵抗とみなせる許容電力1/4Wの47Ω抵抗器4並列.

各部の電圧分担は図を参照.

設計したトランジスタ(2SD1866)スイッチング回路

2SD1866による赤外線LEDドライブ回路 - scheme-it

2SD1866で赤外線LEDを200mA超でドライブする回路
2SD1866で赤外線LEDを200mA超でドライブする回路

3) BE 間に抵抗を内蔵。

とデーターシートにあるのでBE間の抵抗はなくしました.

ブレッドボード上の試作で, エアコン, LEDシーリングライト, 扇風機相手に赤外線リモコン信号が3mは飛ぶ事を確認できた.
部屋が狭いのでこれ以上は分からないが, 壁の反射でも信号が飛ぶので普通のリモコン位の飛距離

もっと電流を流したいなら自分で設計を変更してください.

最終的に採用した赤外線LEDドライブ回路

購入した赤外線LEDが5個入りだから余った2個も追加して再設計した回路を最終的に採用する.

2SD1866による3連赤外線LEDドライブ回路 - scheme-it

2SD1866で3連赤外線LEDを200mA超でドライブする回路
2SD1866で3連赤外線LEDを200mA超でドライブする回路

OSI5FU5111C-40赤外線LEDは15度で50%減衰の指向性なので赤外線リモコン信号を送信する向きを気にする必要があったけれど
この回路では3連赤外線LEDの方向を適度に散らしておけば部屋のどこにおいても赤外線リモコン信号が届くようになった.

まじめに設計した回路をブレッドボードに試作

設計した3種類の回路をブレッドボードに試作.

  • 右上 2SA1015 エミッタ接地スイッチング回路と2SC1815レベルシフト回路
  • 右下 2SC1815 エミッタ接地スイッチング回路
  • 左下 2SD1866 エミッタ接地スイッチング回路

NPNトランジスタ 2SC1815回路とラズパイを接続する
NPNトランジスタ 2SC1815回路とラズパイを接続する
PNPトランジスタ 2SA1015回路とラズパイを接続する
PNPトランジスタ 2SA1015回路とラズパイを接続する
NPNダーリントントランジスタ 2SD1866回路とラズパイを接続する
NPNダーリントントランジスタ 2SD1866回路とラズパイを接続する

今度は2SC1815と同じように使える汎用トランジスタのPN2222とか使ってみようかな.

  • 2SC1815は60V 150mA 400mW TO-92パッケージでピン配列 ECB (ベースが端)
  • PN2222は 60V 600mA 625mW TO-92パッケージでピン配列 EBC (ベースが真ん中)

ピン配列の違いに注意する必要があるけれど.

ソフトウェアの準備

ラズパイゼロのSDカードにOSのインストールとWifiの設定を済ましておいてください。 すでに良い記事がWebにあるので, この部分は省略します。

プログラミング言語の選定

プログラムは2種類用意します。

  • リモコンから送信された赤外線リモコン信号を記録する
  • 記録した赤外線信号を送信する

ラズパイのハードウェアを低レベルで制御するので通常はC言語かなっと。

irrec プログラム(C言語とpigpioライブラリ)

/*
irremocon <https://github.com/ak1211/irremocon>
Copyright 2019 Akihiro Yamamoto
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
//
// use pigpio libraries. without pigpiod
// install libraries
// sudo apt-get install pigpio
//
// build
//
// $ gcc -Wall -pthread -o irrec1 irrec1.c -lpigpio -lrt
#include <assert.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <sys/timerfd.h>
#include <unistd.h>
#include <pigpio.h>
// 赤外線受信モジュール電源ピン(GPIO 24 / Physical 18)
const uint8_t GPIO_IRM_POWER_PIN = 24;
// 赤外線受信モジュール入力ピン(GPIO 25 / Physical 22)
const uint8_t GPIO_IRM_INPUT_PIN = 25;
// 赤外線受信モジュールは負論理信号
const int ASSERT_IR = 0;
const int NEGATE_IR = 1;
// キャリア周波数[kHz}
// 38kHz
const uint16_t CARRIER_FREQ_KHZ = 38;
// キャリア周期[us]
// 1/38,000 * 1,000,000 = 26us
const uint16_t CARRIER_PERIOD_MICROS = 1000 / CARRIER_FREQ_KHZ;
//
// ラズパイゼロではキャリア周期に同期できず
// タイミング違反が起きるので分周する
//
// プリスケーラの倍率
// 2分周
const uint16_t N_OF_PRESCALER = 2;
// 分周後でのクロックカウンタ増加量
const uint16_t COUNT_PACE = N_OF_PRESCALER;
// タイマー周期[us]
// キャリア周期 * プリスケーラの倍率
const uint16_t TIMER_INTERVAL_MICROS = CARRIER_PERIOD_MICROS * N_OF_PRESCALER;
// この時間信号が変化しないと, 赤外線リモコン信号を読み取るのを終了する
// 34ms
const uint16_t TIMEOUT_COUNTS = 34 * 1000 / TIMER_INTERVAL_MICROS;
//
//
//
ssize_t receive_ir_codes( uint16_t* buffer, size_t buffer_size )
{
assert( TIMEOUT_COUNTS <= buffer_size );
ssize_t ret_val = -1;
int tfd = timerfd_create( CLOCK_REALTIME, 0 );
struct itimerspec interval = {
.it_interval = { .tv_sec = 0, .tv_nsec = TIMER_INTERVAL_MICROS * 1000 },
.it_value = { .tv_sec = 0, .tv_nsec = 1000 }
};
timerfd_settime( tfd, 0, &interval, NULL );
// リモコン信号入力待ち
while (gpioRead(GPIO_IRM_INPUT_PIN) == NEGATE_IR) {
// タイマー待ち
uint64_t v;
if (read( tfd, &v, sizeof(uint64_t) ) < 0) {
goto exit;
}
}
// リモコン信号を検出したのでカウント開始
bool previous = false;
size_t buffer_counter = 0;
uint16_t count = 0;
while (count < TIMEOUT_COUNTS) {
if (previous == gpioRead(GPIO_IRM_INPUT_PIN)) {
// 信号が変化しないならカウンタを増やす
count += COUNT_PACE;
} else {
// 信号が変化したら
// カウント値をバッファに入れて
// カウンタを初期化
buffer[buffer_counter++] = count;
previous = !previous;
count = 0;
}
// タイマー待ち
uint64_t v;
if (read( tfd, &v, sizeof(uint64_t) ) < 0) {
goto exit;
}
}
// 最後のカウント値をバッファに入れる
buffer[buffer_counter++] = count;
ret_val = buffer_counter;
exit:
close(tfd);
return ret_val;
}
//
//
//
int main()
{
if (gpioInitialise() < 0) {
perror("");
return 0;
}
if (gpioSetMode(GPIO_IRM_POWER_PIN, PI_OUTPUT) != 0) {
perror("");
goto exit;
}
// 赤外線受信モジュールに電源を供給する。
if (gpioWrite(GPIO_IRM_POWER_PIN, 1) != 0) {
perror("");
goto exit;
}
fprintf( stderr, "irrec\n" );
fprintf( stderr, "This program is display infrared codes.\n" );
enum { IRCODES_SIZE = 1024 };
uint16_t ircodes[IRCODES_SIZE] = {0};
ssize_t count;
if ((count = receive_ir_codes(ircodes, IRCODES_SIZE)) < 0) {
perror("");
goto exit;
}
//
for( ssize_t c=0 ; c<count ; c++ ) {
int lo =ircodes[c] & 0x00ff;
int hi =ircodes[c] >> 8 & 0x00ff;
printf( "%02X%02X", lo, hi );
}
printf( "\n" );
// 赤外線受信モジュールの電源を切る
if (gpioWrite(GPIO_IRM_POWER_PIN, 0) != 0) {
perror("");
goto exit;
}
fprintf( stderr, "bye.\n");
exit:
gpioTerminate();
return 0;
}
view raw irrec1.c hosted with ❤ by GitHub

irrec プログラム(C言語とpigpioデーモンライブラリ)

/*
irremocon <https://github.com/ak1211/irremocon>
Copyright 2019 Akihiro Yamamoto
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
//
// use pigpiod daemon
//
// install libraries
// sudo apt-get install pigpio
//
// build
//
// $ gcc -Wall -pthread -o irrec2 irrec2.c -lpigpiod_if2 -lrt
#include <assert.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <sys/timerfd.h>
#include <unistd.h>
#include <pigpiod_if2.h>
// 赤外線受信モジュール電源ピン(GPIO 24 / Physical 18)
const uint8_t GPIO_IRM_POWER_PIN = 24;
// 赤外線受信モジュール入力ピン(GPIO 25 / Physical 22)
const uint8_t GPIO_IRM_INPUT_PIN = 25;
// 赤外線受信モジュールは負論理信号
const int ASSERT_IR = 0;
const int NEGATE_IR = 1;
// キャリア周波数[kHz}
// 38kHz
const uint16_t CARRIER_FREQ_KHZ = 38;
// キャリア周期[us]
// 1/38,000 * 1,000,000 = 26us
const uint16_t CARRIER_PERIOD_MICROS = 1000 / CARRIER_FREQ_KHZ;
//
// ラズパイゼロではキャリア周期に同期できず
// タイミング違反が起きるので分周する
//
// プリスケーラの倍率
// 2分周
const uint16_t N_OF_PRESCALER = 2;
// 分周後でのクロックカウンタ増加量
const uint16_t COUNT_PACE = N_OF_PRESCALER;
// タイマー周期[us]
// キャリア周期 * プリスケーラの倍率
const uint16_t TIMER_INTERVAL_MICROS = CARRIER_PERIOD_MICROS * N_OF_PRESCALER;
// この時間信号が変化しないと, 赤外線リモコン信号を読み取るのを終了する
// 34ms
const uint16_t TIMEOUT_COUNTS = 34 * 1000 / TIMER_INTERVAL_MICROS;
//
//
//
ssize_t receive_ir_codes( int pi, uint16_t* buffer, size_t buffer_size )
{
assert( TIMEOUT_COUNTS <= buffer_size );
ssize_t ret_val = -1;
int tfd = timerfd_create( CLOCK_REALTIME, 0 );
struct itimerspec interval = {
.it_interval = { .tv_sec = 0, .tv_nsec = TIMER_INTERVAL_MICROS * 1000 },
.it_value = { .tv_sec = 0, .tv_nsec = 1000 }
};
timerfd_settime( tfd, 0, &interval, NULL );
// リモコン信号入力待ち
while (gpio_read( pi, GPIO_IRM_INPUT_PIN ) == NEGATE_IR) {
// タイマー待ち
uint64_t v;
if (read( tfd, &v, sizeof(uint64_t) ) < 0) {
goto exit;
}
}
// リモコン信号を検出したのでカウント開始
int previous = 0;
size_t buffer_counter = 0;
uint16_t count = 0;
while (count < TIMEOUT_COUNTS) {
if (previous == gpio_read( pi, GPIO_IRM_INPUT_PIN )) {
// 信号が変化しないならカウンタを増やす
count += COUNT_PACE;
} else {
// 信号が変化したら
// カウント値をバッファに入れて
// カウンタを初期化
buffer[buffer_counter++] = count;
previous = !previous;
count = 0;
}
// タイマー待ち
uint64_t v;
if (read( tfd, &v, sizeof(uint64_t) ) < 0) {
goto exit;
}
}
// 最後のカウント値をバッファに入れる
buffer[buffer_counter++] = count;
ret_val = buffer_counter;
exit:
close(tfd);
return ret_val;
}
//
//
//
int main()
{
int pi;
if ((pi = pigpio_start( NULL, NULL )) < 0) {
perror("");
return 0;
}
if (set_mode( pi, GPIO_IRM_POWER_PIN, PI_OUTPUT ) != 0) {
perror("");
goto exit;
}
// 赤外線受信モジュールに電源を供給する。
if (gpio_write( pi, GPIO_IRM_POWER_PIN, 1 ) != 0) {
perror("");
goto exit;
}
fprintf( stderr, "irrec\n" );
fprintf( stderr, "This program is display infrared codes.\n" );
enum { IRCODES_SIZE = 1024 };
uint16_t ircodes[IRCODES_SIZE] = {0};
ssize_t count;
if ((count = receive_ir_codes( pi, ircodes, IRCODES_SIZE )) < 0) {
perror("");
goto exit;
}
//
for( ssize_t c=0 ; c<count ; c++ ) {
uint16_t lo =ircodes[c] & 0x00ff;
uint16_t hi =ircodes[c] >> 8 & 0x00ff;
printf( "%02X%02X", lo, hi );
}
printf( "\n" );
// 赤外線受信モジュールの電源を切る
if (gpio_write( pi, GPIO_IRM_POWER_PIN, 0 ) != 0) {
perror("");
goto exit;
}
fprintf( stderr, "bye.\n");
exit:
pigpio_stop(pi);
return 0;
}
view raw irrec2.c hosted with ❤ by GitHub

irsend プログラム(C言語とpigpioデーモンライブラリ)

/*
irremocon <https://github.com/ak1211/irremocon>
Copyright 2019 Akihiro Yamamoto
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
//
// use pigpiod daemon
//
// install libraries
// sudo apt-get install pigpio
//
// build
//
// $ gcc -Wall -pthread -o irsend irsend.c -lpigpiod_if2 -lrt
#include <assert.h>
#include <stdbool.h>
#include <ctype.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include <sys/timerfd.h>
#include <unistd.h>
#include <pigpiod_if2.h>
// キャリア周波数[kHz}
// 38kHz
const uint16_t CARRIER_FREQ_KHZ = 38;
// キャリア周期[us]
// 1/38,000 * 1,000,000 = 26us
const uint16_t CARRIER_PERIOD_MICROS = 1000 / CARRIER_FREQ_KHZ;
// 1/3 duty cycle
const uint16_t ON_DUTY_MICROS = CARRIER_PERIOD_MICROS / 3;
///
/// リモコン信号送信
///
void transmit_ir_codes( int pi, uint16_t* ircodes, size_t ircodes_size )
{
int tfd = timerfd_create( CLOCK_REALTIME, 0 );
uint64_t v;
struct itimerspec timer = {
.it_interval = { .tv_sec = 0, .tv_nsec = 0 },
.it_value = { .tv_sec = 0, .tv_nsec = 0 }
};
for(int i=0 ; i<ircodes_size/2 ; i+=2 ) {
uint16_t ton = ircodes[i];
uint16_t toff = ircodes[i+1];
timer.it_value.tv_nsec = 26 * ton * 1000;
timerfd_settime( tfd, 0, &timer, NULL );
//
// 赤外線LED出力ピン (GPIO 18 / Physical 12)
hardware_PWM( pi, 18, 38000, 1000000/3 );
// タイマー待ち
if (read( tfd, &v, sizeof(uint64_t) ) < 0) {
goto exit;
}
//
timer.it_value.tv_nsec = 26 * toff * 1000;
timerfd_settime( tfd, 0, &timer, NULL );
//
// 赤外線LED出力ピン (GPIO 18 / Physical 12)
hardware_PWM( pi, 18, 0, 0 );
// タイマー待ち
if (read( tfd, &v, sizeof(uint64_t) ) < 0) {
goto exit;
}
}
exit:
hardware_PWM( pi, 18, 0, 0 );
}
//
//
//
int main()
{
int pi;
if ((pi = pigpio_start( NULL, NULL )) < 0) {
perror("");
return 0;
}
fprintf( stderr, "irsend\n" );
enum { LINE_BUFFER_SIZE = 1024 };
char line_buffer[LINE_BUFFER_SIZE ];
if (fgets( line_buffer, LINE_BUFFER_SIZE, stdin ) == NULL) {
goto exit;
}
char* p;
if ((p = strrchr( line_buffer, '\n' )) != NULL) {
*p = '\0';
}
if (strlen(line_buffer) % 4 != 0) {
perror("bad length.");
goto exit;
}
for (p=line_buffer ; *p != '\0' ; p++) {
if (!isxdigit(*p)) {
perror("bad inputs.");
goto exit;
}
}
enum { IRCODES_SIZE = 1024 };
uint16_t ircodes[IRCODES_SIZE] = {0};
size_t count = 0;
for (size_t i=0 ; i<strlen(line_buffer) ; i+=4) {
char work[3] = "";
unsigned int lo, hi;
strncpy( work, &line_buffer[i], 2);
sscanf( work, "%x", &lo );
strncpy( work, &line_buffer[i+2], 2);
sscanf( work, "%x", &hi );
ircodes[count++] = hi << 8 | lo ;
}
for( ssize_t c=0 ; c<count ; c++ ) {
int lo =ircodes[c] & 0x00ff;
int hi =ircodes[c] >> 8 & 0x00ff;
printf( "%02X%02X", lo, hi );
}
printf( "\n" );
transmit_ir_codes( pi, ircodes, count );
fprintf( stderr, "bye.\n");
exit:
pigpio_stop(pi);
return 0;
}
view raw irsend.c hosted with ❤ by GitHub

手続きプログラムとはこんなもん。いろいろしんどい。

このプログラムは参考にしないでね, 自分はプログラムの書き方がよくわからない人なのでプログラムを書くと segmentation fault おこす。

やっぱりRust言語がいいね。

低レベルな制御に「下がる」必要があるプログラマは、お決まりのクラッシュやセキュリティホールのリスクを負わず、 気まぐれなツールチェーンのデリケートな部分を学ぶ必要なくRustで同じことができます。さらにいいことに、 Rustは、スピードとメモリ使用の観点で効率的な信頼性の高いコードへと自然に導くよう設計されています。

まえがき – The Rust Programming Language
https://doc.rust-jp.rs/book/second-edition/

Rust言語をラズパイゼロWにインストール

Rustの公式サイト https://www.rust-lang.org/tools/install から

pi@raspberrypi:~/ $ curl https://sh.rustup.rs -sSf | sh

これで入りました。

pi@raspberrypi:~/irremocon/ $ uname -a
Linux raspberrypi 4.19.57+ #1244 Thu Jul 4 18:42:50 BST 2019 armv6l GNU/Linux
pi@raspberrypi:~/irremocon/ $ cargo --version
cargo 1.36.0 (c4fcfb725 2019-05-15)
pi@raspberrypi:~/irremocon/ $ rustc --version
rustc 1.36.0 (a53f9df32 2019-07-03)
pi@raspberrypi:~/irremocon/ $

GitHub

GitHubにソース一式をおいておきます。
https://github.com/ak1211/irremocon

ビルド

使うだけの人はリリースビルドでよろしく。 もちろん開発者はデバッグビルドでもよい。 初回はとても待つことになる。0.88秒となっているのはすでにビルドした後だったので。

pi@raspberrypi:~/irremocon/ $ cargo build --release
    Finished release [optimized] target(s) in 0.88s
pi@raspberrypi:~/irremocon/ $ ls -l target/release      
 合計 5232
 drwxr-xr-x 8 pi pi    4096  813 00:18 build
 drwxr-xr-x 2 pi pi    4096  813 08:12 deps
 drwxr-xr-x 2 pi pi    4096  813 00:18 examples
 drwxr-xr-x 2 pi pi    4096  813 00:18 incremental
 -rwxr-xr-x 2 pi pi 2665516  813 00:30 irrec
 -rw-r--r-- 1 pi pi     108  813 00:30 irrec.d
 -rwxr-xr-x 2 pi pi 2515120  813 08:12 irsend
 -rw-r--r-- 1 pi pi     110  813 00:30 irsend.d
 -rw-r--r-- 1 pi pi      83  813 00:30 libirremocon.d
 -rw-r--r-- 2 pi pi  137282  813 00:29 libirremocon.rlib
 drwxr-xr-x 2 pi pi    4096  813 00:18 native
 pi@raspberrypi:~/irremocon/ $ cargo install --path .
 pi@raspberrypi:~/irremocon/ $

この後。
ラズパイゼロの遅さにいらついたので, ビルドはラズパイ3b+に移行した。

pi@raspberrypi-3:~/irremocon/ir-command $ cargo build --release
   Compiling libc v0.2.66
   Compiling bitflags v1.2.1
   Compiling nix v0.14.1
   Compiling cfg-if v0.1.10
   Compiling lazy_static v1.4.0
   Compiling void v1.0.2
   Compiling termios v0.2.2
   Compiling timerfd v1.0.0
   Compiling rppal v0.11.3
   Compiling getch v0.2.1
   Compiling irremocon v0.1.1 (/home/pi/irremocon/ir-command)
    Finished release [optimized] target(s) in 1m 22s
pi@raspberrypi-3:~/irremocon/ir-command $ cargo install --path .
  Installing irremocon v0.1.1 (/home/pi/irremocon/ir-command)
    Updating crates.io index
    Finished release [optimized] target(s) in 2.41s
   Replacing /home/pi/.cargo/bin/irrec
   Replacing /home/pi/.cargo/bin/irtransmit
    Replaced package `irremocon v0.1.1 (/home/pi/irremocon/ir-command)` with `irremocon v0.1.1 (/home/pi/irremocon/ir-command)` (executables `irrec`, `irtransmit`)
pi@raspberrypi-3:~/irremocon/ir-command $ 

irrec / irtransmit ができあがった実行ファイル。
rustはスクリプト言語ではないので, 実行するだけならrustのインストールはいらない。 もしコマンドが見つからないならcargoのインストールディレクトリである"~/.cargo/bin"にパスが通っている事を確認して。

赤外線リモコン信号の記録

irrecを実行してIRモジュールに手持ちのリモコンを向けてボタンを押すとコードが見られる。
あとはコピーして上のページに貼り付けたら内容が見える。 見えなければ記録に失敗していると思う。

ここでteeを入れているのは標準出力のついでにファイルに記録するため。

pi@raspberrypi:~/irremocon/ $ irrec | tee remocon.ir
 irrec v0.1.1
 This program is display infrared codes.
 <<< Press any key to exit. >>>
 Please press buttons on your remote control.
 7C003C000C002C000C002E000A0010000C000E000C000E000C002C000A0010000C000E000C002C000C002E000C000E000C002E000A0010000A0010000C002C000C002E000C000E000C002C000C002E000C000E000C002C000C0010000A0010000C000E000C000E000C0010000A002E000C000E000C000E000C0010000C002C000C000E000C000E000E000E000C000E000C000E000C000E000E000E000C000E000C002C000C002E000C000E000C000E000C002C000E000E000C000E000C000E000C002E000C002C000C000E000C000E000E000E000C000E000C000E000C000E000E000E000C000E000C000E000C000E000C000E000E002C000C000E000C000E000C002E000C008E02
 bye.
 pi@raspberrypi:~/irremocon/ $ cat remocon.ir                         
 7C003C000C002C000C002E000A0010000C000E000C000E000C002C000A0010000C000E000C002C000C002E000C000E000C002E000A0010000A0010000C002C000C002E000C000E000C002C000C002E000C000E000C002C000C0010000A0010000C000E000C000E000C0010000A002E000C000E000C000E000C0010000C002C000C000E000C000E000E000E000C000E000C000E000C000E000E000E000C000E000C002C000C002E000C000E000C000E000C002C000E000E000C000E000C000E000C002E000C002C000C000E000C000E000E000E000C000E000C000E000C000E000E000E000C000E000C000E000C000E000C000E000E002C000C000E000C000E000C002E000C008E02
 pi@raspberrypi:~/irremocon/ $

これは三菱電機扇風機リモコン入/切のコード

扇風機
扇風機

赤外線リモコン信号の送信設定

https://docs.golemparts.com/rppal/0.11.3/rppal/pwm/index.html

RPPAL – Raspberry Pi Peripheral Access Library ページによるハードウェアPWMの設定

rootユーザーなら権限問題にならないが, ハードウェアPWMを使うのにsudoするのも使いにくいのでこのように設定を追加します。

注意点
  • バックスラッシュの後に空白をいれない。
  • バックスラッシュは日本語環境では¥記号に見える。
  • /boot/config.txt
dtoverlay=pwm
  • /etc/udev/rules.d/99-com.rules
 SUBSYSTEM=="pwm*", PROGRAM="/bin/sh -c '\
    chown -R root:gpio /sys/class/pwm && chmod -R 770 /sys/class/pwm;\
    chown -R root:gpio /sys/devices/platform/soc/*.pwm/pwm/pwmchip* &&\
    chmod -R 770 /sys/devices/platform/soc/*.pwm/pwm/pwmchip*\
'"

再起動

pi@raspberrypi:~/irremocon/ $ sudo reboot

赤外線リモコン信号の送信

扇風機に赤外線LEDを向けて赤外線リモコン信号を送信すると

pi@raspberrypi:~/irremocon/ $ irtransmit < remocon.ir 
irtransmit v0.1.1
pi@raspberrypi:~/irremocon/ $

ピッと言って扇風機が回り出した。

送信
送信
赤外線LEDの点灯は肉眼で見えないのでカメラを通して見る。
バラスト抵抗がないので赤外線LEDに流れる電流を制限する物は電源のインピーダンスとNOTゲートの抵抗のみだから
写真を見ての通りかなり強烈な電流が流れているようだ。

38kHz 1/3duty(点灯, 消灯, 消灯)の繰り返しのパルス点灯だから, このくらいならデバイスが耐えている。

まじめに設計した回路で赤外線信号を送信

上の実験回路は赤外線LEDを抵抗無しでつないでいるから真似しない。
まじめに設計した以下の回路でよろしく。

NPNトランジスタ 2SC1815回路で赤外線信号送信中
NPNトランジスタ 2SC1815回路で赤外線信号送信中
PNPトランジスタ 2SA1015回路で赤外線信号送信中
PNPトランジスタ 2SA1015回路で赤外線信号送信中
NPNダーリントントランジスタ 2SD1866回路で赤外線信号送信中
NPNダーリントントランジスタ 2SD1866回路で赤外線信号送信中

© 2020 Built with GatsbyStarter created by panr

このサイトに書かれた内容によって生じた損害等の一切の責任を負いかねますので, 予めご了承ください。