一、模塊來源
模塊實物展示:
資料鏈接:https://pan.baidu.com/s/1dEWVMIFDWb7k1NcsRy5hHA
資料提取碼:uucv
1.CR2025環(huán)保紐扣電池,容量160mah
2.發(fā)射距離:8m以上(具體和周圍環(huán)境、接收端的靈敏度等因素有關(guān))
3.有效角度:60度
4.面貼材料:0.125mmPET,有效壽命2萬次。
5.品質(zhì)穩(wěn)定,性價比高
6.靜態(tài)電流3-5uA,動態(tài)電流3-5mA。
以上信息見廠家資料文件
三、移植過程
我們的目標(biāo)是將例程移植至CW32F030C8T6開發(fā)板上【能夠?qū)崿F(xiàn)紅外信號接收的功能】。首先要獲取資料,查看數(shù)據(jù)手冊應(yīng)如何實現(xiàn)讀取數(shù)據(jù),再移植至我們的工程。
3.1查看資料
在光譜中波長自760nm至400um的電磁波稱為紅外線,它是一種不可見光。紅外線通信的例子我們每個人應(yīng)該都很熟悉,目前常用的家電設(shè)備幾乎都可以通過紅外遙控的方式進行遙控,比如電視機、空調(diào)、投影儀等,都可以見到紅外遙控的影子。這種技術(shù)應(yīng)用廣泛,相應(yīng)的應(yīng)用器件都十分廉價,因此紅外遙控是我們?nèi)粘TO(shè)備控制的理想方式。
紅外線的通訊原理
紅外光是以特定的頻率脈沖形式發(fā)射,接收端收到到信號后,按照約定的協(xié)議進行解碼,完成數(shù)據(jù)傳輸。在消費類電子產(chǎn)品里,脈沖頻率普遍采用 30KHz 到 60KHz 這個頻段,NEC協(xié)議的頻率就是38KHZ。這個以特定的頻率發(fā)射其實就可以理解為點燈,不要被復(fù)雜的詞匯難住了,就是控制燈的閃爍頻率(亮滅),和剛學(xué)單片機完成閃爍燈一樣的意思,只不過是燈換了一種類型,都是燈。
接收端的原理: 接收端的芯片對這個紅外光比較敏感,可以根據(jù)有沒有光輸出高低電平,如果發(fā)送端的閃爍頻率是有規(guī)律的,接收端收到后輸出的高電平和低電平也是有規(guī)律對應(yīng)的,這樣發(fā)送端和接收端只要約定好,那就可以做數(shù)據(jù)傳輸了。
紅外線傳輸協(xié)議可以說是所有無線傳輸協(xié)議里成本最低,最方便的傳輸協(xié)議了,但是也有缺點,距離不夠長,速度不夠快;當(dāng)然,每個傳輸協(xié)議應(yīng)用的環(huán)境不一樣,定位不一樣,好壞沒法比較,具體要看自己的實際場景選擇合適的通信方式。
NEC協(xié)議介紹
NEC協(xié)議是眾多紅外線協(xié)議中的一種(這里說的協(xié)議就是他們數(shù)據(jù)幀格式定義不一樣,數(shù)據(jù)傳輸原理都是一樣的),我們購買的外能遙控器、淘寶買的mini遙控器、電視機、投影儀幾乎都是NEC協(xié)議。像格力空調(diào)、美的空調(diào)這些設(shè)備使用的就是其他協(xié)議格式,不是NEC協(xié)議,但是只要學(xué)會一種協(xié)議解析方式,明白了紅外線傳輸原理,其他遙控器協(xié)議都可以解出來。
NEC協(xié)議一次完整的傳輸包含: 引導(dǎo)碼、8位地址碼、8位地址反碼、8位命令碼、8位命令反碼。這里我們主要講解如何接收紅外發(fā)送端發(fā)送的NEC協(xié)議內(nèi)容。
引導(dǎo)碼:由9ms的低電平+4.5ms的高電平組成。
4個字節(jié)的數(shù)據(jù): 地址碼+地址反碼+命令碼+命令反碼。這里的反碼可以用來校驗數(shù)據(jù)是否傳輸正確,有沒有丟包。
重點: NEC協(xié)議傳輸數(shù)據(jù)位的時候,0和1的區(qū)分是依靠收到的高、低電平的持續(xù)時間來進行區(qū)分的。這是解碼關(guān)鍵。
數(shù)據(jù)發(fā)送0碼:0.56ms低電平+ 0.56ms的高電平。
數(shù)據(jù)發(fā)送1碼:0.56ms低電平+1.68ms的高電平
所以,收到一個數(shù)據(jù)位的完整時間表示方法是這樣的:
收到數(shù)據(jù)位0: 0.56ms低電平+ 0.56ms的高電平 收到數(shù)據(jù)位1: 0.56ms低電平+1.68ms的高電平
還有一個重復(fù)碼,它是由一個 9ms 的低電平和一個 2.5ms 的高電平組成。當(dāng)一個紅外信號連續(xù)發(fā)送時,可以通過發(fā)送重復(fù)碼的方式快速發(fā)送。
3.2引腳選擇
當(dāng)紅外線接收頭感應(yīng)到有紅外光就輸出低電平,沒有感應(yīng)到紅外光就輸出高電平。因此我們配置紅外引腳為外部中斷下降沿觸發(fā)方式,當(dāng)紅外引腳有下降沿時,我們馬上進入中斷處理并接收紅外信號。
模塊接線圖
3.3移植至工程
引腳配置如下:
//紅外引腳初始化 void infrared_goio_config(void) { IR_RCC_GPIO_ENABLE(); // 使能GPIO時鐘 GPIO_InitTypeDef GPIO_InitStruct; // GPIO初始化結(jié)構(gòu)體 GPIO_InitStruct.Pins = IR_PIN; // GPIO引腳 GPIO_InitStruct.Mode = GPIO_MODE_INPUT_PULLUP; // 上拉輸入 GPIO_InitStruct.Speed = GPIO_SPEED_HIGH; // 速度高 GPIO_InitStruct.IT = GPIO_IT_FALLING; // 下降沿觸發(fā)中斷 GPIO_Init(IR_PORT, &GPIO_InitStruct); // 初始化 // 清除PA0中斷標(biāo)志 GPIOA_INTFLAG_CLR(EXTI_BV); // 使能NVIC NVIC_EnableIRQ(EXTI_IRQ); }
紅外信號的數(shù)據(jù),全部是以時間長度來確定數(shù)據(jù)是0還是1,而最小的單位要求有560us,已經(jīng)達到了us級的測量。
我們在 空白工程中已經(jīng)為大家準(zhǔn)備好了us延時,就在board 文件中。
獲取高低電平時間
獲取低電平時間的實現(xiàn)代碼如下:
//獲取紅外低電平時間 //以微秒us作為時間參考 void get_infrared_low_time( uint32_t *low_time ) { uint32_t time_val = 0; while( GPIO_ReadPin(IR_PORT, IR_PIN) == 0 ) { if( time_val>= 500 ) { *low_time = time_val; return; } delay_us(20); time_val++; } *low_time = time_val; }
當(dāng)引腳為低電平時,將進入 while 循環(huán),直到不為低電平時就結(jié)束循環(huán)。在循環(huán)之中不斷的讓時間變量time_val累加, 每加一次需要經(jīng)過20us。當(dāng)time_val變量累加時間大于 500 * 20 = 10000us = 10ms時,判斷為超時,強行結(jié)束該函數(shù),防止阻礙系統(tǒng)運行。
獲取高電平時間的代碼同理:
//獲取紅外高電平時間 //以微秒us作為時間參考 void get_infrared_high_time(uint32_t *high_time) { uint32_t time_val = 0; while( GPIO_ReadPin(IR_PORT, IR_PIN) == 1 ) { if( time_val >= 250 ) { *high_time = time_val; return; } delay_us(20); time_val++; } *high_time = time_val; }
引導(dǎo)碼與重復(fù)碼判斷
引導(dǎo)碼是由一個 9ms 的低電平和一個 4.5ms 的高電平組成。每當(dāng)接收到一個紅外信號時,第一個數(shù)據(jù)就是引導(dǎo)碼。我們通過判斷紅外信號的第一個數(shù)據(jù)是否是引導(dǎo)碼,來決定是否要進行后面的數(shù)據(jù)接收處理。
重復(fù)碼是由一個 9ms 的低電平和一個 2.5ms 的高電平組成。當(dāng)我們的紅外遙控一直按住按鍵時,就會發(fā)出重復(fù)碼,我們可以檢測重復(fù)碼,來確定是否要連續(xù)觸發(fā)重復(fù)動作,比如長按開機,長按加速等等。
/****************************************************************** * 函 數(shù) 名 稱:guide_and_repeat_code_judgment * 函 數(shù) 說 明:引導(dǎo) 和 重復(fù) 碼 判斷 * 函 數(shù) 形 參:無 * 函 數(shù) 返 回:1:不是引導(dǎo)碼 2:重復(fù)碼 0:引導(dǎo)碼 * 作 者:LC * 備 注:以20微秒us作為時間參考 引導(dǎo)碼:由一個 9ms 的低電平和一個 4.5ms 的高電平組成 重復(fù)碼:由一個 9ms 的低電平和一個 2.5ms 的高電平組成 ******************************************************************/ uint8_t guide_and_repeat_code_judgment(void) { uint32_t out_time=0; get_infrared_low_time(&out_time); //time>10ms time 8ms if((out_time > 500) || (out_time < 400)) { return 1; } get_infrared_high_time(&out_time); // x?>5ms 或者 x2ms if((out_time > 250) || (out_time < 100)) { return 1; } //如果是重復(fù)碼 2ms < time < 3ms if((out_time > 100) && (out_time < 150)) { return 2; } return 0; }
完整紅外數(shù)據(jù)接收
具體接收流程:【判斷是否接收到引導(dǎo)碼】->【接收數(shù)據(jù)】->【判斷數(shù)據(jù)是否正確】。
//接收紅外數(shù)據(jù) void receiving_infrared_data(void) { uint16_t group_num = 0,data_num = 0; uint32_t time=0; uint8_t bit_data = 0; uint8_t ir_value[5] = {0}; uint8_t guide_and_repeat_code = 0; //等待引導(dǎo)碼 guide_and_repeat_code = guide_and_repeat_code_judgment(); //如果不是引導(dǎo)碼則結(jié)束解析 if( guide_and_repeat_code == 1 ) return; //共有4組數(shù)據(jù) //地址碼+地址反碼+命令碼+命令反碼 for(group_num = 0; group_num < 4; group_num++ ) { //接收一組8位的數(shù)據(jù) for( data_num = 0; data_num < 8; data_num++ ) { //接收低電平 get_infrared_low_time(&time); //如果不在0.56ms內(nèi)的低電平,數(shù)據(jù)錯誤 if((time > 60) || (time < 20)) { return ; } time = 0; //接收高電平 get_infrared_high_time(&time); //如果是在1200us=60) && (time < 100)) { bit_data = 1; } //如果是在200us=10) && (time < 50)) { bit_data = 0; } //groupNum表示第幾組數(shù)據(jù) ir_value[ group_num ] <= 1; //接收的第1個數(shù)為高電平;在第二個for循環(huán)中,數(shù)據(jù)會向右移8次 ir_value[ group_num ] |= bit_data; //用完時間要重新賦值 time=0; } } //判斷數(shù)據(jù)是否正確,正確則保存數(shù)據(jù) infrared_data_true_judgment(ir_value); }
判斷數(shù)據(jù)是否正確,可以通過將正常數(shù)據(jù)取反,與反碼比較。如果不一致說明數(shù)據(jù)不對。
typedef struct INFRARED_DATA{ uint8_t AddressCode; //地址碼 uint8_t AddressInverseCode; //地址反碼 uint8_t CommandCode; //命令碼 uint8_t CommandInverseCode; //命令反碼 }_INFRARED_DATA_STRUCT_; _INFRARED_DATA_STRUCT_ InfraredData; //紅外數(shù)據(jù)是否正確判斷 uint8_t infrared_data_true_judgment(uint8_t *value) { //判斷地址碼是否正確 if( value[0] != (uint8_t)(~value[1]) ) return 0; //判斷命令碼是否正確 if( value[2] != (uint8_t)(~value[3]) ) return 1; //串口輸出查看接收到的數(shù)據(jù) printf("%x %x %x %xrn",value[0],value[1],value[2],value[3]); //保存正確數(shù)據(jù) InfraredData.AddressCode = value[0]; InfraredData.AddressInverseCode = value[1]; InfraredData.CommandCode = value[2]; InfraredData.CommandInverseCode = value[3]; } //獲取紅外發(fā)送過來的命令 uint8_t get_infrared_command(void) { return InfraredData.CommandCode; } //清除紅外發(fā)送過來的數(shù)據(jù) void clear_infrared_command(void) { InfraredData.CommandCode = 0x00; }
最后,記得在外部中斷服務(wù)函數(shù)中,調(diào)用紅外接收函數(shù)。
void EXTI_HANDLER(void) { if(IR_PORT->ISR_f.EXTI_PIN) // 中斷標(biāo)志位 { if(GPIO_ReadPin(IR_PORT, IR_PIN) == GPIO_Pin_RESET) // 如果是低電平 { //接收一次紅外數(shù)據(jù) receiving_infrared_data(); } GPIOA_INTFLAG_CLR(EXTI_BV); // 清除標(biāo)志位 } }
移植步驟中的導(dǎo)入.c和.h文件與【CW32模塊使用】DHT11溫濕度傳感器相同,只是將.c和.h文件更改為bsp_ir_receiver.c與bsp_ir_receiver.h。這里不再過多講述,移植完成后面修改相關(guān)代碼。
以下為完成紅外接收代碼:
bsp_ir_receiver.c
/* * Change Logs: * Date Author Notes * 2024-06-24 LCKFB-LP first version */ #include "bsp_ir_receiver.h" #include "stdio.h" #include "board.h" typedef struct INFRARED_DATA{ uint8_t AddressCode; //地址碼 uint8_t AddressInverseCode; //地址反碼 uint8_t CommandCode; //命令碼 uint8_t CommandInverseCode; //命令反碼 }_INFRARED_DATA_STRUCT_; _INFRARED_DATA_STRUCT_ InfraredData; //紅外引腳初始化 void infrared_goio_config(void) { IR_RCC_GPIO_ENABLE(); // 使能GPIO時鐘 GPIO_InitTypeDef GPIO_InitStruct; // GPIO初始化結(jié)構(gòu)體 GPIO_InitStruct.Pins = IR_PIN; // GPIO引腳 GPIO_InitStruct.Mode = GPIO_MODE_INPUT_PULLUP; // 上拉輸入 GPIO_InitStruct.Speed = GPIO_SPEED_HIGH; // 速度高 GPIO_InitStruct.IT = GPIO_IT_FALLING; // 下降沿觸發(fā)中斷 GPIO_Init(IR_PORT, &GPIO_InitStruct); // 初始化 // 清除PA0中斷標(biāo)志 GPIOA_INTFLAG_CLR(EXTI_BV); // 使能NVIC NVIC_EnableIRQ(EXTI_IRQ); } //獲取紅外低電平時間 //以微秒us作為時間參考 void get_infrared_low_time( uint32_t *low_time ) { uint32_t time_val = 0; while( GPIO_ReadPin(IR_PORT, IR_PIN) == 0 ) { if( time_val>= 500 ) { *low_time = time_val; return; } delay_us(20); time_val++; } *low_time = time_val; } //獲取紅外高電平時間 //以微秒us作為時間參考 void get_infrared_high_time(uint32_t *high_time) { uint32_t time_val = 0; while( GPIO_ReadPin(IR_PORT, IR_PIN) == 1 ) { if( time_val >= 250 ) { *high_time = time_val; return; } delay_us(20); time_val++; } *high_time = time_val; } /****************************************************************** * 函 數(shù) 名 稱:guide_and_repeat_code_judgment * 函 數(shù) 說 明:引導(dǎo) 和 重復(fù) 碼 判斷 * 函 數(shù) 形 參:無 * 函 數(shù) 返 回:1:不是引導(dǎo)碼 2:重復(fù)碼 0:引導(dǎo)碼 * 作 者:LC * 備 注:以20微秒us作為時間參考 引導(dǎo)碼:由一個 9ms 的低電平和一個 4.5ms 的高電平組成 重復(fù)碼:由一個 9ms 的低電平和一個 2.5ms 的高電平組成 ******************************************************************/ uint8_t guide_and_repeat_code_judgment(void) { uint32_t out_time=0; get_infrared_low_time(&out_time); //time>10ms time 8ms if((out_time > 500) || (out_time < 400)) { return 1; } get_infrared_high_time(&out_time); // x?>5ms 或者 x2ms if((out_time > 250) || (out_time < 100)) { return 1; } //如果是重復(fù)碼 2ms < time < 3ms if((out_time > 100) && (out_time < 150)) { return 2; } return 0; } //紅外數(shù)據(jù)是否正確判斷 uint8_t infrared_data_true_judgment(uint8_t *value) { //判斷地址碼是否正確 if( value[0] != (uint8_t)(~value[1]) ) return 0; //判斷命令碼是否正確 if( value[2] != (uint8_t)(~value[3]) ) return 1; printf("%x %x %x %xrn",value[0],value[1],value[2],value[3]); //保存正確數(shù)據(jù) InfraredData.AddressCode = value[0]; InfraredData.AddressInverseCode = value[1]; InfraredData.CommandCode = value[2]; InfraredData.CommandInverseCode = value[3]; } //接收紅外數(shù)據(jù) void receiving_infrared_data(void) { uint16_t group_num = 0,data_num = 0; uint32_t time=0; uint8_t bit_data = 0; uint8_t ir_value[5] = {0}; uint8_t guide_and_repeat_code = 0; //等待引導(dǎo)碼 guide_and_repeat_code = guide_and_repeat_code_judgment(); //如果不是引導(dǎo)碼則結(jié)束解析 if( guide_and_repeat_code == 1 ) { printf("errrn"); return; } //共有4組數(shù)據(jù) //地址碼+地址反碼+命令碼+命令反碼 for(group_num = 0; group_num < 4; group_num++ ) { //接收一組8位的數(shù)據(jù) for( data_num = 0; data_num < 8; data_num++ ) { //接收低電平 get_infrared_low_time(&time); //如果不在0.56ms內(nèi)的低電平,數(shù)據(jù)錯誤 if((time > 60) || (time < 20)) { return ; } time = 0; //接收高電平 get_infrared_high_time(&time); //如果是在1200us=60) && (time < 100)) { bit_data = 1; } //如果是在200us=10) && (time < 50)) { bit_data = 0; } //groupNum表示第幾組數(shù)據(jù) ir_value[ group_num ] <<= 1; //接收的第1個數(shù)為高電平;在第二個for循環(huán)中,數(shù)據(jù)會向右移8次 ir_value[ group_num ] |= bit_data; //用完時間要重新賦值 time=0; } } //判斷數(shù)據(jù)是否正確,正確則保存數(shù)據(jù) infrared_data_true_judgment(ir_value); } //獲取紅外發(fā)送過來的命令 uint8_t get_infrared_command(void) { return InfraredData.CommandCode; } //清除紅外發(fā)送過來的數(shù)據(jù) void clear_infrared_command(void) { InfraredData.CommandCode = 0x00; } void EXTI_HANDLER(void) { if(IR_PORT-?>ISR_f.EXTI_PIN) // 中斷標(biāo)志位 { if(GPIO_ReadPin(IR_PORT, IR_PIN) == GPIO_Pin_RESET) // 如果是低電平 { //接收一次紅外數(shù)據(jù) receiving_infrared_data(); } GPIOA_INTFLAG_CLR(EXTI_BV); // 清除標(biāo)志位 } } bsp_ir_receiver.h /* * Change Logs: * Date Author Notes * 2024-06-24 LCKFB-LP first version */ #ifndef _BSP_IR_RECEIVER_H__ #define _BSP_IR_RECEIVER_H__ #include "board.h" #define IR_RCC_GPIO_ENABLE() __RCC_GPIOA_CLK_ENABLE() #define IR_PORT CW_GPIOA #define IR_PIN GPIO_PIN_2 #define EXTI_PIN PIN2 #define EXTI_BV bv2 #define EXTI_IRQ GPIOA_IRQn #define EXTI_HANDLER GPIOA_IRQHandler void infrared_goio_config(void); uint8_t get_infrared_command(void); void clear_infrared_command(void); #endif
四、移植驗證
在自己工程中的main主函數(shù)中,編寫如下。
/* * Change Logs: * Date Author Notes * 2024-06-24 LCKFB-LP first version */ #include "board.h" #include "stdio.h" #include "bsp_uart.h" #include "bsp_ir_receiver.h" int32_t main(void) { board_init(); // 開發(fā)板初始化 uart1_init(115200); // 串口1波特率115200 //紅外接收初始化 infrared_goio_config(); printf("Start!!!rn"); while(1) { //如果按下遙控的【1】鍵 if( get_infrared_command() == 0xA2 ) { clear_infrared_command(); printf("按下【1】按鍵! rn"); } } }
移植現(xiàn)象:
模塊移植成功案例代碼:
鏈接:https://pan.baidu.com/s/1Yln6MD82bPkgS2x-YMnfCQ?pwd=LCKF
提取碼:LCKF
審核編輯 黃宇
-
紅外接收模塊
+關(guān)注
關(guān)注
1文章
5瀏覽量
6956 -
CW32
+關(guān)注
關(guān)注
1文章
218瀏覽量
706
發(fā)布評論請先 登錄
相關(guān)推薦
評論