1. 红外遥控实验概述
红外遥控作为嵌入式开发中的经典外设应用,在智能家居、工业控制等领域有着广泛的应用场景。普中STM32F1xx开发板搭载的红外接收头(如HS0038)配合标准库开发,是学习嵌入式外设开发的绝佳案例。
这个实验的核心在于理解红外遥控的通信协议(如NEC协议)和STM32的外部中断机制。红外接收头会将38kHz的载波信号解调为数字信号,通过外部中断引脚触发MCU进行解码。相比查询方式,中断机制能显著降低CPU占用率,这也是实际工程中的首选方案。
2. 硬件设计与电路分析
2.1 红外接收模块选型
普中开发板通常配备HS0038红外接收头,其关键参数包括:
- 载波频率:38kHz ±1kHz
- 接收角度:±45度
- 工作电压:2.7-5.5V
- 输出信号:TTL电平
注意:不同型号的红外接收头引脚定义可能不同,HS0038的引脚顺序为(从左到右):输出、GND、VCC。
2.2 电路连接原理
开发板上红外接收头的输出端通常连接到STM32的特定GPIO引脚(如PA8),该引脚需要配置为外部中断模式。典型电路连接如下:
code复制HS0038_OUT --→ PA8 (EXTI_Line8)
HS0038_VCC --→ 3.3V
HS0038_GND --→ GND
2.3 硬件抗干扰设计
红外接收易受环境光干扰,实际应用中建议:
- 在接收头VCC引脚添加0.1μF去耦电容
- 避免强光直射接收头
- 在软件中增加信号校验机制
3. 软件实现与标准库配置
3.1 工程环境搭建
使用STM32标准库需要包含以下关键文件:
c复制#include "stm32f10x.h"
#include "stm32f10x_gpio.h"
#include "stm32f10x_exti.h"
#include "stm32f10x_rcc.h"
#include "misc.h"
3.2 GPIO与外部中断配置
配置外部中断的完整流程:
c复制void EXTI_Config(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
EXTI_InitTypeDef EXTI_InitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
// 1. 使能GPIO和AFIO时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_AFIO, ENABLE);
// 2. 配置GPIO为输入模式
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; // 上拉输入
GPIO_Init(GPIOA, &GPIO_InitStructure);
// 3. 配置EXTI线路
GPIO_EXTILineConfig(GPIO_PortSourceGPIOA, GPIO_PinSource8);
EXTI_InitStructure.EXTI_Line = EXTI_Line8;
EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling; // 下降沿触发
EXTI_InitStructure.EXTI_LineCmd = ENABLE;
EXTI_Init(&EXTI_InitStructure);
// 4. 配置NVIC
NVIC_InitStructure.NVIC_IRQChannel = EXTI9_5_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x02;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x02;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
}
3.3 定时器配置与精准计时
红外解码需要精确计时,我们使用TIM2定时器:
c复制void TIM_Config(void)
{
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
TIM_TimeBaseStructure.TIM_Period = 0xFFFF;
TIM_TimeBaseStructure.TIM_Prescaler = 72 - 1; // 1MHz计数频率
TIM_TimeBaseStructure.TIM_ClockDivision = 0;
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure);
TIM_Cmd(TIM2, ENABLE);
}
4. NEC协议解码实现
4.1 NEC协议帧结构分析
NEC协议的一帧数据包含:
- 9ms的引导码(高电平)
- 4.5ms的空间(低电平)
- 8位地址码
- 8位地址反码
- 8位命令码
- 8位命令反码
- 560μs的结束位
4.2 中断服务程序实现
c复制volatile uint32_t irValue = 0;
volatile uint8_t irFlag = 0;
void EXTI9_5_IRQHandler(void)
{
static uint32_t lastTime = 0;
uint32_t currentTime = TIM_GetCounter(TIM2);
uint32_t duration = currentTime - lastTime;
if(EXTI_GetITStatus(EXTI_Line8) != RESET)
{
// 下降沿触发
if(duration > 8000 && duration < 10000) // 引导码9ms
{
irValue = 0;
}
else if(duration > 4000 && duration < 5000) // 重复码4.5ms
{
irFlag = 2; // 重复键标志
}
else if(duration > 500 && duration < 700) // 数据位560μs
{
irValue <<= 1;
if(duration > 1000 && duration < 1300) // 逻辑1的1.685ms
{
irValue |= 0x01;
}
}
lastTime = currentTime;
EXTI_ClearITPendingBit(EXTI_Line8);
}
}
4.3 解码状态机优化
为提高解码可靠性,可以采用状态机实现:
c复制typedef enum {
IR_IDLE,
IR_LEADER_CODE,
IR_REPEAT_CODE,
IR_DATA_HIGH,
IR_DATA_LOW
} IR_State;
IR_State irState = IR_IDLE;
uint8_t bitCount = 0;
void EXTI9_5_IRQHandler_StateMachine(void)
{
static uint32_t lastTime = 0;
uint32_t currentTime = TIM_GetCounter(TIM2);
uint32_t duration = currentTime - lastTime;
if(EXTI_GetITStatus(EXTI_Line8) != RESET)
{
switch(irState)
{
case IR_IDLE:
if(duration > 8000 && duration < 10000) {
irState = IR_LEADER_CODE;
irValue = 0;
bitCount = 0;
}
break;
case IR_LEADER_CODE:
if(duration > 4000 && duration < 5000) {
irState = IR_REPEAT_CODE;
}
else if(duration > 500 && duration < 700) {
irState = IR_DATA_HIGH;
}
break;
case IR_DATA_HIGH:
if(duration > 500 && duration < 700) {
irState = IR_DATA_LOW;
}
break;
case IR_DATA_LOW:
irValue <<= 1;
if(duration > 1000 && duration < 1300) {
irValue |= 0x01;
}
if(++bitCount >= 32) {
irFlag = 1;
irState = IR_IDLE;
} else {
irState = IR_DATA_HIGH;
}
break;
case IR_REPEAT_CODE:
irFlag = 2;
irState = IR_IDLE;
break;
}
lastTime = currentTime;
EXTI_ClearITPendingBit(EXTI_Line8);
}
}
5. 应用层设计与功能实现
5.1 按键值映射与处理
c复制#define IR_POWER 0x45
#define IR_VOL_UP 0x46
#define IR_VOL_DOWN 0x15
// 其他按键定义...
void IR_Process(void)
{
if(irFlag == 1) // 新按键
{
uint8_t command = (irValue >> 8) & 0xFF;
switch(command)
{
case IR_POWER:
// 电源键处理
break;
case IR_VOL_UP:
// 音量加处理
break;
// 其他按键处理...
}
irFlag = 0;
}
else if(irFlag == 2) // 重复键
{
// 处理重复按键(如长按音量键)
irFlag = 0;
}
}
5.2 红外学习功能实现
高级应用中可能需要实现红外学习功能:
c复制#define LEARN_MODE_ENABLE 1
#define LEARN_MODE_DISABLE 0
uint8_t learnMode = LEARN_MODE_DISABLE;
uint32_t learnedCode = 0;
void IR_Learn_Process(void)
{
if(learnMode == LEARN_MODE_ENABLE && irFlag == 1)
{
learnedCode = irValue;
learnMode = LEARN_MODE_DISABLE;
irFlag = 0;
// 提示学习成功
}
}
6. 调试技巧与常见问题
6.1 逻辑分析仪调试
使用Saleae逻辑分析仪捕获红外信号时:
- 采样率至少设置为1MHz
- 触发方式设置为下降沿触发
- 分析时注意时间测量精度
6.2 常见问题排查
-
无法触发中断
- 检查GPIO时钟是否使能
- 确认EXTI线路配置正确
- 验证NVIC优先级设置
-
解码错误率高
- 调整中断优先级,确保及时响应
- 优化时间判断阈值
- 增加软件滤波(如连续3次相同结果才确认)
-
通信距离短
- 检查红外接收头供电电压
- 确保发射端电池电量充足
- 避免强光干扰
6.3 性能优化建议
- 使用DMA+定时器捕获模式替代外部中断(适用于高精度需求)
- 对重复键实现加速度响应(长按时加快响应速度)
- 增加红外信号强度检测功能
7. 扩展应用与进阶开发
7.1 多协议兼容设计
除NEC协议外,可扩展支持RC5、Sony等协议:
c复制typedef enum {
IR_PROTOCOL_NEC,
IR_PROTOCOL_RC5,
IR_PROTOCOL_SONY
} IR_Protocol;
IR_Protocol currentProtocol = IR_PROTOCOL_NEC;
void IR_Decode_MultiProtocol(void)
{
switch(currentProtocol)
{
case IR_PROTOCOL_NEC:
// NEC解码
break;
case IR_PROTOCOL_RC5:
// RC5解码
break;
// 其他协议...
}
}
7.2 红外转RF网关设计
通过STM32+无线模块(如NRF24L01)实现红外信号转发:
- 接收红外信号并解码
- 通过无线模块发送键值
- 接收端根据键值生成相应红外信号
7.3 低功耗优化
对于电池供电设备:
- 使用外部中断唤醒MCU
- 在不使用时关闭红外接收头电源
- 采用间断扫描方式替代持续接收
