全球即时看!STM32之红外遥控信号自学习实现
1 序言
很早前就想实现这个红外遥控自学习的这个实验,用于来自己控制房子里如空调等红外遥控设备的自动化,NEC的标准到具体的产品上可能就被厂家定义为不一样了,所以自学习就应该是接收到什么就发送什么,不用管内容是什么!
2 硬件实现原理
(资料图片仅供参考)
由上述原理图可知,当IE为高电平时发送红外光,为低电平时不发送红外光。
在NEC协议中,信息传输是基于38K载波,也就是说红外线是以载波的方式传递。
发送波形如下图所示:
NEC协议规定:
发送协议数据“0” = 发送载波560us + 不发送载波560us
发送协议数据“1” = 发送载波560us+ 不发送载波1680us
发送引导码 = 发送载波9000us + 不发送载波4500us
在红外接收端,如果接收到红外38K载波,则IR输出为低电平,如果不是载波包括固定低电平和固定高电平则输出高电平。在IR端接收的信号如下所示:
3 软件实现自学习
设计原理:
1、 根据接收波形记录电平和电平持续时间,以便于发送。
2、电平记录采用定时器捕获功能,从下降沿接收引导信号开始,每触发一次改变触发方式,从而使每个电平变化都能捕获到。
源码实现如下:
定时器捕获初始化设置(CubeMax软件自动配置生成):
void MX_TIM4_Init(void){TIM_ClockConfigTypeDef sClockSourceConfig = {0};TIM_MasterConfigTypeDef sMasterConfig = {0};TIM_IC_InitTypeDef sConfigIC = {0};htim4.Instance = TIM4;htim4.Init.Prescaler = 71;htim4.Init.CounterMode = TIM_COUNTERMODE_UP;htim4.Init.Period = 10000;htim4.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;htim4.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;if (HAL_TIM_Base_Init(&htim4) != HAL_OK){Error_Handler();}sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL;if (HAL_TIM_ConfigClockSource(&htim4, &sClockSourceConfig) != HAL_OK){Error_Handler();}if (HAL_TIM_IC_Init(&htim4) != HAL_OK){Error_Handler();}sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;if (HAL_TIMEx_MasterConfigSynchronization(&htim4, &sMasterConfig) != HAL_OK){Error_Handler();}sConfigIC.ICPolarity = TIM_INPUTCHANNELPOLARITY_RISING;sConfigIC.ICSelection = TIM_ICSELECTION_DIRECTTI;sConfigIC.ICPrescaler = TIM_ICPSC_DIV1;sConfigIC.ICFilter = 0;if (HAL_TIM_IC_ConfigChannel(&htim4, &sConfigIC, TIM_CHANNEL_4) != HAL_OK){Error_Handler();}}
定时器捕获中断回调处理:
void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim){if(htim->Channel == HAL_TIM_ACTIVE_CHANNEL_4) {if(TIM4->CCER & (TIM_CCER_CC4P)) //下降沿触发 { TIM4->CCER &= ~(TIM_CCER_CC4P); //切换 gu8BitVal = 1; }else //上升沿触发 { TIM4->CCER |= TIM_CCER_CC4P; //切换 gu8BitVal = 0; }if(gsInfrared.State == NONE_STATE) { gsInfrared.State = RECV_STATE; }else if(gsInfrared.State == RECV_STATE) { NowTimCnt = HAL_TIM_ReadCapturedValue(&htim4, TIM_CHANNEL_4); gsInfrared.KeepTime[gsInfrared.SampleCount] = Round(NowTimCnt); gsInfrared.BitValue[gsInfrared.SampleCount ++] = gu8BitVal; } TIM4->CNT = 0; }}
3、设置的定时器溢出时间为10ms,如果10毫秒内不再接收电平变化则默认接收结束,设置结束标志。
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim){if(htim == &htim4) {if(gsInfrared.State == RECV_STATE) { gsInfrared.State = END_STATE; } }}
至此,实现了红外遥控的学习功能,获得的记录数据为记录长度和电平信号数组与电平信号维持的时间数组。
4、发送实现
设置定时器输出38KPWM信号,在记录电平为0是输出记录时间的38K载波信号,如果为1则不输出载波,实现如下:
PWM生成设置(CubeMax自动配置生成):
void MX_TIM5_Init(void){ TIM_MasterConfigTypeDef sMasterConfig = {0}; TIM_OC_InitTypeDef sConfigOC = {0}; htim5.Instance = TIM5; htim5.Init.Prescaler = 0; htim5.Init.CounterMode = TIM_COUNTERMODE_UP; htim5.Init.Period = 1896; htim5.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1; htim5.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;if (HAL_TIM_PWM_Init(&htim5) != HAL_OK) { Error_Handler(); } sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET; sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;if (HAL_TIMEx_MasterConfigSynchronization(&htim5, &sMasterConfig) != HAL_OK) { Error_Handler(); } sConfigOC.OCMode = TIM_OCMODE_PWM1; sConfigOC.Pulse = 0; sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH; sConfigOC.OCFastMode = TIM_OCFAST_DISABLE;if (HAL_TIM_PWM_ConfigChannel(&htim5, &sConfigOC, TIM_CHANNEL_2) != HAL_OK) { Error_Handler(); } HAL_TIM_MspPostInit(&htim5);}
发送实现,注意点就是记录为0时发载波,记录为1时不发载波:
void InfraredSend(void){uint16_t Count = 0;while(Count < gsInfrared.SampleCount && gsInfrared.State == END_STATE) {if(gsInfrared.BitValue[Count] == 0) { TIM5->CCR2 = 948; delay_us(gsInfrared.KeepTime[Count]); TIM5->CCR2 = 0; }else { TIM5->CCR2 = 0; delay_us(gsInfrared.KeepTime[Count]); TIM5->CCR2 = 0; } Count ++; } delay_us(20000);}
往PWM比较寄存器设置948即为设置38KPWM波,也可在初始化时固定948,在此函数内启停定时器即可;
至此,自学习功能的全部思路已实现,通过对各个不同类型的红外遥控进行功能测试,均成功。
PS:查看很多资料发现很多红外解码未判断低电平时间,个人感觉不是很好,应该是不仅高电平时间得符合,低电平时间也应该符合。
自己写了一个小函数验证了一下,这个函数只是验证,未经仔细推敲,还可优化,仅供参考这一思想。
误差设计:±200us(拍脑袋值)
void InFraredDataDeal(void){uint32_t DataBuff = 0;uint16_t Count = 0;if(gsInfrared.State == END_STATE) { gsInfraredData.State = 0;do {switch(gsInfraredData.State) {case 0: //引导码识别 {if(gsInfrared.KeepTime[0] >= 8800 && gsInfrared.KeepTime[0] <= 9200 && gsInfrared.BitValue[0] == 0) {if(gsInfrared.KeepTime[1] >= 4300 && gsInfrared.KeepTime[1] <= 4700 && gsInfrared.BitValue[1] == 1) {if(gsInfrared.KeepTime[2] >= 360 && gsInfrared.KeepTime[2] <= 760 && gsInfrared.BitValue[2] == 0) { Count = 3; gsInfraredData.State = 1; } }else if(gsInfrared.KeepTime[1] >= 2300 && gsInfrared.KeepTime[1] <= 2700 && gsInfrared.BitValue[1] == 1) {if(gsInfrared.KeepTime[2] >= 360 && gsInfrared.KeepTime[2] <= 760 && gsInfrared.BitValue[2] == 0) { gsInfraredData.ReDataCount ++; gsInfraredData.State = 3; } }else { gsInfraredData.State = 3; } }else { gsInfraredData.State = 3; } }break;case 1: //数据解析 {if(gsInfrared.KeepTime[Count + 1] >= 360 && gsInfrared.KeepTime[Count + 1] <= 760 && gsInfrared.BitValue[Count + 1] == 0) {if(gsInfrared.BitValue[Count] == 1) {if(gsInfrared.KeepTime[Count] >= 1480 && gsInfrared.KeepTime[Count] <= 1880) { DataBuff <<= 1; DataBuff |= 1; }else if(gsInfrared.KeepTime[Count] >= 360 && gsInfrared.KeepTime[Count] <= 760 && gsInfrared.BitValue[Count] == 1) { DataBuff <<= 1; DataBuff |= 0; }else { gsInfraredData.State = 3; } } }if(Count < gsInfrared.SampleCount) { Count += 2; }else { gsInfraredData.State = 2; } }break;case 2: //成功解析 { gsInfraredData.Data = DataBuff; gsInfraredData.State = 3; }break;default: { gsInfraredData.State = 3; //解析结束 }break; } }while(gsInfraredData.State != 3); gsInfrared.State = NONE_STATE; gsInfrared.SampleCount = 0; }}
解析的话一般高位在前,所以左移,经测试帧格式为:引导码+用户码+用户码反码+命令码+命令码反码,能成功解析数据!解析的话根据具体协议,具体分析。
审核编辑:汤梓红