2022-11-13

Raspberry Pi Pico W 控制接收及發射紅外線訊號

之前在下曾經維修電視機遙控器,認為現代電視機非常不便
當遙控器損壞或遺失,只使用電視機控制板的按鈕,會失去大部分功能
因此在下希望可以先將電視機遙控器的訊號逆向工程
便可以避免原來的電視機遙控制出現事故,失去大部分電視機功能

紅外線接收器

外觀
見下文
見下文
KY-022 紅外線接收器 正面
KY-022 上的紅外線接收器,實際上是 TSOP1838 紅外線接收器

引腳
見下文
KY-022 紅外線接收器 共有3支引腳
位置
(引腳向自己)
引腳 方向 功能
GND 接地
VCC 電源,接受 2.7V 至 5.5V
SIG 輸出 傳回紅外線訊號

見下文
KY-022 紅外線接收器具有 電路板LED
當接收到 紅外線訊號 時,電路板LED 會閃動

紅外線訊號逆向工程

要了解電視機遙控器的紅外線訊號,就是要將 紅外線訊號逆向工程
將遙控器上每個按鈕的訊號紀錄
在下使用 Raspberry Pi Pico W 及 KY-022紅外線接收器 將 紅外線訊號逆向工程

線路原型
見下文
將 KY-022 紅外線接收器 連接 電源 及 接地
將 訊號線 連接到 Raspberry Pi Pico W 其中 1支 GPIO 引腳

實際線路
見下文
實際線路 與 線路原型 相同

程式碼
雖然在下使用 Raspberry Pi Pico W ,但由於 Arduino 有較成熟的 紅外線函式庫
因此在下選擇使用 Arduino IDE 的 AVR-C 編寫程式

見下文
在 Arduino IDE 的 Manage Libraries... 搜尋 IRremote.hpp
便可以安裝支援紅外線接收器及紅外線發射器的函式庫

#include <IRremote.hpp>

void setup() {
	Serial.begin(115200);
	IrReceiver.begin(0);
}

void loop() {
	if (IrReceiver.decode()) {
//		IrReceiver.printIRResultShort(&Serial);
		IrReceiver.printIRSendUsage(&Serial);
//		IrReceiver.printIRResultRawFormatted(&Serial, true);
//		IrReceiver.printIRResultRawFormatted(&Serial, false);
/*
		Serial.print("protocol = ");
		Serial.println(IrReceiver.decodedIRData.protocol);
		Serial.print("address = 0x");
		Serial.println(IrReceiver.decodedIRData.address, HEX);
		Serial.print("command = 0x");
		Serial.println(IrReceiver.decodedIRData.command, HEX);
		Serial.print("extra = ");
		Serial.println(IrReceiver.decodedIRData.extra);
		Serial.print("numberOfBits = ");
		Serial.println(IrReceiver.decodedIRData.numberOfBits);
		Serial.print("flags = 0x");
		Serial.println(IrReceiver.decodedIRData.flags, HEX);
		Serial.print("decodedRawData = 0x");
		Serial.println(IrReceiver.decodedIRData.decodedRawData, HEX);
*/
		IrReceiver.resume();
	}
	delay(1);
}
在下使用由 IRremote.hpp 提供的範例再簡化

測試
見下文
在下使用一些電視機遙控器測試
上載 Sketch 到 Raspberry Pi Pico W 後,按下遙控器上的按鈕
當紅外線接收器接收到紅外線訊號,電路板LED 便會閃動
在 Arduino IDE 的 序列監視器 會顯示類似的資料
Send with: IrSender.sendSamsung(0x707, 0x2, <numberOfRepeats>);
Send with: IrSender.sendSony(0x1, 0x15, <numberOfRepeats>);
這些資料,可以直接利用 IRremote.hpp 中的
sendSonysendSamsung 等功能控制 紅外線LED 發射器紅外線訊號予目標裝置

但有些非主流裝置會使用生產商私有的紅外線訊號,無法識別紅外線訊號所使用的協定
因此不能使用 sendSony 、 sendSamsung 等需要得知 協定地址指令 便可以發射紅外線訊號的功能

當無法識別紅外線訊號時
便需要使用 IrReceiver.printIRResultRawFormatted(&Serial, false); 來偵測原始的紅外線訊號
當偵測到紅外線訊號時,會顯示類似的資料
rawData[68]: 
 -65535
 +91,-90
 +12,-33 +11,-34 +11,-34 +11,-12
 +11,-11 +11,-12 +11,-11 +11,-12
 +11,-34 +11,-34 +11,-34 +11,-11
 +11,-12 +11,-11 +11,-12 +11,-11
 +11,-12 +12,-33 +11,-11 +12,-11
 +11,-11 +12,-11 +11,-11 +12,-11
 +11,-34 +11,-11 +12,-33 +12,-33
 +12,-33 +12,-33 +12,-34 +11,-33
 +12
