1)實驗平臺:【正點原子】 NANO STM32F103 開發板
2)摘自《正點原子STM32 F1 開發指南(NANO 板-HAL 庫版)》關注官方微信號公眾號,獲取更多資料:正點原子
第二十五章 紅外遙控實驗
本章,我們將向大家介紹如何通過 STM32 來解碼紅外遙控器的信號。ALIENTK NANO
STM32F103 標配了紅外接收頭和一個很小巧的紅外遙控器。在本章中,我們將利用 STM32F1
的輸入捕獲功能,解碼開發板標配的這個紅外遙控器的編碼信號,並將解碼後的鍵值顯示在數
碼管上。本章分為如下幾個部分:
25.1 紅外遙控簡介
25.2 硬件設計
25.3 軟件設計
25.4 下載驗證
25.1 紅外遙控簡介
紅外遙控是一種無線、非接觸控制技術,具有抗干擾能力強,信息傳輸可靠,功耗低,成
本低,易實現等顯著優點,被諸多電子設備特別是家用電器廣泛採用,並越來越多的應用到計
算機系統中。
由於紅外線遙控不具有像無線電遙控那樣穿過障礙物去控制被控對象的能力,所以,在設
計紅外線遙控器時,不必要像無線電遙控器那樣,每套(發射器和接收器)要有不同的遙控頻率
或編碼(否則,就會隔牆控制或干擾鄰居的家用電器),所以同類產品的紅外線遙控器,可以有
相同的遙控頻率或編碼,而不會出現遙控信號“串門”的情況。這對於大批量生產以及在家用
電器上普及紅外線遙控提供了極大的方面。由於紅外線為不可見光,因此對環境影響很小,再
由紅外光波動波長遠小於無線電波的波長,所以紅外線遙控不會影響其他家用電器,也不會影
響臨近的無線電設備。
紅外遙控的編碼目前廣泛使用的是:NEC Protocol 的 PWM(脈衝寬度調製)和 Philips
RC-5 Protocol 的 PPM(脈衝位置調製)。ALIENTEK 戰艦 STM32 開發板配套的遙控器使用
的是 NEC 協議,其特徵如下:
1、8 位地址和 8 位指令長度;
2、地址和命令 2 次傳輸(確保可靠性)
3、PWM 脈衝位置調製,以發射紅外載波的佔空比代表“0”和“1”;
4、載波頻率為 38Khz;
5、位時間為 1.125ms 或 2.25ms;
NEC 碼的位定義:一個脈衝對應 560us 的連續載波,一個邏輯 1 傳輸需要 2.25ms(560us
脈衝+1680us 低電平),一個邏輯 0 的傳輸需要 1.125ms(560us 脈衝+560us 低電平)。而遙控
接收頭在收到脈衝的時候為低電平,在沒有脈衝的時候為高電平,這樣,我們在接收頭端收到
的信號為:邏輯 1 應該是 560us 低+1680us 高,邏輯 0 應該是 560us 低+560us 高。
NEC 遙控指令的數據格式為:同步碼頭、地址碼、地址反碼、控制碼、控制反碼。同步碼
由一個 9ms 的低電平和一個 4.5ms 的高電平組成,地址碼、地址反碼、控制碼、控制反碼均是
8 位數據格式。按照低位在前,高位在後的順序發送。採用反碼是為了增加傳輸的可靠性(可
用於校驗)。
我們遙控器的按鍵“▽”按下時,從紅外接收頭端收到的波形如圖 25.1.1 所示:
從圖 33.1.1 中可以看到,其地址碼為 0,控制碼為 168。可以看到在 100ms 之後,我們還
收到了幾個脈衝,這是 NEC 碼規定的連發碼(由 9ms 低電平+2.5m 高電平+0.56ms 低電平
+97.94ms 高電平組成),如果在一幀數據發送完畢之後,按鍵仍然沒有放開,則發射重複碼,
即連發碼,可以通過統計連發碼的次數來標記按鍵按下的長短/次數。
第十五章我們曾經介紹過利用輸入捕獲來測量高電平的脈寬,本章解碼紅外遙控信號,剛
好可以利用輸入捕獲的這個功能來實現遙控解碼。關於輸入捕獲的介紹,請參考第十五章的內
容。
25.2 硬件設計
本實驗採用定時器的輸入捕獲功能實現紅外解碼,本章實驗功能簡介:開機在初始化用到
的外設之後,即進入等待紅外觸發,如過接收到正確的紅外信號,則解碼,並在數碼管上顯示
鍵值(由於數碼管顯示字符有限,我們用顯示數字來代表鍵值)。同樣我們也是用 DS0 來指示
程序正在運行。
所要用到的硬件資源如下:
1) 指示燈 DS0
2) 數碼管
3) 蜂鳴器
4) 紅外接收頭
5) 紅外遙控器
前三個,在之前的實例已經介紹過了,遙控器屬於外部器件,遙控接收頭在板子上,與
MCU 的連接原理圖如 25.2.1 所示:
紅外遙控接收頭連接在 STM32 的 PB0(TIM3_CH3)上。硬件上不需要變動,只要程序將
TIM3_CH3 設計為輸入捕獲,然後將收到的脈衝信號解碼就可以了。開發板配套的紅外遙控器
外觀如圖 25.2.2 所示:
25.3 軟件設計
打開我們光盤的紅外遙控器實驗工程,可以看到我們添加了 remote.c 和 remote.h 兩個文件,
同時因為我們使用的是輸入捕獲,所以還用到庫函數 stm32f1xx_hal_tim.c 和 頭文 件
stm32f1xx_hal_tim.h。
打開 remote.c 文件,代碼如下:
TIM_HandleTypeDef TIM3_Handler; //定時器 3 句柄
//紅外遙控初始化
//設置 IO 以及 TIM3_CH3 的輸入捕獲
void Remote_Init(void)
{
TIM_IC_InitTypeDef TIM3_CH3Config;
TIM3_Handler.Instance=TIM3; //通用定時器 3
TIM3_Handler.Init.Prescaler=(72-1); //預分頻器,1M 的計數頻率,1us 加 1.
TIM3_Handler.Init.CounterMode=TIM_COUNTERMODE_UP; //向上計數器
TIM3_Handler.Init.Period=10000; //自動裝載值
TIM3_Handler.Init.ClockDivision=TIM_CLOCKDIVISION_DIV1;
HAL_TIM_IC_Init(&TIM3_Handler);
//初始化 TIM3 輸入捕獲參數
TIM3_CH3Config.ICPolarity=TIM_ICPOLARITY_RISING; //上升沿捕獲
TIM3_CH3Config.ICSelection=TIM_ICSELECTION_DIRECTTI;//映射到 TI3 上
TIM3_CH3Config.ICPrescaler=TIM_ICPSC_DIV1; //配置輸入分頻,不分頻
TIM3_CH3Config.ICFilter=0x03; //IC4F=0003 8 個定時器時鐘週期濾波
HAL_TIM_IC_ConfigChannel(&TIM3_Handler,&TIM3_CH3Config,
TIM_CHANNEL_3);//配置 TIM3 通道 3
HAL_TIM_IC_Start_IT(&TIM3_Handler,TIM_CHANNEL_3); //開始捕獲 TIM3 的通道 3
__HAL_TIM_ENABLE_IT(&TIM3_Handler,TIM_IT_UPDATE); //使能更新中斷
}
//定時器 3 底層驅動,時鐘使能,引腳配置
//此函數會被 HAL_TIM_IC_Init()調用
//htim:定時器 3 句柄
void HAL_TIM_IC_MspInit(TIM_HandleTypeDef *htim)
{
GPIO_InitTypeDef GPIO_Initure;
__HAL_RCC_TIM3_CLK_ENABLE(); //使能 TIM3 時鐘
__HAL_RCC_GPIOB_CLK_ENABLE(); //開啟 GPIOB 時鐘
GPIO_Initure.Pin=GPIO_PIN_0; //PB0
GPIO_Initure.Mode=GPIO_MODE_AF_INPUT; //複用輸入
GPIO_Initure.Pull=GPIO_PULLUP; //上拉
GPIO_Initure.Speed=GPIO_SPEED_HIGH; //高速
HAL_GPIO_Init(GPIOB,&GPIO_Initure);
HAL_NVIC_SetPriority(TIM3_IRQn,1,3);//設置中斷優先級,搶佔優先級 1,子優先級 3
HAL_NVIC_EnableIRQ(TIM3_IRQn); //開啟 ITM3 中斷
}
//遙控器接收狀態
//[7]:收到了引導碼標誌
//[6]:得到了一個按鍵的所有信息
//[5]:保留
//[4]:標記上升沿是否已經被捕獲
//[3:0]:溢出計時器
u8 RmtSta=0;
u16 Dval;
//下降沿時計數器的值
u32 RmtRec=0;
//紅外接收到的數據
u8 RmtCnt=0;
//按鍵按下的次數
//定時器 3 中端服務函數
void TIM3_IRQHandler(void)
{
HAL_TIM_IRQHandler(&TIM3_Handler);//定時器共用處理函數
}
//定時器更新(溢出)中斷回調函數
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
if(htim->Instance==TIM3)
{
if(RmtSta&0x80)//上次有數據被接收到了
{
RmtSta&=~0X10;
//取消上升沿已經被捕獲標記
if((RmtSta&0X0F)==0X00)RmtSta|=1<<6;
//標記已經完成一次按鍵的鍵值信息採集
if((RmtSta&0X0F)<14)RmtSta++;
else
{
RmtSta&=~(1<<7);//清空引導標識
RmtSta&=0XF0; //清空計數器
}
}
}
}
//定時器輸入捕獲中斷回調函數
void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)//捕獲中斷髮生時執行
{
if(htim->Instance==TIM3)
{
if(RDATA)//上升沿捕獲
{
TIM_RESET_CAPTUREPOLARITY(&TIM3_Handler,TIM_CHANNEL_3);
// 一 定 要 先 清 除 原 來 的 設
置!!
TIM_SET_CAPTUREPOLARITY(&TIM3_Handler,TIM_CHANNEL_3,
TIM_ICPOLARITY_FALLING);//CC1P=1 設置為下降沿捕獲
__HAL_TIM_SET_COUNTER(&TIM3_Handler,0); //清空定時器值
RmtSta|=0X10;
//標記上升沿已經被捕獲
}else //下降沿捕獲
{
Dval=HAL_TIM_ReadCapturedValue(&TIM3_Handler,
TIM_CHANNEL_3);//讀取 CCR2 也可以清 CC2IF 標誌位
TIM_RESET_CAPTUREPOLARITY(&TIM3_Handler,TIM_CHANNEL_3);
//一定要先清除原來的設置!!
TIM_SET_CAPTUREPOLARITY(&TIM3_Handler,TIM_CHANNEL_3,
TIM_ICPOLARITY_RISING);//配置 TIM3 通道 3 上升沿捕獲
if(RmtSta&0X10)//完成一次高電平捕獲
{
if(RmtSta&0X80)//接收到了引導碼
{
if(Dval>300&&Dval<800)
//560 為標準值,560us
{
RmtRec<<=1;
//左移一位.
RmtRec|=0;
//接收到 0
}else if(Dval>1400&&Dval<1800)
//1680 為標準值,1680us
{
RmtRec<<=1;
//左移一位.
RmtRec|=1;
//接收到 1
}else if(Dval>2200&&Dval<2600)
//得到按鍵鍵值增加的信息 2500 為標準值 2.5ms
{
RmtCnt++;
//按鍵次數增加 1 次
RmtSta&=0XF0;
//清空計時器
}
}else if(Dval>4200&&Dval<4700)
//4500 為標準值 4.5ms
{
RmtSta|=1<<7;
//標記成功接收到了引導碼
RmtCnt=0;
//清除按鍵次數計數器
}
}
RmtSta&=~(1<<4);
}
}
}
//處理紅外鍵盤
//返回值:
//
0,沒有任何按鍵按下
//其他,按下的按鍵鍵值.
u8 Remote_Scan(void)
{
u8 sta=0;
u8 t1,t2;
if(RmtSta&(1<<6))//得到一個按鍵的所有信息了
{
t1=RmtRec>>24;
//得到地址碼
t2=(RmtRec>>16)&0xff;
//得到地址反碼
if((t1==(u8)~t2)&&t1==REMOTE_ID)//檢驗遙控識別碼(ID)及地址
{
t1=RmtRec>>8;
t2=RmtRec;
if(t1==(u8)~t2)sta=t1;//鍵值正確
}
if((sta==0)||((RmtSta&0X80)==0))//按鍵數據錯誤/遙控已經沒有按下了
{
RmtSta&=~(1<<6);//清除接收到有效按鍵標識
RmtCnt=0;
//清除按鍵次數計數器
}
}
return sta;
}
該部分代碼包含 6 個函數,首先是 Remote_Init 函數,該函數和 MSP 回調函數
HAL_TIM_IC_MspInit 共同完成 TIM1 的時基參數初始化,TIM3_CH3 的輸入捕獲配置,IO 口
初始化配置,IO 口複用映射配置以及 NVIC 配置。具體內容和輸入捕獲實驗章節基本一致,不
同的是換了定時器而已。請參考輸入捕獲實驗章節講解。
溢出(更新)中斷回調函數為 HAL_TIM_PeriodElapsedCallback,在本例程裡面,該函數來
處理 TIM3 溢出,並表用於標記鍵值獲取完成,每一次紅外按鍵解碼,都必須通過定時器溢出
時間來標記完成。輸入捕獲中斷回調函數 HAL_TIM_IC_CaptureCallback,在本例程裡面,該函
數實現對紅外信號的高電平脈衝的捕獲,同時根據我們之前簡介的協議內容來解碼。這兩個中
斷處理回調函數用到幾個全局變量,用於輔助解碼,並存儲解碼結果。
這裡簡單介紹一下高電平捕獲思路:首先輸入捕獲設置的是捕獲上升沿,在上升沿捕獲到
以後,立即設置輸入捕獲模式為捕獲下降沿(以便捕獲本次高電平),然後,清零定時器的計
數器值,並標記捕獲到上升沿。當下降沿到來時,再次進入捕獲中斷服務函數,立即更改輸入
捕獲模式為捕獲上升沿(以便捕獲下一次高電平),然後處理此次捕獲到的高電平。
最後是 Remote_Scan 函數,該函用來掃描解碼結果,相當於我們的按鍵掃描,輸入捕獲解
碼的紅外數據,通過該函數傳送給其他程序。
接下來打開 remote.h,可以看到下面一行代碼:
#define REMOTE_ID 0
這裡的 REMOTE_ID 就是我們開發板配套的遙控器的識別碼,對於其他遙控器可能不一樣,
只要修改這個為你所使用的遙控器的一致就可以了。其他是一些函數的聲明,我們不做講解。
下面我們看看 main.c 裡面主函數代碼:
// 共陰數字數組
// 0,1,2,3,4,5,6,7,8,9,A,B,C,D,E,F, .,全滅
u8 smg_num[]={0xfc,0x60,0xda,0xf2,0x66,0xb6,0xbe,0xe0,0xfe,
0xf6,0xee,0x3e,0x9c,0x7a,0x9e,0x8e,0x01,0x00};
u8 key=0; //按鍵值
u8 num=0x00;//數值
u8 num1=0x00;//數值
u8 smg_wei=6;//數碼管位選
u8 smg_duan=0;//數碼管段選
u8 smg_flag=0;//數碼管顯示標誌 0:正常顯示 1:消除鬼影
u8 t=0;
int main(void)
{
HAL_Init(); //初始化 HAL 庫
Stm32_Clock_Init(RCC_PLL_MUL9); //設置時鐘,72M
delay_init(72); //初始化延時函數
uart_init(115200);
//串口初始化為 115200
LED_Init();
//初始化與 LED 連接的硬件接口
BEEP_Init(); //蜂鳴器初始化
LED_Init();
//初始化與 LED 連接的硬件接口
LED_SMG_Init(); //數碼管初始化
TIM4_Init(19,7199); //數碼管 2ms 定時顯示
Remote_Init();
//紅外接收初始化
while(1)
{
}
}
//定時器 4 中斷服務函數調用
void TIM4_IRQHandler(void)
{
if(__HAL_TIM_GET_IT_SOURCE(&TIM4_Handler, TIM_IT_UPDATE) !=RESET)
{
__HAL_TIM_CLEAR_IT(&TIM4_Handler, TIM_IT_UPDATE);
key = Remote_Scan();
if(key)
{
switch(key)
{
case 104:num1=0x00; num = smg_num[1]; BEEP=0;break; //按鍵'1'
case 152:num1=0x00;num=smg_num[2];BEEP=0;break; //按鍵'2'
case 176:num1=0x00;num=smg_num[3];BEEP=0;break; //按鍵'3'
case 48:num1=0x00;num=smg_num[4];BEEP=0;break; //按鍵'4'
case 24:num1=0x00;num=smg_num[5];BEEP=0;break; //按鍵'5'
case 122:num1=0x00;num=smg_num[6];BEEP=0;break; //按鍵'6'
case 16:num1=0x00;num=smg_num[7];BEEP=0;break; //按鍵'7'
case 56:num1=0x00;num=smg_num[8];BEEP=0;break; //按鍵'8'
case 90:num1=0x00;num=smg_num[9];BEEP=0;break; //按鍵'9'
case 66:num1=0x00;num=smg_num[0];BEEP=0;break; //按鍵'0'
case 82:num1=0x00;num=smg_num[17];BEEP=0;break; //按鍵'DELETE'
case 162:num1=smg_num[1];num=smg_num[0];BEEP=0; break;
//按鍵'POWER'
case 98:num1=smg_num[1];num=smg_num[1];BEEP=0; break;
//按鍵'UP'
case 226:num1=smg_num[1];num=smg_num[2];BEEP=0; break;
//按鍵'ALIENTEK'
case 34:num1=smg_num[1];num=smg_num[3];BEEP=0;break;
//按鍵'LEFT'
case 2:num1=smg_num[1];num=smg_num[4];BEEP=0; break;
//按鍵'PLAY'
case 194:num1=smg_num[1];num=smg_num[5];BEEP=0; break;
//按鍵'RIGHT'
case 224:num1=smg_num[1];num=smg_num[6];BEEP=0; break;
//按鍵'VOL-'
case 168:num1=smg_num[1];num=smg_num[7];BEEP=0; break;
//按鍵'DOWN'
case 144:num1=smg_num[1];num=smg_num[8];BEEP=0; break;
//按鍵'VOL+'
}
}else
{
BEEP=1;
}
if(smg_wei==6)//數碼管位
{
smg_duan = num1;
}
else if(smg_wei==7)
{
smg_duan = num;
}
if(smg_flag) LED_Write_Data(0x00,smg_wei);//消除鬼影(段碼不顯示)
else
LED_Write_Data(smg_duan,smg_wei);//正常顯示
LED_Refresh();//數碼管數據更新
smg_flag=!smg_flag;
if(smg_flag==0)//正常顯示才更新位碼
{
smg_wei++;
if(smg_wei==8) smg_wei=6;
}
t++;
if(t==250)//LED0 每 500MS 閃爍
{
t=0;
LED0=!LED0;
}
}
}
main 函數代碼比較簡單,主要是外設初始化,在定時器 4 中斷裡(沒有用回調函數形式),
通過 Remote_Scan 函數獲得紅外遙控輸入的數據(鍵值),然後以鍵值對應的數字,動態掃描
方式顯示在數碼管上面,同時數碼管做了消除鬼影的操作。
至此,我們的軟件設計部分就結束了。
25.4 下載驗證
在代碼編譯成功之後,我們通過下載代碼到 ALIENTEK NANO STM32F103 上,沒有按下
遙控器時數碼管無任何顯示,DS0 閃爍表示程序正在運行,此時我們通過遙控器按下不同的按
鍵,則可以看到數碼管上顯示了不同數字,同時蜂鳴器鳴叫一聲,顯示如圖 25.4.1 所示:
從圖可看到,數碼管顯示“5”,即代表我們按下紅外遙控器的是按鍵“5”。
閱讀更多 正點原子 的文章