紅外遙控學習,萬能遙控解決方案
- 1. 原理
- 2. 思路
- 3. 紅外遙控接收
- 3.1 初始化定時器
- 3.2 定時器輸入捕獲
- 3.3 獲取資料
- 3.4 紅外接收測驗
- 3.5 測驗程序
- 4. 發送程式
- 4.1 初始化定時器和定時器的通道
- 4.1發送函式
1. 原理
目前很多遙控器還是采用的紅外,如電視機、空調等,有時專案需要把遙控嵌入式自己的設備中,進行智能控制,此時就需要智能學習和發送了,紅外遙控電路圖如下:

左側為紅外發送電路,右側有紅外接收電路,
● 發送端:
以NEC協議為例(實際測驗中遵循NEC協議的不多),資訊傳輸是基于38K載波,也就是說紅外線是以載波的方式傳遞,
發送協議資料“0” = 發送載波560us + 不發送載波560us
發送協議資料“1” = 發送載波560us+ 不發送載波1680us
發送的波形如下圖所示,下圖中為 0 0 0 0 1 0 1

● 接收端:
在紅外接收端,如果接收到紅外38K載波,則IR輸出為低電平,如果不是載波包括固定低電平和固定高電平則輸出高電平,在IR端接收的信號如下所示:

NEC協議規定:
發送協議資料“0” = 發送載波560us + 不發送載波560us
發送協議資料“1” = 發送載波560us+ 不發送載波1680us
發送引導碼 = 發送載波9000us + 不發送載波4500us
● 總結:
1、接收端收到38K載波脈沖為低電平,沒有則為高電平;所以在設計發送的時候,發送低電平應該開啟38KHZ的PWM,發送高電平則關閉PWM(默認為高電平)
2、資料“0” 和資料“1” 是由接收到的一個高電平和一個低電平組合而來,一般來說高電平時間等于低電平時間為資料0,其它則為1
參考: STM32之紅外遙控信號自學習實作
2. 思路
思路簡單,簡述為: 捕獲——>保存——>發射
先捕獲一個紅外遙控按鍵的全部資訊,不管內容是什么,再保存到flash中,就是多個高低電平的持續時間,然后再一一發射出去,這樣不管是什么協議都能成功,
3. 紅外遙控接收
3.1 初始化定時器
具體見代碼和詳細的注釋:
// ir_inputCapture_send.c
/*******************************************************************
* @brief 定時器16輸入捕獲初始化/紅外遙控初始化、設定IO以及TIM16_CH1的輸入捕獲、10ms溢位
********************************************************************/
void TIM16_Remote_Init(void)
{
TIM_IC_InitTypeDef TIM16_CH1Config;
TIM16_Handler.Instance = TIM16; //通用定時器16
TIM16_Handler.Init.Prescaler=(48-1); //預分頻器,1M的計數頻率,1us加1.
TIM16_Handler.Init.CounterMode=TIM_COUNTERMODE_UP; //向上計數器
TIM16_Handler.Init.Period = (60000-1); //自動裝載值,16位最大65536,此處設定60ms的計時
TIM16_Handler.Init.ClockDivision=TIM_CLOCKDIVISION_DIV1; //時鐘分頻因子
HAL_TIM_IC_Init(&TIM16_Handler);
//初始化TIM1輸入捕獲引數
TIM16_CH1Config.ICPolarity = TIM_INPUTCHANNELPOLARITY_FALLING; //注意下降沿捕獲
TIM16_CH1Config.ICSelection=TIM_ICSELECTION_DIRECTTI; //映射到TI4上
TIM16_CH1Config.ICPrescaler=TIM_ICPSC_DIV1; //配置輸入分頻,不分頻
TIM16_CH1Config.ICFilter=0x03; // 0 //IC4F=0003 8個定時器時鐘周期濾波
HAL_TIM_IC_ConfigChannel(&TIM16_Handler, &TIM16_CH1Config, TIM_CHANNEL_1);//配置TIM4通道4
__HAL_TIM_ENABLE_IT(&TIM16_Handler,TIM_IT_UPDATE); //使能更新中斷
HAL_TIM_IC_Stop_IT(&TIM16_Handler,TIM_CHANNEL_1);
}
// stm32f0xx_hal_msp.c.c
//定時器16底層驅動,時鐘使能,引腳配置
//此函式會被上述的HAL_TIM_IC_Init()呼叫
//htim:定時器句柄
void HAL_TIM_IC_MspInit(TIM_HandleTypeDef *htim)
{
GPIO_InitTypeDef GPIO_InitStruct;
if(htim->Instance==TIM16)
{
/************TIM16_CH1************/
GPIO_InitTypeDef GPIO_Initure;
__HAL_RCC_TIM16_CLK_ENABLE(); //使能TIM16時鐘
__HAL_RCC_GPIOA_CLK_ENABLE(); //開啟GPIOA時鐘
GPIO_Initure.Pin = GPIO_PIN_6; //PB9
GPIO_Initure.Mode = GPIO_MODE_AF_PP; //復用輸入
GPIO_Initure.Pull = GPIO_PULLUP; //上拉
GPIO_Initure.Speed = GPIO_SPEED_FREQ_HIGH; //高速
GPIO_Initure.Alternate = GPIO_AF5_TIM16;
HAL_GPIO_Init(GPIOA,&GPIO_Initure);
HAL_NVIC_SetPriority(TIM16_IRQn,2,0); //設定中斷優先級,搶占優先級2,子優先級0
HAL_NVIC_EnableIRQ(TIM16_IRQn); //開啟ITM16中斷
}
}
3.2 定時器輸入捕獲
紅外編碼是由多個位元組編碼組成的,根據持續的高低電平時間不同,而形成的編碼信號,此時可以通過定時器捕獲來實作,
定時器輸入捕獲中斷回呼函式如下,紅外信號基本都是低電平起,可先用下降沿捕獲:
// ir_inputCapture_send.c
void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)//捕獲中斷發生時執行
{
if(htim->Instance==TIM16)
{
if(READ_IR_GPIO()) //上升沿捕獲
{
Dval = HAL_TIM_ReadCapturedValue(&TIM16_Handler, TIM_CHANNEL_1); //讀取CCR1也可以清CC4IF標志位
TIM_RESET_CAPTUREPOLARITY(&TIM16_Handler, TIM_CHANNEL_1); //一定要先清除原來的設定!!
TIM_SET_CAPTUREPOLARITY(&TIM16_Handler, TIM_CHANNEL_1, TIM_ICPOLARITY_FALLING); //CC1P=1 設定為下降沿捕獲
if(0 == irRun.receive.cnt) {
__HAL_TIM_SET_COUNTER(&TIM16_Handler,0); //清空定時器值
return ;
}
if(irRun.receive.cnt<(MAX_IR_LEARN_DATA_SIZE - 1)) { // 保存在陣列中的 0 2 4 6 8 中
irRun.buf[irRun.receive.cnt++] = Dval;
}
else {
RcvRemoteFinish();
}
__HAL_TIM_SET_COUNTER(&TIM16_Handler,0); //清空定時器值
}
else //下降沿捕獲
{
Dval = HAL_TIM_ReadCapturedValue(&TIM16_Handler, TIM_CHANNEL_1);
TIM_RESET_CAPTUREPOLARITY(&TIM16_Handler,TIM_CHANNEL_1);
TIM_SET_CAPTUREPOLARITY(&TIM16_Handler,TIM_CHANNEL_1,TIM_ICPOLARITY_RISING); //配置TIM16通道1上升沿捕獲
//當前這一次,不計數,下一次上升沿才計數
if(0 == irRun.receive.cnt) {
__HAL_TIM_SET_COUNTER(&TIM16_Handler,0); //清空定時器值
return;
}
if(irRun.receive.cnt < (MAX_IR_LEARN_DATA_SIZE-1)) //1 3 5 7 9
{
irRun.buf[irRun.receive.cnt++] = Dval;
if(irRun.receive.cnt>=20) {
irRun.receive.dataComplete = 1;
}
}
else
{
RcvRemoteFinish();
}
__HAL_TIM_SET_COUNTER(&TIM16_Handler,0); //清空定時器 放最后
}
}
}
// ir_inputCapture_send.c
//60m定時器計時后溢位中斷的回呼函式:
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
if(htim->Instance==TIM16) //定時器更新(溢位)中斷回呼函式 10ms溢位
{
if(irRun.receive.dataComplete) //至少已經收到了20個位元組,才算接收到一幀資訊
{
RcvRemoteFinish();
}
else {
irRun.receive.cnt = 0; //重新來過,此幀資料無效
}
}
}
// ir_inputCapture_send.c
/* 當前紅外遙控一幀結束,
觸發條件:① 接收到的紅外位元組個數已經超過最大允許的長度 (HAL_TIM_IC_CaptureCallback)
② 如上述的定時器計時后溢位中斷的回呼函式 (HAL_TIM_PeriodElapsedCallback)
*/
static void RcvRemoteFinish(void)
{
//1. 先失能 避免重洗掉前面保存的一幀完整資料
TIM16_Remote_Disable();
//2. 重新設定下降沿捕獲,以防上升沿捕獲的
TIM_SET_CAPTUREPOLARITY(&TIM16_Handler, TIM_CHANNEL_1, TIM_ICPOLARITY_FALLING); //CC1P=1 設定為下降沿捕獲
//3-1. 清零資料存在的標志
irRun.receive.dataComplete = 0; //先清零
//4-2. (多等待了60ms),不管如何都算完成了
irRun.receive.complete = 1; // irRun.receive.complete 為一幀接收的完整標志
irRun.receive.len = irRun.receive.cnt - 1; //一幀接收的最大長度,irRun.receive.cnt 多了1個
//清零接收長度 緩沖資料
irRun.receive.cnt = 0;
}
至此為止 資料保存在 irRun.buf 中, 個數為 irRun.receive.len ,
3.3 獲取資料
具體見代碼和詳細的注釋:
// 獲取一幀資料在上層呼叫
// 回傳值: 0, 沒有任何按鍵按下
// 1 ,按下的按鍵鍵值.
// ir_inputCapture_send.c
u8 GetRemoteRcvBuf(uint16_t *buf, uint32_t *len, uint8_t isRecord)
{
if(irRun.receive.complete && !irRun.receive.dataComplete) { //完成一幀的標記, 且現在沒有正在捕獲的信號
TIM_SET_CAPTUREPOLARITY(&TIM16_Handler, TIM_CHANNEL_1, TIM_ICPOLARITY_FALLING); //CC1P=1 設定為下降沿捕獲
//最多拷貝MAX_IR_LEARN_DATA_SIZE個, 第二次保護,前有一次保護措施
if(irRun.receive.len >= MAX_IR_LEARN_DATA_SIZE-1)
irRun.receive.len = MAX_IR_LEARN_DATA_SIZE-1;
if(irRun.receive.len >= GetFuncLen() && GetFuncLen()>20 && isRecord) {
irRun.receive.len = GetFuncLen();
if(irRun.receive.len >= MAX_IR_COMPARE)
irRun.receive.len = MAX_IR_COMPARE;
}
for(uint8_t i=0; i < irRun.receive.len; i++) { //i<98
buf[i] = irRun.buf[i+1]; //[97+1]
}
*len = irRun.receive.len;
//可以中斷接收另外的資料呢
irRun.receive.cnt = 0;
irRun.receive.complete = 0;
irRun.receive.len = 0;
return 1;
}
return 0;
}
上層應用呼叫:
// app.c
ack = GetRemoteRcvBuf(&userSave.irFunctionBuf[0], &userSave.irFunctionLen, 0);
得到的紅外編碼保存在 userSave.irFunctionBuf[] 中,資料長度在 userSave.irFunctionLen 中,當然驗證后有效,就可以保存在Flash中了,掉電后不丟失,
紅外接收到此結束,接下來驗證一番:
3.4 紅外接收測驗
測驗代碼,直接在main.c中呼叫:
#ifdef SYS_USE_DEBUG
u16 test_cnt = 0;
void All_Remote_Test()
{
u8 ack = 0;
TIM16_Remote_Enable(); //使能紅外遙控捕獲
while(1) {
ack = GetRemoteRcvBuf(&userSave.irFunctionBuf[0], &userSave.irFunctionLen, 0);
if(ack == 1) {
ack = 0;
LED_ON();
test_cnt++;
TIM16_Remote_Enable(); //使能紅外遙控捕獲
}
LED_OFF();
}
}
#endif
3.5 測驗程序
左邊使用 Keil 的 Debug除錯,捕獲一幀紅外遙控信號;
右邊使用 Kingst 數字信號邏輯分析儀,接收到的此幀資料;
遙控器采用三星的電視機遙控器(不是典型NEC協議),對比如下,左邊任意一個位元組的數字(持續時間us)都能與右邊完美對應上,證明了以上程式的可行性,

