红外遥控技术在家电控制领域已有数十年历史,但直到今天仍是大多数电视、空调等设备的标配控制方式。作为嵌入式开发者,掌握红外信号解码技术不仅能实现万能遥控器功能,更是理解硬件定时器应用的绝佳案例。本文将基于STM32的定时器输入捕获功能,带您从寄存器配置到协议解析,完整实现一个不依赖特定协议的红外信号学习系统。
红外遥控的本质是通过红外LED发射经过调制的光信号,接收端则通过光电二极管将光信号转换为电信号。典型红外遥控系统的工作频率为38kHz,这个载波频率既能有效避免环境光干扰,又能保证足够的传输距离。
关键硬件组件:
接收头输出信号特性:
c复制// 典型红外接收头电路连接
VS1838B_PIN ----> PA6 (TIM16_CH1)
|
+-- 10kΩ上拉电阻
|
+-- 0.1μF去耦电容
输入捕获是定时器的核心功能之一,用于精确测量脉冲信号的时序特征。在红外解码应用中,我们需要测量高低电平的持续时间,这需要正确配置定时器的多个参数。
以STM32F103的TIM16为例,关键配置参数包括:
| 参数 | 推荐值 | 作用说明 |
|---|---|---|
| Prescaler | 47 | 48分频,1MHz计数频率(1μs分辨率) |
| CounterMode | UP | 向上计数模式 |
| Period | 65535 | 最大计数值(65.535ms溢出) |
| ClockDivision | DIV1 | 无时钟分频 |
| AutoReloadPreload | DISABLE | 禁止自动重装载预装载 |
c复制TIM_HandleTypeDef htim16;
void TIM16_Init(void) {
htim16.Instance = TIM16;
htim16.Init.Prescaler = 47;
htim16.Init.CounterMode = TIM_COUNTERMODE_UP;
htim16.Init.Period = 65535;
htim16.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
htim16.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;
HAL_TIM_IC_Init(&htim16);
}
输入捕获需要特别关注极性设置和滤波参数:
c复制TIM_IC_InitTypeDef sConfigIC;
void TIM16_IC_Config(void) {
sConfigIC.ICPolarity = TIM_INPUTCHANNELPOLARITY_FALLING;
sConfigIC.ICSelection = TIM_ICSELECTION_DIRECTTI;
sConfigIC.ICPrescaler = TIM_ICPSC_DIV1;
sConfigIC.ICFilter = 3; // 8个时钟周期滤波
HAL_TIM_IC_ConfigChannel(&htim16, &sConfigIC, TIM_CHANNEL_1);
}
提示:滤波值(ICFilter)需要根据实际信号质量调整,值越大抗干扰能力越强,但会降低边沿检测精度。
红外信号采集的核心是在中断回调函数中处理边沿变化,记录时间戳,并动态切换捕获极性。
c复制volatile uint32_t captureBuffer[100];
volatile uint8_t captureIndex = 0;
void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim) {
static uint32_t lastCapture = 0;
uint32_t currentCapture = HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_1);
if(htim->Instance == TIM16) {
if(__HAL_TIM_GET_IT_SOURCE(htim, TIM_IT_CC1)) {
// 计算时间差并存储
uint32_t pulseWidth = (currentCapture - lastCapture) & 0xFFFF;
captureBuffer[captureIndex++] = pulseWidth;
// 切换捕获极性
if(htim->Channel == HAL_TIM_ACTIVE_CHANNEL_1) {
TIM_RESET_CAPTUREPOLARITY(htim, TIM_CHANNEL_1);
if(sConfigIC.ICPolarity == TIM_INPUTCHANNELPOLARITY_FALLING) {
TIM_SET_CAPTUREPOLARITY(htim, TIM_CHANNEL_1, TIM_INPUTCHANNELPOLARITY_RISING);
} else {
TIM_SET_CAPTUREPOLARITY(htim, TIM_CHANNEL_1, TIM_INPUTCHANNELPOLARITY_FALLING);
}
}
lastCapture = currentCapture;
// 防止缓冲区溢出
if(captureIndex >= 100) {
captureIndex = 0;
}
}
}
}
红外信号通常由以下几部分组成:
c复制#define NEC_LEADER_LOW_MIN 8500 // 8.5ms
#define NEC_LEADER_LOW_MAX 9500 // 9.5ms
#define NEC_LEADER_HIGH_MIN 4000 // 4.0ms
#define NEC_LEADER_HIGH_MAX 5000 // 5.0ms
uint8_t IR_DecodeNEC(uint32_t *buffer, uint8_t len, uint8_t *address, uint8_t *command) {
// 检查引导码
if(buffer[0] < NEC_LEADER_LOW_MIN || buffer[0] > NEC_LEADER_LOW_MAX) return 0;
if(buffer[1] < NEC_LEADER_HIGH_MIN || buffer[1] > NEC_LEADER_HIGH_MAX) return 0;
// 解析用户码和数据码
*address = 0;
*command = 0;
for(uint8_t i=0; i<16; i++) {
if(buffer[2+i*2] > 1000 && buffer[3+i*2] > 500) { // 逻辑1
if(i<8) *address |= (1<<(7-i));
else *command |= (1<<(15-i));
} else if(buffer[2+i*2] > 500 && buffer[3+i*2] > 1000) { // 逻辑0
// 位保持默认0值
} else {
return 0; // 无效数据
}
}
return 1;
}
学习到的红外信号需要能够重新发射,这需要配置定时器的PWM输出功能。
c复制TIM_HandleTypeDef htim3;
TIM_OC_InitTypeDef sConfigOC;
void TIM3_PWM_Init(void) {
htim3.Instance = TIM3;
htim3.Init.Prescaler = 0;
htim3.Init.CounterMode = TIM_COUNTERMODE_UP;
htim3.Init.Period = 1263; // 48MHz/38kHz ≈ 1263
htim3.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
HAL_TIM_PWM_Init(&htim3);
sConfigOC.OCMode = TIM_OCMODE_PWM1;
sConfigOC.Pulse = 632; // 50%占空比
sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH;
sConfigOC.OCFastMode = TIM_OCFAST_DISABLE;
HAL_TIM_PWM_ConfigChannel(&htim3, &sConfigOC, TIM_CHANNEL_4);
HAL_TIM_PWM_Start(&htim3, TIM_CHANNEL_4);
}
c复制void IR_SendSignal(uint32_t *buffer, uint8_t len) {
HAL_TIM_PWM_Stop(&htim3, TIM_CHANNEL_4); // 初始状态关闭
for(uint8_t i=0; i<len; i++) {
if(i%2 == 0) { // 低电平周期
HAL_TIM_PWM_Start(&htim3, TIM_CHANNEL_4);
} else { // 高电平周期
HAL_TIM_PWM_Stop(&htim3, TIM_CHANNEL_4);
}
delay_us(buffer[i]); // 精确延时
}
HAL_TIM_PWM_Stop(&htim3, TIM_CHANNEL_4); // 确保最后关闭
}
实际项目中,红外解码系统可能面临各种挑战。以下是几个关键优化点:
抗干扰优化:
精度提升技巧:
c复制// 中值滤波示例
uint32_t medianFilter(uint32_t *buffer, uint8_t size) {
uint32_t temp;
// 简单冒泡排序
for(uint8_t i=0; i<size-1; i++) {
for(uint8_t j=i+1; j<size; j++) {
if(buffer[j] < buffer[i]) {
temp = buffer[i];
buffer[i] = buffer[j];
buffer[j] = temp;
}
}
}
return buffer[size/2]; // 返回中值
}
在调试过程中,逻辑分析仪是最有力的工具。通过对比实际信号与解码结果,可以快速定位问题。常见问题包括: