Arduino Mega 2560 LESSON:14 赤外線受信モジュール
概要
赤外線受信モジュールを接続するレッスンです。
完成図は以下のとおりです。
KiCADで追加した部品
- Conn_01x03:Generic connector, single row, 01x03, script generated (kicad-library-utils/schlib/autogen/connector)
- +5V
- GND
- PWR_FLAG
KiCADで追加したフットプリント
- Conn_01x03:Connector_PinHeader_2.54mm:PinHeader_1x03_P2.54mm_Vertical
- Conn_01x03:Connector_PinSocket_2.54mm:PinSocket_1x03_P2.54mm_Vertical
PCB図は以下のようなもの。
3DPreview図は以下のようなもの。
コードは以下のとおりです。
//www.elegoo.com
//2023.05.05
#include "IRremote.h"
int receiver = 11; // Signal Pin of IR receiver to Arduino Digital Pin 11
/*-----( Declare objects )-----*/
IRrecv irrecv(receiver); // create instance of 'irrecv'
//vairable uses to store the last decodedRawData
uint32_t last_decodedRawData = 0;
/*-----( Function )-----*/
void translateIR() // takes action based on IR code received
{
// 受信したプロトコル名を表示
Serial.print("Protocol: ");
Serial.println(getProtocolString(irrecv.decodedIRData.protocol));
// Check if it is a repeat IR code
if (irrecv.decodedIRData.flags)
{
//set the current decodedRawData to the last decodedRawData
irrecv.decodedIRData.decodedRawData = last_decodedRawData;
Serial.println("REPEAT!");
} else
{
//output the IR code on the serial monitor
Serial.print("IR code:0x");
Serial.println(irrecv.decodedIRData.decodedRawData, HEX);
}
//map the IR code to the remote key
switch (irrecv.decodedIRData.decodedRawData)
{
//標準NECプロトコル
case 0xBA45FF00: Serial.println("POWER"); break;
case 0xB847FF00: Serial.println("FUNC/STOP"); break;
case 0xB946FF00: Serial.println("VOL+"); break;
case 0xBB44FF00: Serial.println("FAST BACK"); break;
case 0xBF40FF00: Serial.println("PAUSE"); break;
case 0xBC43FF00: Serial.println("FAST FORWARD"); break;
case 0xF807FF00: Serial.println("DOWN"); break;
case 0xEA15FF00: Serial.println("VOL-"); break;
case 0xF609FF00: Serial.println("UP"); break;
case 0xE619FF00: Serial.println("EQ"); break;
case 0xF20DFF00: Serial.println("ST/REPT"); break;
case 0xE916FF00: Serial.println("0"); break;
case 0xF30CFF00: Serial.println("1"); break;
case 0xE718FF00: Serial.println("2"); break;
case 0xA15EFF00: Serial.println("3"); break;
case 0xF708FF00: Serial.println("4"); break;
case 0xE31CFF00: Serial.println("5"); break;
case 0xA55AFF00: Serial.println("6"); break;
case 0xBD42FF00: Serial.println("7"); break;
case 0xAD52FF00: Serial.println("8"); break;
case 0xB54AFF00: Serial.println("9"); break;
default:
Serial.println(" other button ");
}// End Case
//store the last decodedRawData
last_decodedRawData = irrecv.decodedIRData.decodedRawData;
delay(500); // Do not get immediate repeat
} //END translateIR
void setup() /*----( SETUP: RUNS ONCE )----*/
{
Serial.begin(9600);
Serial.println("IR Receiver Button Decode");
irrecv.enableIRIn(); // Start the receiver
}/*--(end setup )---*/
void loop() /*----( LOOP: RUNS CONSTANTLY )----*/
{
if (irrecv.decode()) // have we received an IR signal?
{
translateIR();
irrecv.resume(); // receive the next value
}
}/* --(end main loop )-- */
いろいろなリモコンコードを取得するのは以下のコード
#define RAW_BUFFER_LENGTH 750
#include <IRremote.hpp>
const int IR_RECEIVE_PIN = 11;
void setup() {
Serial.begin(115200);
IrReceiver.begin(IR_RECEIVE_PIN, ENABLE_LED_FEEDBACK);
Serial.println("IR Remote Analyzer");
Serial.println("TV / Recorder / Air Conditioner");
Serial.println("Toshiba / Daikin signal investigation");
}
void loop() {
if (IrReceiver.decode()) {
Serial.println();
Serial.println("============================");
// 受信バッファが足りなかった場合
if (IrReceiver.decodedIRData.flags &
IRDATA_FLAGS_WAS_OVERFLOW) {
Serial.println("WARNING: Buffer overflow!");
}
// ライブラリによるプロトコル解析
IrReceiver.printIRResultShort(&Serial);
// 受信ビット数
Serial.print("Bits: ");
Serial.println(IrReceiver.decodedIRData.numberOfBits);
// リピート判定
if (IrReceiver.decodedIRData.flags &
IRDATA_FLAGS_IS_REPEAT) {
Serial.println("Repeat signal");
}
// 生のパルスデータ
Serial.println("--- Raw Timing ---");
IrReceiver.printIRResultRawFormatted(&Serial, true);
Serial.println("============================");
IrReceiver.resume();
}
}
ライブラリを使わないIR読み取りコードは以下のとおりです。
const byte IR_PIN = 2;
// 許容するパルス幅(マイクロ秒)
const unsigned long TIMEOUT_US = 12000;
// 指定したレベルが続いた時間を測定する
// タイムアウトした場合は0を返す
unsigned long measurePulse(byte level) {
unsigned long start = micros();
while (digitalRead(IR_PIN) == level) {
if (micros() - start > TIMEOUT_US) {
return 0;
}
}
return micros() - start;
}
// NEC信号の読み取り
bool readNEC(uint32_t &data) {
// リーダーMARK(約9000us)
unsigned long mark = measurePulse(LOW);
if (mark < 8000 || mark > 11000) return false;
// リーダーSPACE(約4500us)
unsigned long space = measurePulse(HIGH);
if (space < 3500 || space > 6000) return false;
data = 0;
// NECは32ビット、LSBから送信
for (byte i = 0; i < 32; i++) {
// データMARK(約560us)
mark = measurePulse(LOW);
if (mark < 300 || mark > 900) return false;
// データSPACE
space = measurePulse(HIGH);
if (space >= 300 && space < 1000) {
// ビット0
}
else if (space >= 1200 && space < 2200) {
// ビット1
data |= (uint32_t)1 << i;
}
else {
return false;
}
}
// 最後のMARK(約560us)
mark = measurePulse(LOW);
if (mark < 300 || mark > 900) return false;
return true;
}
void setup() {
pinMode(IR_PIN, INPUT);
Serial.begin(115200);
Serial.println("NEC Receiver Ready");
}
void loop() {
// 通常はHIGH、受信開始でLOWになる
if (digitalRead(IR_PIN) == LOW) {
uint32_t rawData;
if (readNEC(rawData)) {
Serial.print("RAW: 0x");
// 8桁の16進数で表示
for (int i = 7; i >= 0; i--) {
byte nibble = (rawData >> (i * 4)) & 0x0F;
Serial.print(nibble, HEX);
}
Serial.println();
}
}
}
もっと汎用的な生データを取得するコードはこうだな。
const byte IR_PIN = 11;
// ========================================
// 設定
// ========================================
// true : RAW波形を表示する
// false: 復号結果だけ表示する
const bool SHOW_RAW = true;
// true : 0/1のビット列も表示する
const bool SHOW_BINARY = true;
// true : 1バイトを下位ビットから受信
// false: 1バイトを上位ビットから受信
const bool LSB_FIRST = true;
// 無信号150msで受信終了
const uint32_t END_GAP_US = 150000;
// 無信号15ms以上でフレーム分割
const uint32_t FRAME_GAP_US = 15000;
// 異常に長いMARKへの対策
const uint32_t MAX_MARK_US = 500000;
// RAW記録の最大数
const uint16_t MAX_TIMINGS = 1100;
// HEX復号の最大バイト数
const uint16_t MAX_BYTES = 80;
// ビット判定が曖昧になる幅(us)
const uint32_t AMBIGUOUS_MARGIN_US = 100;
// ========================================
// 記録用メモリ
// ========================================
uint32_t timings[MAX_TIMINGS];
uint16_t timingCount = 0;
uint8_t decodedBytes[MAX_BYTES];
unsigned long operationCount = 0;
// ========================================
// 16進数表示
// ========================================
void printHexByte(uint8_t value) {
if (value < 0x10) {
Serial.print('0');
}
Serial.print(value, HEX);
}
// ========================================
// 2進数表示
// ========================================
void printBinaryByte(uint8_t value) {
for (int8_t i = 7; i >= 0; i--) {
Serial.print((value >> i) & 1);
}
}
// ========================================
// RAW波形表示
// ========================================
void printRaw(uint16_t startIndex,
uint16_t endIndex) {
Serial.println();
Serial.println("--- RAW TIMING (us) ---");
uint8_t column = 0;
for (uint16_t i = startIndex;
i < endIndex; i++) {
// 奇数インデックスがSPACE
// 偶数インデックスがMARK
if ((i & 1) == 0) {
Serial.print('+');
} else {
Serial.print('-');
}
Serial.print(timings[i]);
Serial.print(' ');
column++;
if (column >= 8) {
Serial.println();
column = 0;
}
}
Serial.println();
}
// ========================================
// パルス距離方式の解析
// ========================================
void decodeFrame(uint16_t startIndex,
uint16_t endIndex) {
uint16_t rawCount = endIndex - startIndex;
// 単独MARKの場合
if (rawCount == 1) {
Serial.println();
Serial.println("Single MARK / Preamble");
Serial.print("MARK (us): ");
Serial.println(timings[startIndex]);
return;
}
// 想定する構造:
// Header MARK
// Header SPACE
// Data MARK + SPACE を繰り返す
// Stop MARK
if (rawCount < 5 ||
(rawCount & 1) == 0) {
Serial.println();
Serial.println("Unknown frame structure.");
Serial.println("Use RAW timing data.");
return;
}
uint16_t bitCount = (rawCount - 3) / 2;
Serial.println();
Serial.println("--- FRAME INFORMATION ---");
Serial.print("Candidate Bits: ");
Serial.println(bitCount);
Serial.print("Header MARK (us): ");
Serial.println(timings[startIndex]);
Serial.print("Header SPACE (us): ");
Serial.println(timings[startIndex + 1]);
Serial.print("Stop MARK (us): ");
Serial.println(timings[endIndex - 1]);
// ======================================
// SPACEの最小値・最大値を調査
// ======================================
uint32_t minSpace = 0xFFFFFFFFUL;
uint32_t maxSpace = 0;
for (uint16_t i = 0; i < bitCount; i++) {
uint16_t spaceIndex =
startIndex + 3 + i * 2;
uint32_t value = timings[spaceIndex];
if (value < minSpace) {
minSpace = value;
}
if (value > maxSpace) {
maxSpace = value;
}
}
Serial.print("Data SPACE min (us): ");
Serial.println(minSpace);
Serial.print("Data SPACE max (us): ");
Serial.println(maxSpace);
// ======================================
// パルス距離方式か簡易判定
// ======================================
// 最大SPACEが最小SPACEの
// 1.7倍未満なら2値判定を保留
//
// 単一種類のビットしか含まれない
// フレームもここでは復号を保留する。
if (maxSpace <= minSpace ||
(uint64_t)maxSpace * 10 <
(uint64_t)minSpace * 17) {
Serial.println();
Serial.println(
"Pulse-distance decode unavailable."
);
Serial.println(
"Insufficient SPACE separation."
);
Serial.println(
"RAW timing is still available."
);
return;
}
// SPACEの中間値を判定基準にする
uint32_t threshold =
minSpace + (maxSpace - minSpace) / 2;
Serial.print("Auto threshold (us): ");
Serial.println(threshold);
// ======================================
// ビット復号
// ======================================
uint16_t ambiguousCount = 0;
uint16_t byteCount = (bitCount + 7) / 8;
if (byteCount > MAX_BYTES) {
Serial.println(
"ERROR: Decode buffer too small."
);
return;
}
// 復号バッファを初期化
for (uint16_t i = 0; i < byteCount; i++) {
decodedBytes[i] = 0;
}
if (SHOW_BINARY) {
Serial.println();
Serial.println("--- BINARY DATA ---");
Serial.println("(Transmission order)");
}
for (uint16_t i = 0; i < bitCount; i++) {
uint16_t spaceIndex =
startIndex + 3 + i * 2;
uint32_t space = timings[spaceIndex];
uint8_t bitValue =
(space > threshold) ? 1 : 0;
// 判定しきい値に近いビットを数える
uint32_t difference =
(space > threshold)
? space - threshold
: threshold - space;
if (difference <= AMBIGUOUS_MARGIN_US) {
ambiguousCount++;
}
// ====================================
// バイトへ格納
// ====================================
uint16_t byteIndex = i / 8;
uint8_t bitPosition = i % 8;
if (!LSB_FIRST) {
bitPosition = 7 - bitPosition;
}
if (bitValue) {
decodedBytes[byteIndex] |=
(uint8_t)(1U << bitPosition);
}
// ====================================
// 送信順にビットを表示
// ====================================
if (SHOW_BINARY) {
Serial.print(bitValue);
if ((i + 1) % 8 == 0) {
Serial.print(' ');
}
if ((i + 1) % 64 == 0) {
Serial.println();
}
}
}
if (SHOW_BINARY) {
Serial.println();
}
// ======================================
// HEX DATA
// ======================================
Serial.println();
Serial.println("--- HEX DATA ---");
for (uint16_t i = 0; i < byteCount; i++) {
printHexByte(decodedBytes[i]);
Serial.print(' ');
if ((i + 1) % 10 == 0) {
Serial.println();
}
}
Serial.println();
// ======================================
// BYTE TABLE
// ======================================
Serial.println();
Serial.println("--- BYTE TABLE ---");
for (uint16_t i = 0; i < byteCount; i++) {
Serial.print("Byte ");
if (i + 1 < 10) {
Serial.print('0');
}
Serial.print(i + 1);
Serial.print(": 0x");
printHexByte(decodedBytes[i]);
Serial.print(" BIN: ");
printBinaryByte(decodedBytes[i]);
Serial.println();
}
// 8ビット未満の端数
uint8_t remainingBits = bitCount % 8;
if (remainingBits != 0) {
Serial.println();
Serial.print("Partial last byte: ");
Serial.print(remainingBits);
Serial.println(" valid bits");
Serial.println(
"Unused positions are padded with 0."
);
}
// ======================================
// 解析結果
// ======================================
Serial.println();
Serial.println("--- DECODE STATUS ---");
Serial.println("Method: PULSE DISTANCE");
Serial.print("Bit order: ");
if (LSB_FIRST) {
Serial.println("LSB FIRST");
} else {
Serial.println("MSB FIRST");
}
Serial.print("Decoded bits: ");
Serial.println(bitCount);
Serial.print("Decoded bytes: ");
Serial.println(byteCount);
Serial.print("Ambiguous bits: ");
Serial.println(ambiguousCount);
Serial.println(
"Protocol/checksum not identified."
);
}
// ========================================
// 1フレームの表示
// ========================================
void printFrame(uint16_t startIndex,
uint16_t endIndex,
unsigned int frameNo,
uint32_t gapBefore) {
Serial.println();
Serial.println("============================");
Serial.print("Frame No: ");
Serial.println(frameNo);
Serial.print("Gap before (us): ");
Serial.println(gapBefore);
Serial.print("Raw Count: ");
Serial.println(endIndex - startIndex);
// フレーム合計時間
uint32_t total = 0;
for (uint16_t i = startIndex;
i < endIndex; i++) {
total += timings[i];
}
Serial.print("Sum (us): ");
Serial.println(total);
// 生波形
if (SHOW_RAW) {
printRaw(startIndex, endIndex);
}
// 復号
decodeFrame(startIndex, endIndex);
}
// ========================================
// 全フレームを表示
// ========================================
void printAllFrames(bool overflow,
bool abnormalMark) {
Serial.println();
Serial.println("############################");
Serial.print("Operation No: ");
Serial.println(operationCount);
Serial.print("Total timings: ");
Serial.println(timingCount);
if (overflow) {
Serial.println(
"WARNING: BUFFER OVERFLOW!"
);
}
if (abnormalMark) {
Serial.println(
"WARNING: Abnormally long MARK!"
);
}
uint16_t frameStart = 0;
uint32_t gapBefore = 0;
unsigned int frameNo = 1;
for (uint16_t i = 0; i < timingCount; i++) {
// 偶数がMARK、奇数がSPACE
// 15ms以上のSPACEで分割
if ((i & 1) == 1 &&
timings[i] >= FRAME_GAP_US) {
if (i > frameStart) {
printFrame(
frameStart,
i,
frameNo,
gapBefore
);
frameNo++;
}
gapBefore = timings[i];
frameStart = i + 1;
}
}
// 最終フレーム
if (frameStart < timingCount) {
printFrame(
frameStart,
timingCount,
frameNo,
gapBefore
);
}
Serial.println();
Serial.println(
"######## END OPERATION ########"
);
}
// ========================================
// 初期化
// ========================================
void setup() {
pinMode(IR_PIN, INPUT);
Serial.begin(115200);
Serial.println(
"Universal IR Multi-Frame Analyzer"
);
Serial.println(
"Generic RAW + HEX Decoder"
);
Serial.println("Arduino Mega2560");
Serial.println("Ready");
}
// ========================================
// メインループ
// ========================================
void loop() {
// 赤外線信号が来るまで待機
if (digitalRead(IR_PIN) != LOW) {
return;
}
operationCount++;
timingCount = 0;
bool overflow = false;
bool abnormalMark = false;
byte lastLevel = LOW;
uint32_t lastChange = micros();
// 受信中はSerial出力しない
while (true) {
byte level = digitalRead(IR_PIN);
uint32_t now = micros();
uint32_t duration = now - lastChange;
// 信号レベルが変化した
if (level != lastLevel) {
if (timingCount < MAX_TIMINGS) {
timings[timingCount++] = duration;
} else {
overflow = true;
break;
}
lastLevel = level;
lastChange = now;
}
// 150ms以上HIGHなら受信終了
if (lastLevel == HIGH &&
(uint32_t)(now - lastChange) >= END_GAP_US) {
break;
}
// LOWが異常に長い場合
if (lastLevel == LOW &&
(uint32_t)(now - lastChange) >= MAX_MARK_US) {
abnormalMark = true;
break;
}
}
// 全データの受信が完了してから出力
printAllFrames(overflow, abnormalMark);
}