4. 發送程式
原理弄懂,紅外發送程式相對來說就簡單很多
4.1 初始化定時器和定時器的通道
定時器不分頻的情況下,需要產生38KHZ的頻率的方波,Period 需要設定成: 48000/38=1263.158
具體見代碼和詳細的注釋:
//ir_inputCapture_send.c
//定時器不分頻,需要產生38KHZ的頻率的方波,Period 需要設定成: 48000/38=1263.158
void TIM3_PWM_Init(void)
{
TIM_MasterConfigTypeDef MasterConfig;
TIM3_Handler.Instance = TIM3; //定時器3
TIM3_Handler.Init.Prescaler = 0; //定時器分頻
TIM3_Handler.Init.CounterMode=TIM_COUNTERMODE_UP; //向上計數模式
TIM3_Handler.Init.Period = 1264; ///48000/38=1263.158
TIM3_Handler.Init.ClockDivision=TIM_CLOCKDIVISION_DIV1;
TIM3_Handler.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;
HAL_TIM_PWM_Init(&TIM3_Handler); //初始化PWM
MasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
MasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
HAL_TIMEx_MasterConfigSynchronization(&TIM3_Handler, &MasterConfig);
TIM3_CH4Handler.OCMode = TIM_OCMODE_PWM1; //模式選擇PWM1
TIM3_CH4Handler.Pulse = 632; //設定比較值,此值用來確定占空比,默認比較值為自動重裝載值的一半,即占空比為50%
TIM3_CH4Handler.OCPolarity = TIM_OCPOLARITY_HIGH; //輸出比較極性為低
TIM3_CH4Handler.OCFastMode = TIM_OCFAST_DISABLE;
HAL_TIM_PWM_ConfigChannel(&TIM3_Handler,&TIM3_CH4Handler,TIM_CHANNEL_4); //配置TIM3通道4
HAL_TIM_PWM_MspInit(&TIM3_Handler);
HAL_TIM_PWM_Stop(&TIM3_Handler,TIM_CHANNEL_4);//關閉PWM通道4
}
4.1發送函式
發送函式記住核心即可:低電平的時候開啟PWM,高電平關閉,
/*******************************************************************
* @brief 紅外PWM控制的發送,在輪詢中操作 低電平的時候開啟PWM,高電平關閉
* @retval None
********************************************************************/
void IrPwmMultiSend(u16 *buf, u32 len)
{
//1、關閉紅外接收中斷
TIM16_Remote_Disable(); //停止捕獲TIM16的通道1
//先停止
HAL_TIM_PWM_Stop(&TIM3_Handler,TIM_CHANNEL_4);
delay_ms(10);
for(u8 i=0; i<len; i++) {
//偶數 [0] [2] [4] [6] 低電平的時候開啟PWM
__HAL_TIM_SET_COUNTER(&TIM3_Handler,0);
if(i%2 == 0) {
HAL_TIM_PWM_Start(&TIM3_Handler,TIM_CHANNEL_4);
}
else {
HAL_TIM_PWM_Stop(&TIM3_Handler,TIM_CHANNEL_4);
}
delay_us(buf[i]);
}
HAL_TIM_PWM_Stop(&TIM3_Handler,TIM_CHANNEL_4);
TIM16_Remote_Enable(); //開始捕獲TIM16的通道1
}
上層呼叫:
//app.c
IrPwmMultiSend(&userSave.irPowerBuf[0], userSave.irPowerLen); //發送開機指令
驗證的話,直接看要操控的目標,如電視機是否生效了,如果不行,在驗證發出的波形,是否滿足要求,
轉載請註明出處,本文鏈接:https://www.uj5u.com/qukuanlian/287661.html
標籤:區塊鏈
上一篇:9-6學習打卡(6.14)
下一篇:HTTPS底層實作原理
