基于STM32 HAL库的通用红外网关设计与智能家居集成方案
1. 红外协议时序特征与容差定义
红外遥控通信依赖载波调制与脉冲宽度编码。以主流NEC编码规范为例,系统通常采用38kHz振荡频率进行载波生成。逻辑状态的判定通过发射时隙与空闲时隙的比例实现:逻辑零对应560μs导通与560μs截止,逻辑一则由560μs导通后跟随1680μs截止构成。单帧数据结构包含引导序列、地址域、地址反码、命令域及命令反码,校验机制依赖于按位取反的冗余设计。
在实际硬件采集过程中,由于晶体震荡偏差与温度漂移,时间窗需要预留合理容差。工程上常将关键阈值设定为±100μs区间,以兼容不同产线的制造公差:
// 协议时序阈值常量定义
#define IR_PROTO_LEADER_PULSE_US_MIN 8500
#define IR_PROTO_LEADER_PULSE_US_MAX 9500
#define IR_PROTO_LEADER_SPACE_US_MIN 4000
#define IR_PROTO_LEADER_SPACE_US_MAX 5000
#define IR_PROTO_BIT_PULSE_US_MIN 450
#define IR_PROTO_BIT_PULSE_US_MAX 650
#define IR_PROTO_BIT_ZERO_SPACE_US_MIN 450
#define IR_PROTO_BIT_ZERO_SPACE_US_MAX 650
#define IR_PROTO_BIT_ONE_SPACE_US_MIN 1550
#define IR_PROTO_BIT_ONE_SPACE_US_MAX 1800
部分厂商会引入扩展地址模式或连续重复码结构。在逆向解析阶段,建议通过示波器或逻辑分析仪提取原始波形,确认目标设备的精确参数边界。
2. 硬件选型与定时器捕获配置
主控单元选用STM32F103系列芯片,其内置的高级定时器具备独立的多通道输入捕获功能。配合TSOP系列解调模块(如HS0038B),可实现38kHz信号至TTL电平的转换。电路布局需满足去耦要求,电源引脚并联100nF陶瓷电容,地线走线保持最短路径以降低回路阻抗。
MCU时钟树配置为72MHz主频。定时器预分频器设置为71,使计数频率降至1MHz,达成微秒级时间分辨率。周期寄存器重载最大值设定为65535,覆盖完整引导码时长。
// 定时器实例初始化结构体
TIM_HandleTypeDef htimCaptureInst;
htimCaptureInst.Instance = TIM1;
htimCaptureInst.Init.Prescaler = 71U;
htimCaptureInst.Init.CounterMode = TIM_COUNTERMODE_UP;
htimCaptureInst.Init.Period = 65535U;
htimCaptureInst.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
htimCaptureInst.Init.RepetitionCounter = 0U;
htimCaptureInst.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_ENABLE;
输入捕获通道启用数字滤波器以抑制高频毛刺。触发极性初始设为上升沿,滤波系数配置为8级采样。
// 捕获通道参数配置
TIM_IC_InitTypeDef sConfigIC = {0};
sConfigIC.ICFilter = 8U;
sConfigIC.ICPolarity = TIM_INPUTCHANNELPOLARITY_RISING;
sConfigIC.ICSelection = TIM_ICSELECTION_DIRECTTI;
sConfigIC.ICPrescaler = TIM_ICPSC_DIV1;
HAL_TIM_IC_ConfigChannel(&htimCaptureInst, &sConfigIC, TIM_CHANNEL_1);
模块供电建议使用独立LDO稳压源,物理安装位置避开强光直射区域,防止环境光造成解调误触发。
3. 中断驱动与数据缓冲机制
高速脉冲流处理需避免在中断服务例程中执行耗时操作。采用双次捕获交替切换极性的策略,计算相邻边沿的时间差即为脉宽值。为避免共享变量竞争,引入轻量级环形队列缓存原始时序数据。
// 脉冲捕获上下文
static volatile IrCaptureState_t capState = CAP_IDLE;
static uint32_t lastEdgeTime = 0U;
void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)
{
if (htim->Channel != HAL_TIM_ACTIVE_CHANNEL_1) return;
uint32_t currentTick = HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_1);
uint32_t delta = (currentTick >= lastEdgeTime) ?
(currentTick - lastEdgeTime) :
(0xFFFF - lastEdgeTime + currentTick + 1U);
lastEdgeTime = currentTick;
switch (capState)
{
case CAP_IDLE:
// 忽略异常短脉冲
if (delta < IR_PROTO_LEADER_PULSE_US_MIN) break;
capState = CAP_FIRST_EDGE;
__HAL_TIM_SET_CAPTUREPOLARITY(htim, TIM_CHANNEL_1, TIM_INPUTCHANNELPOLARITY_FALLING);
break;
case CAP_FIRST_EDGE:
ir_ringbuf_push(delta);
capState = CAP_SECOND_EDGE;
__HAL_TIM_SET_CAPTUREPOLARITY(htim, TIM_CHANNEL_1, TIM_INPUTCHANNELPOLARITY_RISING);
break;
case CAP_SECOND_EDGE:
ir_ringbuf_push(delta);
capState = CAP_IDLE;
// 唤醒解码线程或置位标志
break;
default: break;
}
}
环形队列采用无锁单生产者设计,适用于中断环境写入与应用程序读取:
#define IR_RING_SIZE 64
static uint32_t ir_buffer[IR_RING_SIZE];
static volatile uint16_t buf_head = 0U;
static volatile uint16_t buf_tail = 0U;
static volatile uint8_t buf_count = 0U;
bool ir_ringbuf_push(uint32_t val)
{
if (buf_count == IR_RING_SIZE) return false;
ir_buffer[buf_head++] = val;
if (buf_head == IR_RING_SIZE) buf_head = 0U;
buf_count++;
return true;
}
bool ir_ringbuf_pop(uint32_t *val)
{
if (buf_count == 0U) return false;
*val = ir_buffer[buf_tail++];
if (buf_tail == IR_RING_SIZE) buf_tail = 0U;
buf_count--;
return true;
}
解码前可开启串口追踪端口,监控队列深度与丢包率,便于后续容差参数微调。
4. 多协议自适应解码架构
消费电子设备涵盖多种编码标准。系统通过引导头时序特征进行分类,并抽象统一的数据结果结构。解码核心支持按需加载算法模块。
typedef enum { PROTO_UNKNOWN, PROTO_NEC, PROTO_RC5, PROTO_SONY } IrProtocol_t;
typedef struct {
IrProtocol_t type;
uint32_t addr;
uint32_t cmd;
bool is_repeat;
uint8_t bit_depth;
} IrDecodeResult_t;
IrProtocol_t ir_detect_from_leader(uint32_t pulse_us, uint32_t space_us)
{
if (pulse_us >= IR_PROTO_LEADER_PULSE_US_MIN &&
pulse_us <= IR_PROTO_LEADER_PULSE_US_MAX &&
space_us >= IR_PROTO_LEADER_SPACE_US_MIN &&
space_us <= IR_PROTO_LEADER_SPACE_US_MAX) {
return PROTO_NEC;
}
// 其他协议特征匹配逻辑...
return PROTO_UNKNOWN;
}
各协议解析函数遵循相同签名,便于策略路由:
bool parse_nec_sequence(const uint32_t *pulses, uint16_t count, IrDecodeResult_t *out);
bool parse_rc5_sequence(const uint32_t *pulses, uint16_t count, IrDecodeResult_t *out);
bool parse_sony_sequence(const uint32_t *pulses, uint16_t count, IrDecodeResult_t *out);
针对非标改造设备,可接入特征学习子程序。该模块提取脉宽分布统计量,动态生成匹配模板并持久化至Flash扇区,降低硬编码依赖。
5. 网络桥接与云端通信实现
本地解码结果需映射至IP网络层。MQTT作为轻量级发布订阅协议,适合资源受限的边缘节点。客户端封装负责连接管理、主题路由与JSON序列化。
typedef struct {
char dev_sn[32];
const char *broker_host;
uint16_t broker_port;
const char *sub_topic;
const char *pub_topic;
bool link_established;
} MqttBridgeCtx_t;
void on_mqtt_message(const char *topic, const uint8_t *payload, size_t length)
{
IrDecodeResult_t target_cfg;
if (deserialize_command_payload(payload, length, &target_cfg)) {
trigger_ir_transmitter(target_cfg.type, target_cfg.cmd);
}
}
bool push_device_event(const IrDecodeResult_t *evt)
{
char json_buf[128] = {0};
sprintf(json_buf, "{\"addr\":%lu,\"cmd\":%lu,\"proto\":\"%s\"}",
evt->addr, evt->cmd, protocol_name(evt->type));
return mqtt_publish(pub_topic, json_buf, strlen(json_buf), 1, 0) == 0;
}
云端流处理平台可通过可视化编排引擎对接。以下为Node-RED工作流的核心逻辑片段,用于指令转发:
// 接收MQTT入站消息 -> 路由解析 -> 下发红外动作
msg.payload.device_id = global.get('gw_sn');
msg.topic = 'devices/' + msg.payload.dev_sn + '/action';
return msg;
传输层建议启用QoS Level 1保障至少一次投递,结合本地事件队列实现断网续传。
6. 嵌入式低功耗调度策略
电池型终端需优化静态与动态功耗。系统维护多层级运行状态,依据任务负载切换外设时钟与内核频率。红外解调依赖外部中断维持监听能力,主核可进入低功耗睡眠。
typedef enum {
POWER_ACTIVE, POWER_IDLE, POWER_STOP, POWER_STANDBY
} PwrLevel_t;
void set_power_target(PwrLevel_t level)
{
switch (level)
{
case POWER_ACTIVE:
SystemCoreClockUpdate();
__HAL_RCC_GPIOA_CLK_ENABLE();
__HAL_RCC_TIM1_CLK_ENABLE();
break;
case POWER_IDLE:
__HAL_RCC_GPIOA_CLK_DISABLE();
HAL_PWR_EnterSLEEPMode(PWR_MAINREGULATOR_ON, PWR_SLEEPENTRY_WFI);
break;
case POWER_STOP:
HAL_PWR_EnterSTOPMode(PWR_LOWPOWERREGULATOR_ON, PWR_STOPENTRY_WFI);
break;
case POWER_STANDBY:
HAL_PWR_EnterSTANDBYMode();
break;
default: break;
}
}
中断唤醒处理器恢复时钟树,清除挂起标志并触发信号处理流水线:
void EXTI0_IRQHandler(void)
{
if (__HAL_GPIO_EXTI_GET_IT(GPIO_PIN_0) != RESET)
{
__HAL_GPIO_EXTI_CLEAR_IT(GPIO_PIN_0);
set_power_target(POWER_ACTIVE);
isr_start_demodulation();
}
}
电源监测模块定期采集ADC数值,换算为毫安时容量估算值,低于阈值时上报低电量告警事件。
BatteryInfo_t query_battery_state(void)
{
BatteryInfo_t stat;
stat.raw_mv = adc_read_vbat_channel();
stat.percentage = map_voltage_to_soc(stat.raw_mv);
stat.insert_flag = check_charge_det_pin();
return stat;
}
7. 典型设备状态同步与自动化逻辑
红外链路属于开环单向传输,执行机构无法回传实际工况。网关侧需维护虚拟状态机映射物理设备属性。以空调控制器为例,需跟踪开关机、温区设定、风向等离散参量。
typedef struct {
bool power_on;
uint8_t target_temp_c;
uint8_t fan_level;
uint8_t swing_dir;
EnvProfile_t current_mode;
} ApplianceProxy_t;
void sync_proxy_state(const IrDecodeResult_t *raw_cmd, ApplianceProxy_t *proxy)
{
switch (extract_action_code(raw_cmd))
{
case ACT_POWER_TOGGLE:
proxy->power_on = !proxy->power_on;
break;
case ACT_TEMP_INC:
if (proxy->target_temp_c < 30) proxy->target_temp_c++;
break;
case ACT_MODE_COOL:
proxy->current_mode = MODE_COOLING;
break;
default: break;
}
}
自动化调度器整合时间基准与环境传感数据,动态调整控制策略:
void run_schedule_engine(ApplianceProxy_t *proxy)
{
TimeStamp cur = rtos_get_tick_time();
EnvironmentalReadings env = sensor_fetch_all();
if (cur.hour >= 23 || cur.hour < 5) {
proxy->target_temp_c = adjust_for_sleep(env.temp, env.humidity);
} else if (!location_tracked()) {
proxy->power_on = false;
} else {
apply_comfort_curve(proxy, env, cur);
}
}
部署阶段推荐采用插件化架构,将信号收发、协议解析、网络桥接分离为独立组件。建立开源时序库共享各型号编码表,可大幅缩短新设备适配周期。整体方案通过边缘侧协议转换与云端场景编排,实现非联网老设备的低成本智能化升级。