Sum: 1228
由 IrReceiver 偵測到的原始紅外線訊號的資料
  • 正負:正表示 紅外線LED閃動,負表示 紅外線LED熄滅
  • 數值:紅外線LED的 狀態持續時間
  • 第1組訊號:大負數值,一般是 -65535 ,表示 等待
  • 第2及第3組訊號:開始訊號通常是一對非常接近的正負數值或數值相差2至5倍
  • 第4至尾2組訊號:元位資料,是由 一對非常接近的正負數值表示 0或數值相差2至5倍表示 1
  • 最後訊號:結束訊號

如果需要使用原始紅外線訊號,將資料改變成 byte陣列 即可,即是:
byte signals[] = {
	91, 90,
	12, 33, 11, 34, 11, 34, 11, 12,
	11, 11, 11, 12, 11, 11, 11, 12,
	11, 34, 11, 34, 11, 34, 11, 11,
	11, 12, 11, 11, 11, 12, 11, 11,
	11, 12, 12, 33, 11, 11, 12, 11,
	11, 11, 12, 11, 11, 11, 12, 11,
	11, 34, 11, 11, 12, 33, 12, 33,
	12, 33, 12, 33, 12, 34, 11, 33,
	12
};

格式化與否並不重要

紅外線發射器

外觀
見下文
紅外線發射器 是一粒 能夠發射紅外線的LED

見下文
在下從一個損壞的電視機遙控器拆解的 紅外線LED
由於引腳太短,焊接銅線延長
既然需要加工,在下還將 100Ω的電阻器 焊接,方便使用
再使用 熱縮管 保護焊接的 銅線 及 電阻器
(在下將 電阻器 焊接到 紅外線LED 的 正極引腳)

見下文
由於 紅外線不是可見光,無法直接靠人類的眼睛看到
需要借助拍攝裝置才能夠觀察 紅外線LED 是否能夠運作

發射紅外線訊號

獲取按鈕的 紅外線訊號 後,便可以使用 紅外線LED 模擬對應按鈕的 紅外線訊號

線路原型
見下文
在下附加按壓按鈕模仿遙控器按下按鈕時的效果

實際線路
見下文
由於在下的 紅外線LED 已經焊接 電阻器,因此可以簡化部分線路

程式碼
#include "PinDefinitionsAndMore.h"
#include <IRremote.hpp>

const byte BUTTON_PIN = 2;

void setup() {
	Serial.begin(115200);
	pinMode(BUTTON_PIN, INPUT_PULLUP);
	IrReceiver.begin(0);
	IrSender.begin();
}

void loop() {
	if (digitalRead(BUTTON_PIN) == LOW) {
		IrSender.sendSony(0x1, 0x15, 2);
	}
	if (IrReceiver.decode()) {
		IrReceiver.printIRSendUsage(&Serial);
		IrReceiver.resume();
	}
	delay(1);
}
在下同樣使用由 IRremote.hpp 提供的範例再簡化
除了修改 Sketch 的 ino檔案,還要修改 PinDefinitionsAndMore.h
到 PinDefinitionsAndMore.h 最後的位置加入
#undef IR_SEND_PIN
#define IR_SEND_PIN 1
IR_SEND_PIN 是 IRremote.hpp 預設的紅外線發射器引腳
在下修改成使用 GP1 引腳,閣下的可因應需要修改成其他引腳

Send with: IrSender.sendSony(0x1, 0x15, <numberOfRepeats>);

測試
見下文
按下按壓按鈕後,紅外線發射器會將紅外線訊號發射器,紅外線接收器會接收到紅外線訊號
由於在下指定發射的紅外線訊號,因此在 Arduino IDE 的 序列監視器會顯示與程式碼相同的資料

先前提及有些電子產品未必會使用主流的紅外線協定,因此需要偵測原始紅外線訊號
以之前的示範發射紅外線訊號為例子,將 訊號轉成 AVR-C 的位元組陣列資料 (不包含第1組大負數值)
PROGMEM const byte CARRIER_FREQUENCY = 38;
PROGMEM const byte PULSES[] = {
	91 ,90
	,12 ,33 ,11 ,34 ,11 ,34 ,11 ,12
	,11 ,11 ,11 ,12 ,11 ,11 ,11 ,12
	,11 ,34 ,11 ,34 ,11 ,34 ,11 ,11
	,11 ,12 ,11 ,11 ,11 ,12 ,11 ,11
	,11 ,12 ,12 ,33 ,11 ,11 ,12 ,11
	,11 ,11 ,12 ,11 ,11 ,11 ,12 ,11
	,11 ,34 ,11 ,11 ,12 ,33 ,12 ,33
	,12 ,33 ,12 ,33 ,12 ,34 ,11 ,33
	,12
};
delay(10);
IrSender.sendRaw_P(PULSES, sizeof(PULSES) / sizeof(byte), CARRIER_FREQUENCY);
由於 sendRaw_P 需要設定 紅外線載波 (Carrier Frequency) ,如果不清楚電子裝置接收紅外線載波,便需要測試
常見的 紅外線載波 使用 38KHz ,但 有效的 紅外線載波 是 20KHz 至 60KHz
另外在下發現使用 sendRaw_P 發射紅外線訊號前需要 延遲大約10毫秒 才能將正確地將外線訊訊號發射

