基于STM32F4的紅外控制
紅外控制原理
從紅外接收頭接收到的編碼可以看出,-幀編碼以引導碼開始,然後跟随32位的系統和資料碼。引導碼是以下降沿開始,按照輸入捕獲的原理,在引導碼的第一個下降沿到來時,将此時的計數值捕獲,第二個下降沿到來時,再次捕獲計數值,計算第二次 的計數值和第一次計數值的內插補點,如果內插補點時間在13.5ms附近(編碼時間上會有一定誤差),則認為是引導碼,例如時間差Δt在13ms-14ms之間,則認為是引導碼。同理,如果時間差Δt在1ms-1.3ms之間,則認為時編碼‘0’,如果時間差Δt在2ms-2.5ms之間,則認為時編碼‘1’。 還要注意的是,計數內插補點機關是個數,而兩次紅外編碼兩次下降沿時間間隔機關為ms,它們相比較需要機關統一,是以要将計數內插補點轉換為ms或者将ms轉換成內插補點個數,轉換時需要用到定時器驅動時鐘的周期,如果将ms轉換為個數,則隻需要将兩次下降沿時間差除以時鐘周期即可得到個數,如果将個數轉換為ms,則隻需将內插補點個數乘以時鐘周期即可得到ms的機關時間差。
接收端接收到碼除引導碼外有32位二進制碼組成,其中前16位為使用者識别碼,能差別不同的電器裝置,防止不同機種遙控碼互相幹擾。後16位為8位操作碼(功能碼)及其反碼,可以進行編碼的校驗。發射端發射的碼和接收端的碼波形是相反的。遙控器在按鍵按下後,周期性地發出同一種32位二進制碼,周期約為108ms。一組碼本身的持續時間随它包含的二進制“0”和“1”的個數不同而不同,大約在45-63ms之間, 下圖為接收波形圖。
按鍵擡起檢測
當一個鍵按下超過36ms,振蕩器使晶片激活,将發射一組108ms的編碼脈沖,這108ms發射代碼由一個引導碼(9ms) ,一個結果碼(4. 5ms) ,低8位位址碼(9ms-18ms) ,高8位位址碼(9ms-18ms) , 8位資料碼(9ms-18ms)和這8位資料的反碼(9ms-18ms) 組成。如果鍵按下超過108ms仍未松開,接下來發射的代碼(連發碼)将僅由起始碼(9ms) 和結束碼(2. 25ms)組成。
![](https://img.laitimes.com/img/_0nNw4CM6IyYiwiM6ICdiwiIyVGduV2YfNWawNyZuBnL3MDO2MzMzYTM4ADMxAjMwIzLc52YucWbp5GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.png)
定時器的捕獲
STM32F4除了定時5和6不能捕獲外,其他的定時器都可一進行捕獲,即捕獲脈沖波形的下降沿或者是上升沿。
下圖是定時器捕獲紅外編碼原理,其中定時器先進行(168-1)的分頻,時鐘頻率為84MHz,即2us定時器加1,定時器自動重裝為0xFFFF,即溢出時間約為65536*2us = 131ms,以使得定時器可以在1-2個定時器溢出周期獲得紅外編碼。
定時器捕獲共有兩種情況,第一種情況是在一個定時器溢出周期能夠采集完,即兩次捕獲的下降沿時間為t2 - t1,第二種情況是在第二個定時器溢出周期獲得紅外編碼,那麼他的捕獲下降沿的時間差為0xFFFF - t3 + t4。
定時器的配置和紅外解碼源碼
下面是定時器的的配置函數
Remote_Init
.采用定時器3的channel3進行捕獲,并對IO口進行複用,系統時鐘為84MHz,溢出時間為2us。
//紅外遙控初始化
//設定IO以及TIM3_CH3的輸入捕獲
void Remote_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
TIM_ICInitTypeDef TIM_ICInitStructure;
RCC_APB2PeriphClockCmd(RCC_AHB1Periph_GPIOB,ENABLE); //使能PORTB時鐘
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3,ENABLE); //TIM3 時鐘使能
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0; // PB0 輸入
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF; // 上拉輸入
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;
GPIO_Init(GPIOB, &GPIO_InitStructure);
GPIO_ResetBits(GPIOB, GPIO_Pin_0); //初始化GPIOB0
GPIO_PinAFConfig(GPIOB, GPIO_PinSource0, GPIO_AF_TIM3); //GPIOB0複用為TIM3
TIM_TimeBaseStructure.TIM_Period = 0xffff; //設定計數器自動重裝值 最大131ms溢出
TIM_TimeBaseStructure.TIM_Prescaler = (176 - 1); //預分頻器,2us加1.t = (arr+1)*(psc+1)/84000000
TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1; //設定時鐘分割:TDTS = Tck_tim
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; //TIM向上計數模式
TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure); //根據指定的參數初始化TIMx
TIM_ICInitStructure.TIM_Channel = TIM_Channel_3; // 選擇輸入端 IC3映射到TI3上
TIM_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Falling;
TIM_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI;
TIM_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1; //配置輸入分頻,不分頻
TIM_ICInitStructure.TIM_ICFilter = 0x00; //IC4F=0011 配置輸入濾波器 8個定時器時鐘周期濾波
TIM_ICInit(TIM3, &TIM_ICInitStructure); //初始化定時器輸入捕獲通道
TIM_ITConfig( TIM3,TIM_IT_Update|TIM_IT_CC3,ENABLE);//允許更新中斷 ,允許CC4IE捕獲中斷
TIM_Cmd(TIM3, ENABLE ); //使能定時器3
TIM_ClearFlag(TIM3, TIM_IT_CC3|TIM_IT_Update);
NVIC_InitStructure.NVIC_IRQChannel = TIM3_IRQn; //TIM3中斷
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1; //先占優先級1級
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3; //從優先級3級
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道被使能
NVIC_Init(&NVIC_InitStructure); //根據NVIC_InitStruct中指定的參數初始化外設NVIC寄存器
}
下面是定時器3的回調函數
TIM3_IRQHandler
.分别進入定時器3的溢出中斷和捕獲中斷對紅外遙控器進行解碼。
void TIM3_IRQHandler(void)
{
if(TIM_GetITStatus(TIM3, TIM_IT_CC3) != RESET)
{
TIM_ClearITPendingBit(TIM3, TIM_IT_CC3);
IR_Up = 0; // 每次進入捕獲中斷就清除溢出次數計數值
IR_ThisPulse = TIM_GetCapture3(TIM3); // 擷取通道3的捕獲值
if(IR_ThisPulse > IR_LastPulse) // 這次捕獲的值大于上次捕獲的值
{
IR_PulseSub = IR_ThisPulse - IR_LastPulse; // 得到時間差
}
else // 小于時要加上0xffff
{
IR_PulseSub = 0xffff - IR_LastPulse + IR_ThisPulse; // 得到時間差
}
IR_LastPulse = IR_ThisPulse; // 将本次得到值作為下一次編碼的前一個值
IR_PulseCnt++; // 解碼位數加1
if(IR_PulseCnt == 2)
{
if((IR_PulseSub > 6000) && (IR_PulseSub < 8000)) // 引導碼範圍,13.5ms 13.5ms/2us = 6750
{
IR_Sta = 0x01; // 标志位為1, 表示引導碼已經收到
}
else
IR_PulseCnt = 0; // 如果時間差沒有在引導碼範圍内,中斷次數清零,重新編碼
}
if((IR_PulseCnt > 2) && (IR_Sta == 0x01)) // 中斷次數大于2,并且引導碼已經解完
{
IR_Code <<= 1; // 存儲紅外編碼的寄存器向左移一位, 以便于存儲下一位放到最低位
if((IR_PulseSub > 450) && (IR_PulseSub < 700)) // 編碼‘0’範圍, 1.125ms 1.125/2us = 562.5
{
IR_Code |= 0x00; // 存儲0
}
else if((IR_PulseSub > 800) && (IR_PulseSub) < 1300) // 編碼‘1’範圍, 2.25ms 2.25/2us = 1125
{
IR_Code |= 0x01; // 存儲1
}
else // 如果不是0碼也不是1碼, 變量清零,重新解碼
{
IR_Sta = 0;
IR_Code = 0;
IR_PulseCnt = 0;
}
}
if(IR_PulseCnt == 34) // 如果解完碼, 第34次進入中斷正好解完一幀碼
{
IR_Key = IR_Code; // 存放解出的碼
IR_Sta = 0x02; // 進入連發碼的狀态
flag = 1; // 解完碼标志位1,進行編碼處理
}
if((IR_PulseCnt == 36) && (IR_Sta == 0x02)) // 2位引導碼,32位資料碼
{
IR_PulseCnt = 34; // 按鍵不松手便認為他在34
if((IR_PulseSub > 4500) && (IR_PulseSub < 6000)) // 進入連發狀态, 11.5ms/2us = 5525
{
LianfaCnt++;
IR_Key = IR_Code;
flag = 1;
}
}
}
if(TIM_GetITStatus(TIM3,TIM_IT_Update) != RESET)
{
TIM_ClearITPendingBit(TIM3,TIM_IT_Update);
if(RDATA == 1)
{
IR_Up++;
if(IR_Up >= 2)
{
IR_Code = 0;
IR_Sta = 0;
IR_PulseCnt = 0;
LianfaCnt = 0;
}
}
}
}
總結
解碼的關鍵是如何識别“0”和“1”,解碼方法也有很多種方法,第一種使用讀取GPIO端口的輸入電平的方法:從位的定義我們可以發現“0”“1”均以0. 56ms的低電平開始。不同的是高電平的寬度不同,“0”為0. 56ms,“1”為1. 68ms,是以必須根據高電平的寬度差別“0”和“1”。如果從0.56ms低電平過後,開始延時,0. 56ms以後,若讀到的電平為低,說明該位為“0”,反之則為“1”,為了可靠起見,延時必須比0. 56ms長些,但又不能超過1.12ms,否則如果該位為“0”,讀到的已是下一位的高電平,是以取(1. 12ms+0. 56ms )/2=0. 84ms最為可靠,一般取0. 84ms左右均可;第二種采用單片機的外部中斷功能:由于編碼0和編碼1的周期不同,隻要計算兩次下降沿之間的時間差,即可判斷是編碼0還是編碼1;第三種采用單片機的輸入捕獲功能,在紅外編碼的下降沿捕捉計數器的值,記錄兩次下降沿捕捉到的計數器的值,然後後- -次減前一次即可得到時間差,進而判斷是編碼0還是編碼1.
源碼如下:
連結: 紅外遙控.