軟硬件開(kāi)源項(xiàng)目-紅外遙控網(wǎng)關(guān):IRext 紅外碼庫(kù)+遠(yuǎn)程實(shí)現(xiàn)紅外開(kāi)關(guān)空調(diào)+紅外學(xué)習(xí)
【下載地址】:紅外遙控網(wǎng)關(guān)源碼
本倉(cāng)庫(kù)提供了一個(gè)完整的紅外遙控網(wǎng)關(guān)源碼,旨在幫助開(kāi)發(fā)者了解紅外控制和紅外學(xué)習(xí)的實(shí)現(xiàn)過(guò)程。項(xiàng)目包含了紅外控制和紅外學(xué)習(xí)的完整代碼。
項(xiàng)目地址:https://gitee.com/daadadada/infrared 小程序源碼地址:https://pan.baidu.com/s/17PZ9PxKzOpBtRuvA9ePOTw?pwd=4hgn
1.前言
當(dāng)智能家居從概念走向現(xiàn)實(shí),傳統(tǒng)紅外設(shè)備的“智能改造”卻成了多數(shù)家庭的痛點(diǎn):不同品牌空調(diào)的紅外協(xié)議千差萬(wàn)別,遙控器堆成小山仍難實(shí)現(xiàn)統(tǒng)一控制。盡管市面上有很多智能控制 APP,但是這需要設(shè)備端支持網(wǎng)絡(luò),一些生產(chǎn)較早的空調(diào)等設(shè)備大多都不支持網(wǎng)絡(luò)這一功能。如果有一個(gè)類似遙控器的產(chǎn)品能遠(yuǎn)程控制市面上大部分的空調(diào)品牌,并且支持紅外學(xué)習(xí)實(shí)現(xiàn)控制,那么問(wèn)題就迎刃而解了。由于 WiFi 信號(hào)容易受影響并且做通信功耗相對(duì)較高,因此我使用以太網(wǎng)做通訊。之前做項(xiàng)目用過(guò) WIZnet 的芯片實(shí)現(xiàn)以太網(wǎng)功能,近期他們出了個(gè)帶 MCU 的以太網(wǎng)芯片 W55MH32,找他們銷售申請(qǐng)了一套開(kāi)發(fā)板。計(jì)劃是小程序做控制端,開(kāi)發(fā)板做設(shè)備端,通過(guò)以太網(wǎng)進(jìn)行通訊。紅外學(xué)習(xí)到的紅外碼在設(shè)備端使用 w25q64 本地存儲(chǔ)。
目前開(kāi)發(fā)板用著可以,這款芯片集成了全硬件 TCP/IP 協(xié)議棧、MAC 及 PHY,開(kāi)發(fā)板也已經(jīng)集成了網(wǎng)絡(luò)變壓器和網(wǎng)口。Flash 和 SRAM 也足夠大。一些不是很懂的網(wǎng)絡(luò)協(xié)議也可以去他們[官網(wǎng)例程]去了解,里面也有一些講如何配置 Keil 和燒錄,資料很充足。
目前的功能已經(jīng)實(shí)現(xiàn)了遠(yuǎn)程控制空調(diào),但是紅外學(xué)習(xí)機(jī)制是通過(guò)外界發(fā)射紅外信號(hào)來(lái)觸發(fā),并在串口打印,紅外學(xué)習(xí)的功能并沒(méi)有完全實(shí)現(xiàn)。后續(xù)會(huì)通過(guò)小程序按鍵來(lái)觸發(fā)紅外學(xué)習(xí),并且能下發(fā)命令實(shí)現(xiàn)控制。未被收錄的紅外碼通過(guò) w25q64 和 FatFs 文件系統(tǒng),實(shí)現(xiàn)本地存儲(chǔ)!另外,后面也會(huì)自己畫原理圖和 pcb 板,將所有的硬件集成到板子上,也會(huì)把代碼、原理圖以及 pcb 都開(kāi)源出來(lái)。
2.紅外
紅外遙控碼是一種基于 38000Hz 或者 56000Hz 載波頻率的控制編碼,接收方通過(guò)識(shí)別帶有或不帶有載波的時(shí)間間隔片進(jìn)行編碼識(shí)別。
在控制層通常使用高低物理電平對(duì)的序列來(lái)表示一個(gè)獨(dú)立的控制信號(hào),例如以下的 NEC 碼:
它使用 9000us 載波+4500us 無(wú)載波時(shí)間序列表示引導(dǎo)碼,并且使用 500us 載波+500us 無(wú)載波時(shí)間序列表示邏輯 0,使用 500us 載波+1500us 無(wú)載波時(shí)間序列表示邏輯 1,采用 2 字節(jié)地址碼和 2 字節(jié)命令碼構(gòu)成,全碼時(shí)間序列長(zhǎng)度為 67.5ms。
IRext 紅外庫(kù)移植
IRext 是一個(gè)開(kāi)源萬(wàn)能紅外遙控碼庫(kù)、編解碼壓縮算法以及免費(fèi)周邊服務(wù)。它向智能家居開(kāi)發(fā)者提供:
- 支持 16 類 1000 多品牌,上萬(wàn)個(gè)型號(hào)的家用電器遙控。
- 在線以及離線的萬(wàn)能紅外碼庫(kù),包括按照品牌分類的索引以及遙控編碼。
- 靈活的服務(wù)部署方式,利用開(kāi)源的服務(wù)端以及控制臺(tái)代碼在容器環(huán)境內(nèi) 5 分鐘快速搭建自己的碼庫(kù)服務(wù)。
- 碼庫(kù)和解碼算法經(jīng)過(guò)了極限壓縮,能存儲(chǔ)和運(yùn)行在配置低至 51 MCU 的嚴(yán)苛硬件環(huán)境中。
- 豐富的平臺(tái)適配支持和示例代碼。
- 詳盡的文檔資源,覆蓋萬(wàn)能遙控開(kāi)發(fā)的每一個(gè)環(huán)節(jié)。
- 開(kāi)發(fā)者可使用碼庫(kù)擴(kuò)展工具自行擴(kuò)展碼庫(kù),可基于開(kāi)源代碼自由修改方案功能細(xì)節(jié)。
前往[IRext 官網(wǎng)]下載離線碼庫(kù)和 IRext 解碼庫(kù),將 IRext 解碼庫(kù)放進(jìn)單片機(jī)工程目錄里并修改頭文件目錄,讓其包含解碼庫(kù)中的頭文件。根據(jù) IRext 提供的品牌索引庫(kù)從離線碼庫(kù)下載所需要的品牌紅外碼庫(kù)??纱娣诺絾纹瑱C(jī) FLASH 或放到服務(wù)器上。IRext 解碼庫(kù)支持從文件系統(tǒng)解碼或從內(nèi)存解碼。
從文件系統(tǒng)解碼
ir_file_open(category, sub_category, "my_ir_code_file.bin");
ir_decode(key_code, user_data, ac_status, change_wind_dir);
ir_close();
加載到內(nèi)存解碼
ir_binary_open(category, sub_category, buffer, buffer_length);
ir_decode(key_code, user_data, &ac_status, change_wind_dir);
ir_close();
解碼之后在 decoded_data 中獲得可供輸出的 IR 時(shí)間序列,將此序列遞交給 IR 設(shè)備驅(qū)動(dòng)即可實(shí)現(xiàn)紅外發(fā)送。不過(guò),目前 IRext 庫(kù)只支持開(kāi)關(guān),風(fēng)速,溫度控制和掃風(fēng)這些功能。
3.程序功能實(shí)現(xiàn)
小程序與設(shè)備通信功能
使用小程序可以做到遠(yuǎn)程開(kāi)啟空調(diào)而無(wú)需在設(shè)備旁邊,并且小程序無(wú)需下載安裝,通過(guò)微信等平臺(tái)就能直接訪問(wèn),因此我選擇微信小程序來(lái)作為控制端。如圖為小程序界面圖:
小程序與設(shè)備通信的方式為,小程序調(diào)用 OneNET 平臺(tái)提供的 HTTP 接口實(shí)現(xiàn)設(shè)備屬性下發(fā),云平臺(tái)通過(guò) mqtt 協(xié)議將數(shù)據(jù)發(fā)送給設(shè)備。小程序與云平臺(tái)以及設(shè)備交互的數(shù)據(jù)格式如下,Command 用于下發(fā)控制或紅外學(xué)習(xí)命令。
其中 AC_Status 是一個(gè)結(jié)構(gòu)體,存儲(chǔ)空調(diào)的狀態(tài),具體內(nèi)容如下,其中的結(jié)構(gòu)體成員分別是空調(diào)的品牌、型號(hào)、電源、模式、溫度、風(fēng)速和操作,其中操作是因?yàn)?IRext 庫(kù)需要在解碼的時(shí)候作為形參傳入,所以必須定義,這個(gè)操作對(duì)應(yīng)著小程序端按下的按鍵,比如,開(kāi)關(guān)機(jī),升溫等。
在設(shè)備端我用到 OneNET 平臺(tái)的通信主題有$sys/{pid}/{device-name}/thing/property/set這個(gè)用于訂閱,接收下發(fā)的控制命令;$sys/{pid}/{device-name}/thing/property/set_reply 用于應(yīng)答下發(fā)的控制命令。一些其他的通信主題沒(méi)有用到,感興趣的小伙伴可以訪問(wèn)OneNET mqtt 通信主題了解。
小程序端使用的是 OneNET 平臺(tái)提供的 http 接口,具體用到的接口為 https://iot-api.heclouds.com/thingmodel/set-device-property ,用于下發(fā)設(shè)備屬性設(shè)置命令到設(shè)備,設(shè)備會(huì)返回設(shè)置結(jié)果。其他的 http 接口可以訪問(wèn)OneNET http 接口。
另外在發(fā)起 https 請(qǐng)求前,還需要在 Headers 中攜帶統(tǒng)一的安全鑒權(quán)信息 authorization 才能成功請(qǐng)求接口。具體方法需前往安全鑒權(quán)查看詳細(xì)內(nèi)容。
我們?cè)?W55MH32 提供的開(kāi)發(fā)套件下寫代碼,一些依賴的頭文件和 pack 包都在里面。
將開(kāi)發(fā)套件下的 do_mqtt.c 和 do_mqtt.h 文件復(fù)制到項(xiàng)目里,并修改 mqtt_params 里的參數(shù)為自己要連接的 mqtt 地址等參數(shù)。
do_mqtt()函數(shù)是一個(gè)基于狀態(tài)機(jī)的 MQTT 客戶端處理函數(shù),負(fù)責(zé)管理 MQTT 連接、訂閱、?;詈拖⒔邮盏炔僮?。當(dāng)函數(shù)第一次運(yùn)行時(shí),會(huì)來(lái)到 conn,開(kāi)始和 MQTT 服務(wù)器建立連接,之后再根據(jù)狀態(tài)進(jìn)行判斷狀態(tài),進(jìn)入到 SUB 訂閱主題或者 KEEPALIVE 保活等。在狀態(tài)為 ERR 時(shí),代表著 mqtt 連接之間出現(xiàn)了錯(cuò)誤,在 ERR 狀態(tài)中,可以重新連接或者自定義一些處理。由于在 mqtt 的狀態(tài)機(jī)中我不需要發(fā)布消息,所以我把發(fā)布消息這一節(jié)點(diǎn)刪除了,代碼如下:
void do_mqtt(void)
{
uint8_t ret;
switch (run_status)
{
case CONN:
{
ret = MQTTConnect(&c, &data); /* 連接到MQTT服務(wù)器 */
printf("Connect to the MQTT server: %d.%d.%d.%d:%drn", mqtt_params.server_ip[0], mqtt_params.server_ip[1], mqtt_params.server_ip[2], mqtt_params.server_ip[3], mqtt_params.port);
printf("Connected:%srnrn", ret == SUCCESSS ? "success" : "failed");
if (ret != SUCCESSS)
{
run_status = ERR;
}
else
{
run_status = SUB;
}
break;
}
case SUB:
{
ret = MQTTSubscribe(&c, mqtt_params.subtopic, mqtt_params.subQoS, message_Arrived); /* 訂閱主題 */
printf("Subscribing to %srn", mqtt_params.subtopic);
printf("Subscribed:%srnrn", ret == SUCCESSS ? "success" : "failed");
if (ret != SUCCESSS)
{
run_status = ERR;
}
else
{
run_status = KEEPALIVE;
}
break;
}
case KEEPALIVE:
{
if (MQTTYield(&c, 30) != SUCCESSS) /* ?;?MQTT */
{
run_status = ERR;
}
delay_ms(100);
}
case RECV:
{
if (mqtt_recv_flag)
{
mqtt_recv_flag = 0;
json_decode(mqtt_recv_msg);
}
delay_ms(100);
break;
}
case ERR: /* 錯(cuò)誤處理 */
printf("system ERROR!");
delay_ms(1000);
break;
default:
break;
}
}
messageArrived 函數(shù)是當(dāng)我們訂閱的主題發(fā)來(lái)消息時(shí)的回調(diào)函數(shù),當(dāng)有消息到達(dá)時(shí),會(huì)觸發(fā)此函數(shù),并把消息內(nèi)容作為參數(shù)傳遞給此函數(shù)。這里會(huì)打印一些參數(shù),如主題、消息內(nèi)容、QOS 等級(jí)等。代碼如下:
void message_Arrived(MessageData *md)
{
char topicname[64] = {0};
char msg[512] = {0};
sprintf(topicname, "%.*s", (int)md- >topicName- >lenstring.len, md- >topicName- >lenstring.data);
sprintf(msg, "%.*s", (int)md- >message- >payloadlen, (char *)md- >message- >payload);
printf("recv data from %s", topicname);
if (strcmp(topicname, mqtt_params.subtopic) == 0)
{
mqtt_recv_flag = 1;
memset(mqtt_recv_msg, 0, sizeof(mqtt_recv_msg));
memcpy(mqtt_recv_msg, msg, strlen(msg));
}
}
json_decode 函數(shù)用于解析 JSON 數(shù)據(jù),這里可以根據(jù)我們?cè)谠破脚_(tái)定義的數(shù)據(jù)來(lái)解析數(shù)據(jù)。解析完成后,會(huì)做一個(gè)回復(fù),表示接收到數(shù)據(jù),做應(yīng)答。代碼如下:
void json_decode(char *msg)
{
int ret;
char replymsg[128] = {0};
cJSON *jsondata = NULL;
cJSON *id = NULL;
cJSON *params = NULL;
cJSON *AC = NULL;
cJSON *data = NULL;
jsondata = cJSON_Parse(msg);
if (jsondata == NULL)
{
printf("json parse fail.rn");
return;
}
id = cJSON_GetObjectItem(jsondata, "id");
params = cJSON_GetObjectItem(jsondata, "params");
data = cJSON_GetObjectItem(params, "Command");
// 如果不是控制命令則返回
if (strcmp(data- >valuestring, "Control"))
{
return;
}
// 解析json并獲取空調(diào)狀態(tài)
AC = cJSON_GetObjectItem(params, "AC");
data = cJSON_GetObjectItem(AC, "ACBrand");
brand = (unsigned char)data- >valueint;
data = cJSON_GetObjectItem(AC, "ACType");
type = (unsigned char)data- >valueint;
data = cJSON_GetObjectItem(AC, "openFlag");
ac_status.ac_power = (t_ac_power)data- >valueint;
data = cJSON_GetObjectItem(AC, "modeGear");
ac_status.ac_mode = (t_ac_mode)data- >valueint;
data = cJSON_GetObjectItem(AC, "tempature");
ac_status.ac_temp = (t_ac_temperature)(data- >valueint - 16);
data = cJSON_GetObjectItem(AC, "fengsuGear");
ac_status.ac_wind_speed = (t_ac_wind_speed)data- >valueint;
data = cJSON_GetObjectItem(AC, "opertion");
operation = (unsigned char)data- >valueint;
// 回復(fù)
pubmessage.qos = QOS0;
sprintf(replymsg, "{"id":"%s","code":200,"msg":"success"}", id- >valuestring);
printf("reply:%srn", replymsg);
pubmessage.payload = replymsg;
pubmessage.payloadlen = strlen(replymsg);
ret = MQTTPublish(&c, mqtt_params.subtopic_reply, &pubmessage);
if (ret != SUCCESSS)
{
run_status = ERR;
}
else
{
printf("publish:%s,%srnrn", mqtt_params.subtopic_reply, (char *)pubmessage.payload);
}
cJSON_Delete(jsondata);
irSendFlag = 1;
}
紅外發(fā)射功能
由于紅外發(fā)射是一種基于 38000Hz 載波頻率的控制編碼,因此我們需要使用到 PWM 功能和 GPIO 口,創(chuàng)建 GPIOConfiguration 和 TIMConfiguration 函數(shù),分別初始化 GPIO 口和 PWM。GPIO 口,即 PWM 輸出口設(shè)為 PA3。定時(shí)器 2 采用的是 PCLK1 時(shí)鐘,頻率盡量最大,這樣輸出的紅外誤碼率低。我選擇頻率為 216MHz,因此不需要分頻。計(jì)數(shù)值設(shè)置為 5736,216MHz/5736≈38KHz。紅外發(fā)射的占空比一般設(shè)為 1/3,因此將占空比,即 TIM_Pulse 設(shè)為 1912。PWM 代碼如下:
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
TIM_OCInitTypeDef TIM_OCInitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
TIM_TimeBaseStructure.TIM_Period = 5736 - 1; // 216Mhz/5736≈38Khz
TIM_TimeBaseStructure.TIM_Prescaler = 1 - 1; // 216Mhz/3=216Mhz
TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure);
// 配置PWM模式
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
TIM_OCInitStructure.TIM_Pulse = 1912; // 占空比計(jì)算
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;
TIM_OC4Init(TIM2, &TIM_OCInitStructure);
TIM_Cmd(TIM2, ENABLE);
TIM_SetCompare4(TIM2, 0);
在 IRControl.c 文件中創(chuàng)建 getIrCode 函數(shù),通過(guò) mqtt 解析的 json 數(shù)據(jù),即可知道要控制哪個(gè)品牌以及類型的空調(diào),目前僅支持三個(gè)品牌。接著調(diào)用 IRext 提供的 API 解碼,并將解碼后的數(shù)據(jù)存放到先前定義的 user_data 變量里,代碼如下:
unsigned char *number_point = NULL;
unsigned short *length_point = NULL;
switch (brand)
{
case 0:
{
number_point = Gree[type];
length_point = Gree_Length;
break;
}
case 1:
{
number_point = Midea[type];
length_point = Midea_Length;
break;
}
case 2:
{
number_point = Haier[type];
length_point = Haier_Length;
break;
}
default:
break;
}
unsigned char ret = ir_binary_open(1, 1, number_point, length_point[type]);
length = ir_decode(operation_list[operation], user_data, &ac_status, 1);
if (length == 0)
printf("Decode error");
ir_close();
在 IRControl.c 中創(chuàng)建 runIr 函數(shù),用于驅(qū)動(dòng)紅外發(fā)射模塊,根據(jù)解碼后的時(shí)間序列,驅(qū)動(dòng) PWM 輸出占空比為 33%或 0%的 PWM 波即可控制空調(diào),代碼如下。在最后輸出一個(gè) 40ms 的占空比為 0%的無(wú)載波時(shí)間片,來(lái)終止紅外。
for (unsigned short i = 0; i < length; i++)
{
if (!(i % 2))
TIM_SetCompare4(TIM2, 1912);
else
TIM_SetCompare4(TIM2, 0);
delay_us(user_data[i]);
}
TIM_SetCompare4(TIM2, 0);
delay_us(40000);
最后創(chuàng)建 ir_control 函數(shù)并調(diào)用 getIrCode 和 runIr 函數(shù)即可。
紅外學(xué)習(xí)功能
在初始化階段會(huì)配置 GPIO 口中斷用于監(jiān)聽(tīng)紅外信號(hào),定時(shí)器中斷用于計(jì)時(shí)。由于定時(shí)器的重裝值為 16 位,最大只能到 65535us,而紅外信號(hào)會(huì)有重復(fù)碼,很容易就超過(guò)了定時(shí)器的最大值,因此需要使用定時(shí)器溢出中斷來(lái)轉(zhuǎn)換成 32 位計(jì)數(shù)值。在每次觸發(fā) TIM4 更新中斷時(shí),就將溢出計(jì)數(shù)加 1,在需要獲取時(shí)間時(shí)就可以將溢出計(jì)數(shù)左移 16 位并與上當(dāng)前定時(shí)器 的計(jì)數(shù)值就是 32 位計(jì)數(shù)值。例如:
return (timer_overflow_count << 16) | TIM_GetCounter(TIM4);
紅外接收有 2 個(gè)狀態(tài)分別是 IR_IDLE(空閑)、IR_RECEIVING(接收完成)。當(dāng)紅外信號(hào)到來(lái)時(shí),會(huì)進(jìn)入到 EXTI0_IRQHandler 中斷處理函數(shù)中,并將空閑狀態(tài)改變?yōu)榻邮諣顟B(tài),之后開(kāi)始接收數(shù)據(jù),完整代碼如下:
void EXTI0_IRQHandler(void)
{
uint32_t current_time = 0;
uint32_t time_interval = 0;
// 檢查是否是該EXTI線產(chǎn)生的中斷
if (EXTI_GetITStatus(IR_EXTI_LINE) != RESET)
{
// 獲取當(dāng)前時(shí)間
current_time = Get_32bit_Timer_Value();
// 計(jì)算時(shí)間間隔
time_interval = current_time - ir_last_time;
// 如果是第一個(gè)數(shù)據(jù)點(diǎn)或者是接收狀態(tài),則記錄時(shí)間
if (ir_state == IR_IDLE)
{
// 第一個(gè)邊沿,開(kāi)始接收數(shù)據(jù)
ir_state = IR_RECEIVING;
ir_data_count = 0;
ir_data_ready = 0;
}
// 存儲(chǔ)時(shí)間數(shù)據(jù)(如果緩沖區(qū)未滿)
if (ir_data_count < IR_DATA_BUFFER_SIZE)
{
ir_time_data[ir_data_count] = time_interval;
ir_data_count++;
}
// 更新時(shí)間
ir_last_time = current_time;
// 清除中斷標(biāo)志位
EXTI_ClearITPendingBit(IR_EXTI_LINE);
}
}
我們還需要一個(gè)判斷接收完畢的函數(shù),函數(shù)內(nèi)會(huì)聲明兩個(gè)全局靜態(tài)變量,分別是用于記錄上次進(jìn)入函數(shù)的時(shí)間 last_activity_time 和上次記錄的紅外數(shù)據(jù)計(jì)數(shù)值 previous_count。首先,我們需要判斷當(dāng)前紅外狀態(tài)是否是接收狀態(tài),其次,我們需要判斷紅外數(shù)據(jù)計(jì)數(shù)值不為 0。當(dāng)都滿足時(shí),開(kāi)始計(jì)算時(shí)間間隔,如果當(dāng)前的紅外數(shù)據(jù)計(jì)數(shù)值和上次的不一樣就重置 last_activity_time 并更新 previous_count,返回 0,否則判斷當(dāng)時(shí)間間隔超過(guò) 10ms 時(shí)就認(rèn)為紅外接收完成并返回 1,并重置一些參數(shù)。完整代碼如下:
uint8_t IR_Is_Ready(void)
{
// 如果正在接收數(shù)據(jù)且有數(shù)據(jù)
if (ir_state == IR_RECEIVING && ir_data_count > 0)
{
// 使用全局靜態(tài)變量來(lái)跟蹤超時(shí)
static uint32_t last_activity_time = 0;
static uint16_t previous_count = 0;
uint32_t current_time = Get_32bit_Timer_Value();
// 如果數(shù)據(jù)計(jì)數(shù)發(fā)生變化,說(shuō)明有新的數(shù)據(jù)到來(lái)
if (previous_count != ir_data_count)
{
previous_count = ir_data_count;
last_activity_time = current_time;
}
else
{
// 數(shù)據(jù)計(jì)數(shù)沒(méi)有變化,檢查是否超時(shí)
uint32_t time_since_last_activity = current_time - last_activity_time;
// 如果超過(guò)10ms沒(méi)有新數(shù)據(jù),認(rèn)為接收完成
if (time_since_last_activity > 10000) // 10ms = 10000us
{
// 設(shè)置數(shù)據(jù)就緒標(biāo)志
ir_data_ready = 1;
ir_state = IR_IDLE;
// 重置跟蹤變量
previous_count = 0;
return 1;
}
}
return 0;
}
return 0;
}
在主循環(huán)里輪詢 IR_Is_Ready,如果返回為 1,則說(shuō)明數(shù)據(jù)就緒,開(kāi)始紅外學(xué)習(xí),調(diào)用函數(shù) ir_learn,ir_learn 函數(shù)用于重復(fù)獲取紅外信號(hào),用于后續(xù)濾波。在函數(shù)內(nèi)也會(huì)計(jì)時(shí),當(dāng)超過(guò) 30s 后,認(rèn)為紅外學(xué)習(xí)失敗,若在 30s 內(nèi)完成 3 次紅外接收,則認(rèn)為紅外學(xué)習(xí)成功,并開(kāi)始均值濾波。均值濾波即將數(shù)組相同下標(biāo)的值相加并除以數(shù)組的個(gè)數(shù)。濾波后通過(guò)串口打印紅外時(shí)間序列。完整代碼如下:
/**
* @brief 對(duì)二維數(shù)組進(jìn)行均值濾波處理
* @param dataCount: 每個(gè)一維數(shù)組中有效數(shù)據(jù)的個(gè)數(shù)
*/
void IR_Mean_Filter(uint8_t dataCount)
{
uint16_t i;
uint32_t sum;
uint16_t mean_value;
// 對(duì)每個(gè)數(shù)據(jù)位置進(jìn)行均值濾波
for (i = 0; i < dataCount && i < IR_DATA_BUFFER_SIZE; i++)
{
sum = 0;
// 累加所有數(shù)組在同一位置的值
for (unsigned char j = 0; j < 3; j++)
{
sum += receive_data[j][i];
}
// 計(jì)算均值
mean_value = (uint16_t)(sum / 3);
// 存儲(chǔ)到濾波后的數(shù)據(jù)數(shù)組中
filtering_data[i] = mean_value;
}
}
void ir_learn()
{
// 計(jì)數(shù)
unsigned char count = 0;
unsigned char successFlag = 0;
unsigned int dataCount = 0;
IR_Get_Time_Data(receive_data[count], IR_Get_Data_Count());
// 獲取紅外數(shù)據(jù)數(shù)量
dataCount = IR_Get_Data_Count();
IR_Clear_Data();
count++;
// 啟動(dòng)30s計(jì)時(shí)
RTC_30Sec_Start();
while (!RTC_30Sec_IsTimeout())
{
if (IR_Is_Ready())
{
IR_Get_Time_Data(receive_data[count], IR_Get_Data_Count());
IR_Clear_Data();
count++;
if (count == 3)
{
successFlag = 1;
break;
}
}
}
RTC_30Sec_Stop();
if (!successFlag)
{
// 超時(shí),停止紅外學(xué)習(xí),并返回
printf("timeout return rn");
return;
}
// 均值濾波
IR_Mean_Filter(dataCount);
printf("IR learn success rn");
printf("length:%d,紅外時(shí)間序列:rn", dataCount);
for (unsigned int i = 1; i < dataCount; i++)
{
printf("%drn", filtering_data[i]);
}
printf("rn");
}
主函數(shù)代碼
主函數(shù)完整代碼如下:
#include "bsp_rcc.h"
#include "bsp_tim.h"
#include "bsp_uart.h"
#include "delay.h"
#include "do_mqtt.h"
#include "socket.h"
#include "stdlib.h"
#include "wiz_interface.h"
#include "wizchip_conf.h"
#include < stdio.h >
#include < stdlib.h >
#include < string.h >
#include "IR_Control.h"
#include "IR_Receive.h"
#include "RTC.h"
#include "ir_ac_control.h"
#define SOCKET_ID 0
#define ETHERNET_BUF_MAX_SIZE (1024 * 2)
/* 網(wǎng)絡(luò)配置信息 */
wiz_NetInfo default_net_info = {
.mac = {0x00, 0x08, 0xdc, 0x12, 0x22, 0x05},
.ip = {192, 168, 2, 40},
.gw = {192, 168, 2, 1},
.sn = {255, 255, 255, 0},
.dns = {8, 8, 8, 8},
.dhcp = NETINFO_DHCP};
uint8_t ethernet_buf[ETHERNET_BUF_MAX_SIZE] = {0};
static uint8_t mqtt_send_ethernet_buf[ETHERNET_BUF_MAX_SIZE] = {0};
static uint8_t mqtt_recv_ethernet_buf[ETHERNET_BUF_MAX_SIZE] = {0};
// 第三方庫(kù)IRext定義的結(jié)構(gòu)體,用于存儲(chǔ)空調(diào)狀態(tài)
t_remote_ac_status ac_status;
// 品牌
unsigned char brand = 0;
// 型號(hào)
unsigned char type = 0;
// 操作類型
unsigned char operation = 0;
int main(void)
{
// 時(shí)鐘初始化,使能外部高速晶振時(shí)鐘,主頻倍頻至為216MHz,PCLK1、PCLK2都設(shè)為216MHz
rcc_clk_config();
delay_init();
console_usart_init(115200);
tim3_init();
printf("Infrared Remote Control Gatewayrn");
wiz_toe_init();
wiz_phy_link_check();
network_init(ethernet_buf, &default_net_info);
// 紅外初始化
ir_Config();
IR_Receive_Init();
// rtc 時(shí)鐘初始化
RTC_Init();
mqtt_init(SOCKET_ID, mqtt_send_ethernet_buf, mqtt_recv_ethernet_buf);
while (1)
{
do_mqtt();
// 檢查是否收到紅外發(fā)送命令
if (IR_is_send())
{
printf("start IR controlrn");
// 紅外控制
ir_Control(brand, type, operation, &ac_status);
Reset_IR_Send();
// 清除紅外接收到的數(shù)據(jù),避免觸發(fā)紅外學(xué)習(xí)
IR_Clear_Data();
}
// 檢查是否收到紅外學(xué)習(xí)命令
if (IR_is_learn())
{
printf("start IR learnrn");
ir_Learn(getFileName());
Reset_IR_learn();
}
}
}
4.功能驗(yàn)證
經(jīng)過(guò)驗(yàn)證,目前的功能能夠穩(wěn)定運(yùn)行。
5.結(jié)語(yǔ)
基于 W55MH32Q-EVB 構(gòu)建的紅外遙控?關(guān),以其獨(dú)特的技術(shù)優(yōu)勢(shì)為智能家居領(lǐng)域的紅外設(shè)備控制提供了全新解決?案。W55MH32Q 芯?的?性能內(nèi)核與全硬件?絡(luò)協(xié)議棧,確保了數(shù)據(jù)處理與通信的?效穩(wěn)定,讓微信?程序與?關(guān)的交互響應(yīng)迅速。
IRext 紅外庫(kù)的靈活移植,突破了品牌壁壘,實(shí)現(xiàn)了多品牌空調(diào)的統(tǒng)?控制;JSON 數(shù)據(jù)解析則讓指令傳遞更精準(zhǔn)?效。從硬件設(shè)計(jì)到軟件實(shí)現(xiàn),整個(gè)系統(tǒng)?縫銜接,既解決了傳統(tǒng)遙控器混亂的痛點(diǎn),?降低了智能家居改造的門檻。
未來(lái),隨著功能的進(jìn)?步拓展,該?關(guān)有望兼容更多類型的紅外設(shè)備,為?戶帶來(lái)更便捷、智能的?活體驗(yàn),也為紅外設(shè)備的智能化升級(jí)提供了可借鑒的技術(shù)范式。
感謝?家的耐?閱讀,想了解紅外學(xué)習(xí)、irext 紅外庫(kù),F(xiàn)atFs 文件系統(tǒng)的請(qǐng)關(guān)注我。后續(xù)會(huì)更新文章。另外,如果你在閱讀過(guò)程中有任何疑問(wèn),歡迎隨時(shí)通過(guò)私信留?。我會(huì)盡快回復(fù)消息。
審核編輯 黃宇
-
單片機(jī)
+關(guān)注
關(guān)注
6076文章
45479瀏覽量
669622 -
網(wǎng)關(guān)
+關(guān)注
關(guān)注
9文章
6731瀏覽量
56166 -
智能家居
+關(guān)注
關(guān)注
1943文章
9989瀏覽量
197314 -
紅外控制
+關(guān)注
關(guān)注
0文章
26瀏覽量
11857
發(fā)布評(píng)論請(qǐng)先 登錄
智能紅外遙控開(kāi)關(guān)控制器
云智能紅外轉(zhuǎn)WIFI網(wǎng)關(guān)
紅外學(xué)習(xí)與接收
基于紅外學(xué)習(xí)的離線語(yǔ)音控制低成本方案
基于紅外遙控的智能語(yǔ)音芯片在空調(diào)中的應(yīng)用
紅外遙控的相關(guān)資料下載
用ESP8266實(shí)現(xiàn)的紅外學(xué)習(xí)遙控器介紹
基于NiosⅡ的紅外學(xué)習(xí)型遙控器設(shè)計(jì)
基于51系列單片機(jī)的紅外遙控設(shè)計(jì)
通用的紅外遙控開(kāi)關(guān)設(shè)計(jì)與實(shí)現(xiàn)
ESP8266紅外學(xué)習(xí)遙控器
智能學(xué)習(xí)型紅外空調(diào)遙控器的設(shè)計(jì)與實(shí)現(xiàn)
軟硬件開(kāi)源項(xiàng)目-紅外遙控網(wǎng)關(guān):IRext 紅外碼庫(kù)+遠(yuǎn)程實(shí)現(xiàn)紅外開(kāi)關(guān)空調(diào)+紅外學(xué)習(xí)
評(píng)論