實際效果
見下文
將紅外線訊號修改成對應紅外線裝置的實際操作功能,按下測試按鈕後可以控制到紅外線裝置

訊號轉換

由於在下不希望因為電視機遙控器損壞或遺失,導致電視機失去大部分功能
因此在下將遙控器所有按鈕的紅外線訊號都逆向工程,可以借用其他遙控器,透過紅外線接收器
經由微控制器分辨訊號後,控制紅外線發射器向電視機傳送對應訊號
便可以不限遙控器類型都能夠發射紅外線訊號

線路原型
見下文
基本上與測試紅外線發射器的線路相似,只是由以按壓按鈕觸發發射紅外線訊號,改為以紅外線接收器觸發

實際線路
見下文
見下文
見下文
卸取按壓按鈕及線路即可,可以正式實際使用

程式碼
#include "PinDefinitionsAndMore.h"
#include <IRremote.hpp>

void setup() {
	IrReceiver.begin(0);
	IrSender.begin();
}

void loop() {
	if (IrReceiver.decode()) {
		decode_type_t protocol = IrReceiver.decodedIRData.protocol;
		unsigned int address = IrReceiver.decodedIRData.address;
		byte command = IrReceiver.decodedIRData.command;
		if (protocol == SAMSUNG && address == 0x707 && command == 0x2) {
			IrReceiver.resume();
			delay(1000);
			IrSender.sendSony(0x1, 0x15, 2);
		} else if (protocol == SONY && address == 0x1 && command == 0x15) {
			IrReceiver.resume();
			delay(1000);
			IrSender.sendSamsung(0x707, 0x2, 2);
		} else {
			IrReceiver.resume();
		}
	}
	delay(1);
}
在下使用 Samsung 遙控器的開關按鈕訊號,即是 address == 0x707 及 command == 0x2
轉換成 Sony 遙控器的開關按鈕訊號,即是 address == 0x1 及 command 0x15
為了確保訊號能夠發射,又不會過度重覆訊號,在下讓紅外線發射器送出2次訊號

測試
見下文
紅外線接收器 及 紅外線發射器 不斷來回重覆 接收及發射紅外線訊號

如果閣下要測試轉換的訊號不是 Samsung 或 Sony 的協定
請查看 IRremote.hpp 能夠支援哪種 紅外線訊號的協定

實際效果
見下文
實際測試當然不需要重覆交替紅外線訊號,只需要將某一個訊號轉換成另一個訊號即可

網絡控制

網絡控制線路原型
見下文
由於 Raspberry Pi Pico W 內置 Wi-Fi 模組 ,簡化到只需要一粒 紅外線LED 即可

網絡控制實際線路
見下文
由於在下的 紅外線LED 已經焊接 電阻器,基本上完全不需要接駁線路
直接將引腳連接到 Raspberry Pi Pico W 的 GPIO 及 接地 即可

網絡控制程式碼
////////// .ino file //////////
#include <WebServer.h>
#include <WiFi.h>
#include "PinDefinitionsAndMore.h"
#include <IRremote.hpp>
#include "wificonfig.h"
#include "html.h"

WebServer server = WebServer(80);

void setup(void) {
	Serial.begin(115200);
	WiFi.begin(MY_SSID, MY_PASSWORD);
	while (WiFi.status() != WL_CONNECTED) {
		delay(1000);
		Serial.print(".");
	}
	Serial.print("Visit: http://");
	Serial.println(WiFi.localIP());
	server.on("/", [] {
		server.send(200, "text/html", HTML);
	});
	server.on("/power", [] {
		IrSender.sendNEC(0x20, 0x52, 2);
	});
	server.begin();
	IrSender.begin();
}

void loop() {
	server.handleClient();
	delay(1);
}

////////// wificonfig.h ///////////
PROGMEM const char* MY_SSID = "";
PROGMEM const char* MY_PASSWORD = "";

