赤外線リモコン信号の解析アプリケーションのページはなぜかよく見られているようなので, そのページで解析していたリモコン信号を手に入れるデバイスを作ってみようかと思う。
用意するもの
- Raspberry pi Zero W (ラズパイゼロW) または WH
- 電源アダプタ
- USBケーブル
- ケース
- SDカード
- 赤外線受信モジュール PL-IRM2161-XD1 または 3.3V電源で使える物
- デカップリングコンデンサ
0.1uFのセラミックコンデンサ
100uFの電解コンデンサ(無くても動きますが) - ブレッドボード
- ブレッドボードジャンパワイヤー
- 赤外線LED
部品箱から出てきたLUIR034
これは波長850nmなので実は今回の応用に不適切な部品。
手に入れるなら波長950nmの物を使ってください。 - トランジスタとかFETとか, 赤外線LEDを駆動するデバイス
自分は部品箱から出てきた反転バッファ TC4049BP を使っているが, 駆動回路は各自設計してください。
実験回路をブレッドボード上に作る
目的が実験なので雑です。常用しない事。自分でも知性を感じない回路だと思っている。
このあたりがよろしくない
- 赤外線LEDを「電源直結」にしているとか。
(電流?電源自身のインピーダンスで制限されるでしょう) - さらに手持ちの赤外線LEDを全部乗せるとか
(電流バランス?よろしくないね) - 1ゲートのNOTで供給電流がもの足りないから6ゲート束にするとか。
- さらに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データーシート1
皆さんはよいLED駆動回路を設計して
赤外線LEDには思い切りよく電流を流してやってください。
上の回路は単なる実験回路とはいえ雑すぎなので真似しない事。
ここから, まじめにLED駆動回路を設計しました。
まじめにトランジスタで赤外線LEDをドライブする回路を設計する。
送信回路は図示の通り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のデーターシートを確認する
Item | Var | Value | Unit | Note |
---|---|---|---|---|
DC Forward Current | IF | 100 | mA | 絶対最大定格 |
Pulse Forward Current | IFP | 1000 | mA | 絶対最大定格 |
DC Forward Voltage | VF | 1.35 | V |
トランジスタのデーターシートを確認する
項目 | 記号 | 値 | 単位 | 付記 |
---|---|---|---|---|
コレクタ・エミッタ間電圧 | VCEO | 50 | V | 絶対最大定格 |
コレクタ電流 | IC | 150 | mA | 絶対最大定格 |
コレクタ損失 | PC | 400 | mW | 絶対最大定格 |
コレクタ・エミッタ間飽和電圧 | VCE(sat) | 0.1 | V | IC=100mA, IB=10mA |
ベース・エミッタ間飽和電圧 | VBE(sat) | 1.0 | V | IC=100mA, IB=10mA |
回路の設計手法
- 赤外線リモコン信号の送信は赤外線LEDを 38kHz, 1/3 dutyのパルスで点灯することを念頭におき, 赤外線LEDのデーターシートを参考に
今回は赤外線LEDに流す電流IFを100mAに決める. - 赤外線LEDの順方向電流IF=100mAの条件でVF=1.35V(typ.)
- トランジスタの動作領域を決める
赤外線LEDをドライブするエミッタ接地スイッチング回路の入力はディジタル信号(ON / OFFのみ)なので, 自ずと図に示されたディジタル領域を使うことになる。
つまり, 遮断領域(OFF状態), 飽和領域(ON状態)
FAQ 1006317 : トランジスタの動作領域である飽和領域、活性領域、遮断領域とはそれぞれどの様な動作状態を言うのですか? - Renesas Electronics - トランジスタのコレクタ電流IC=100mAの条件でVCEが飽和するベース電流IBをグラフから読む.
ここで言っているコレクタ飽和電圧VCE(sat)とは
FAQ 1008933 : コレクタ飽和電圧(Collector Saturation Voltage) - Renesas Electronicsベース電流IB=5mA ~ 6mAで飽和し, その時のコレクタ飽和電圧VCE(sat)をグラフから読む.
(0.1V ~0.3Vくらいだろうか) - データーシートからVCE(sat) - IC 特性をグラフから読む.
トランジスタのコレクタ電流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に落ちるのが分かっているので, これが問題になるなら後でベース電流を増やせば良いでしょう. - トランジスタのコレクタ損失は
コレクタ飽和電圧VCE(sat) = 0.3V, 電流I=100mAの条件で
コレクタ損失PC = VCE(sat) * I = 0.3 * 0.1 = 30mW(DC) = 10mW(1/3 Duty)
2SC1815のコレクタ損失PC(絶対最大定格) は 400mWなのでまだ余裕がある. - ベース電流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Ω を選択する. - 赤外線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
まじめにトランジスタで赤外線LEDをドライブする回路を設計する。(その2)
NPNトランジスタ 2SC1815のローサイドスイッチ回路を設計したので, ついでにPNPトランジスタ 2SA1015のハイサイドスイッチ回路を設計する.
2SC1815のコンプリメンタリトランジスタである2SA1015なので, そのまま回路を反転させるとよい.
と思った?
残念. この回路の場合は違います.
電源が +5V に接続されている回路に2SA1015を介してラズパイのGPIOをつなぐとラズパイが壊れます.
ラズパイのGPIOには3.3V以上の電圧をかけてはいけません. (5VトレラントI/Oのマイコンなら耐える)
ということで, GPIOを2SC1815のエミッタ接地スイッチング回路で受けて, 2SA1015のエミッタ接地スイッチング回路に接続します.
2SC1815の絶対最大定格 VCEOは50Vあるので十分耐えられる.
設計は2SC1815のやり方と同じ.
設計したトランジスタ(2SA1015)スイッチング回路
2SA1015による赤外線LEDドライブ回路 - scheme-it
以上の回路をブレッドボード上で試作して, エアコン, 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 位のコンデンサ(電解でもセラミックでもよい)
- 抵抗器
トランジスタのデーターシートを確認する
モーター・リレードライブ用 (60±10V, 2A) 2SD2212 / 2SD2143 / 2SD1866
- 特長
- コレクタ・ベース間にツェナーDi を内蔵。
- L 負荷での逆起電力サージに強い。
- BE 間に抵抗を内蔵。
- ダンパーDi 内蔵。
項目 | 記号 | 値 | 単位 | 付記 |
---|---|---|---|---|
コレクタ電流 | IC | 2 | A(DC) | 絶対最大定格 |
コレクタ電流 | IC | 3 | A(Pulse) | 絶対最大定格 |
コレクタ損失 | PC | 1 | W | 絶対最大定格 |
コレクタ・エミッタ飽和電圧 | VCE(sat) | 1.5 | V(Max) | IC / IB = 1A/1mA |
回路の設計手法
- 赤外線リモコン信号の送信は赤外線LEDを 38kHz, 1/3 dutyのパルスで点灯することを念頭におき, 赤外線LEDのデーターシートを参考に
今回は赤外線LEDに流す電流IFを200mA超に決める. - 赤外線LEDの順方向電流IF=200mA超の条件でもVF=1.35Vと勝手に決める.
- トランジスタのコレクタ電流IC=200mA超の条件でVCEが飽和するベース電流IBをグラフから読む.
ベース電流IB=200uA以上と読める, 今回は500uA流してきっちり飽和させる.
コレクタ電流IC=200mAでコレクタ飽和電圧VCEをグラフから読む.(0.7Vだね) - トランジスタのコレクタ損失は
コレクタ飽和電圧VCE(sat) = 0.7V, 電流I=200mA超の条件で
コレクタ損失PC = VCE(sat) * I = 0.7 * 0.2 = 140mW(DC) = 47mW(1⁄3 Duty)
2SD1866のコレクタ損失PC(絶対最大定格) は 1Wなのでまだ余裕がある. - 2SD1866はダーリントン接続トランジスタなのでベース電圧VBEをグラフから読む.
コレクタ電流IC=200mAでのベース電圧VBEをグラフから読む.(1.4Vだね)
- ベース電流IB=500uAになるベース抵抗値を決める.
ラズパイのGPIOはHigh時に3.3V, Low時に0Vとなるので, ベース抵抗にかかる電圧V = 3.3 - 1.4 = 1.9V,
ベース抵抗R = 1.9V / 0.5mA = 3.8kΩ だから
手持ちの抵抗器 3.9kΩ を選択する. - 赤外線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
3) BE 間に抵抗を内蔵。
とデーターシートにあるのでBE間の抵抗はなくしました.
ブレッドボード上の試作で, エアコン, LEDシーリングライト, 扇風機相手に赤外線リモコン信号が3mは飛ぶ事を確認できた.
部屋が狭いのでこれ以上は分からないが, 壁の反射でも信号が飛ぶので普通のリモコン位の飛距離
もっと電流を流したいなら自分で設計を変更してください.
最終的に採用した赤外線LEDドライブ回路
購入した赤外線LEDが5個入りだから余った2個も追加して再設計した回路を最終的に採用する.
2SD1866による3連赤外線LEDドライブ回路 - scheme-it
OSI5FU5111C-40赤外線LEDは15度で50%減衰の指向性なので赤外線リモコン信号を送信する向きを気にする必要があったけれど
この回路では3連赤外線LEDの方向を適度に散らしておけば部屋のどこにおいても赤外線リモコン信号が届くようになった.
まじめに設計した回路をブレッドボードに試作
設計した3種類の回路をブレッドボードに試作.
- 右上 2SA1015 エミッタ接地スイッチング回路と2SC1815レベルシフト回路
- 右下 2SC1815 エミッタ接地スイッチング回路
- 左下 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; | |
} |
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; | |
} |
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; | |
} |
手続きプログラムとはこんなもん。いろいろしんどい。
このプログラムは参考にしないでね, 自分はプログラムの書き方がよくわからない人なのでプログラムを書くと 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 から
|
|
これで入りました。
|
|
GitHub
GitHubにソース一式をおいておきます。
https://github.com/ak1211/irremocon
ビルド
使うだけの人はリリースビルドでよろしく。 もちろん開発者はデバッグビルドでもよい。 初回はとても待つことになる。0.88秒となっているのはすでにビルドした後だったので。
|
|
この後。
ラズパイゼロの遅さにいらついたので, ビルドはラズパイ3b+に移行した。
|
|
irrec / irtransmit ができあがった実行ファイル。
rustはスクリプト言語ではないので, 実行するだけならrustのインストールはいらない。
もしコマンドが見つからないならcargoのインストールディレクトリである"~/.cargo/bin"にパスが通っている事を確認して。
赤外線リモコン信号の記録
irrecを実行してIRモジュールに手持ちのリモコンを向けてボタンを押すとコードが見られる。
あとはコピーして上のページに貼り付けたら内容が見える。
見えなければ記録に失敗していると思う。
ここでteeを入れているのは標準出力のついでにファイルに記録するため。
|
|
これは三菱電機扇風機リモコン入/切のコード
赤外線リモコン信号の送信設定
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
|
|
- /etc/udev/rules.d/99-com.rules
|
|
再起動
|
|
赤外線リモコン信号の送信
扇風機に赤外線LEDを向けて赤外線リモコン信号を送信すると
|
|
ピッと言って扇風機が回り出した。
赤外線LEDの点灯は肉眼で見えないのでカメラを通して見る。
バラスト抵抗がないので赤外線LEDに流れる電流を制限する物は電源のインピーダンスとNOTゲートの抵抗のみだから
写真を見ての通りかなり強烈な電流が流れているようだ。
38kHz 1/3duty(点灯, 消灯, 消灯)の繰り返しのパルス点灯だから, このくらいならデバイスが耐えている。
まじめに設計した回路で赤外線信号を送信
上の実験回路は赤外線LEDを抵抗無しでつないでいるから真似しない。
まじめに設計した以下の回路でよろしく。