////////// html.h //////////
PROGMEM const String HTML = "<!DOCTYPE html>\
<html lang=\"zh-yue-HK\">\
	<head>\
		<meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\"/>\
		<title>WiFi to IRremote</title>\
		<script>\
function power() {\
	var xhr = new XMLHttpRequest();\
	xhr.open(\"GET\", \"/power\", true);\
	xhr.send();\
}\
		</script>\
	</head>\
	<body>\
		<button onclick=\"power();\">Power</button>\
	</body>\
</html>";

使用 Raspberry Pi Pico W 內置的 WiFi功能,建立網頁伺服器

實際效果
見下文
經網絡控制 Raspberry Pi Pico W 再控制 紅外線LED 發射紅外線訊號操作紅外線裝置

在下仍然將紅外線接收器保留,但不分析訊器,只作為偵測有否紅外線訊號用途

總結

再下不太喜歡一些函式庫的設定,經常使用 標頭檔案 (header file) 及 #define 來設定資料
甚至限制設定值,需要花時間查看原始檔才能修改設定
PinDefinitionsAndMore.h 的設定值雖然覆蓋大量 微控制器,但要指定設定變得非常麻煩
例如在下使用 Raspberry Pi Pico W 時,不知為何無法使用 LED_BUILTIN 作為測試提示
最後在下使用 Serial.print 查找 LED_BUILTIN 在 Raspberry Pi Pico W 的數值為 32
在下在 PinDefinitionsAndMore.h 的最後位置加上

#undef LED_BUILTIN
#define LED_BUILTIN 32

與 IR_SEND_PIN 相同,強制修改設定值,最後在 .ino檔案 加上
#include <WebServer.h>
#include <WiFi.h>
#include "PinDefinitionsAndMore.h"
#include <IRremote.hpp>
#include "wificonfig.h"
#include "html.h"

WebServer server = WebServer(80);

void setup(void) {
	Serial.begin(115200);
	// use built-in LED
	pinMode(LED_BUILTIN, OUTPUT);
	// light on the build-in LED when Raspberry Pi Pico W is power on
	digitalWrite(LED_BUILTIN, HIGH);
	WiFi.begin(MY_SSID, MY_PASSWORD);
	while (WiFi.status() != WL_CONNECTED) {
		delay(1000);
		Serial.print(".");
		// flash the build-in LED when connecting to the WiFi
		digitalWrite(LED_BUILTIN, !digitalRead(LED_BUILTIN));
	}
	// light off the build-in LED when WiFi is connected
	digitalWrite(LED_BUILTIN, LOW);
	Serial.print("Visit: http://");
	Serial.println(WiFi.localIP());
	server.on("/", [] {
		server.send(200, "text/html", HTML);
	});
	server.on("/power", [] {
		IrSender.sendNEC(0x20, 0x52, 2);
	});
	server.begin();
	IrSender.begin();
}

void loop() {
	server.handleClient();
	delay(1);
}

可以讓 LED_BUILTIN 顯示 WiFi的連接狀態

另外,將 PinDefinitionsAndMore.h 保留需要的設定值,刪除其他設定值,會無法執行需要的效果
因此唯有重置設定值,再重新定義設定值

這個紅外線測試,其實早在在下編寫維修電視機遙控器時已經構思
但當時即使用範例 Sketch 使用預設引腳,仍然無法完成測試,因此暫停
事隔一段時間,IRremote 函式庫 由 2.6.x 更新至 3.9.x ,在下終於完成測試

這個專案最大用途就是可以將所有能夠使用紅外線遙控器控制的電子裝置
在不需要解體的情況下,只需要事前將紅外線遙控器的訊號逆向工程
再使用 微控制器 配合 WiFi模組 ,便可以將電子裝置自行改裝成智能家居>
而且製作價錢非常便宜,只需要使用 6美元 的 Raspberry Pi Pico W 、 一粒紅外線LED 及 一粒100Ω電阻 即可
如果要將價錢再壓縮,可以使用 ESP8266 NodeMCU 、甚至 ESP-01 更便宜又具備內置 WiFi模組 的微控制器

自學了差不多4年微控制器程式,今日才知道 INPUT_PULLUP 的用途,真是慚愧
引腳使用 INPUT 時,當引腳沒有接駁到電源或接地,訊號會受到干擾,訊號會在高電壓及低電壓間不斷跳動

當引腳使用 INPUT_PULLUP 時,引腳會使用大約 20KΩ 的內置電阻器接駁到電源,令引腳訊號保持高電壓

當按下按壓按鈕時,令引腳接駁到接地,令訊號改變成低電壓

某些 微控制器還有 INPUT_PULLDOWN 會令訊號保持低電壓,接通效果時令訊號改變成高電壓
INPUT_PULLUP 及 INPUT_PULLDOWN 執行效果其實是相同,只是檢查訊號時是 高電壓 還是 低電壓


參考資料

沒有留言 :

張貼留言