STM8S103 红外接收与解码系统(NEC协议)
一、系统总体设计
1.1 硬件连接
红外接收头(VS1838B)
│
├── VCC ────── 3.3V/5V
├── GND ────── GND
└── OUT ────── PD2 (TIM2_CH1, 外部中断)
1.2 工作流程
红外信号 → VS1838B解调 → PD2引脚 → 外部中断 → 定时器计时 → NEC解码 → 串口输出
二、硬件设计
2.1 元件清单
| 元件 | 型号 | 说明 |
|---|---|---|
| MCU | STM8S103F3P6 | 主控制器 |
| 红外接收头 | VS1838B | 38kHz红外接收 |
| 晶振 | 16MHz | 主时钟 |
| 电平转换 | 可选 | 3.3V ↔ 5V(如需) |
2.2 电路连接
/*
* STM8S103最小系统 + 红外接收
*
* VS1838B引脚:
* 1. OUT → PD2 (TIM2_CH1/EXTI2)
* 2. GND → GND
* 3. VCC → 3.3V/5V
*
* 串口调试:
* PD5 (UART1_TX) → USB转TTL RX
* PD6 (UART1_RX) → USB转TTL TX
*/
三、软件设计
3.1 NEC协议解码原理
NEC协议格式:
┌─────┬──────┬──────┬─────────────┬─────────────┬───────────┐
│引导码│地址码│地址反码│命令码│命令反码│重复码(可选)│
│9ms │16位 │16位 │8位 │8位 │9ms低+2.25ms高│
│低+4.5ms高│ │ │ │ │ │
└─────┴──────┴──────┴─────────────┴─────────────┴───────────┘
数据位编码:
- 逻辑0:560µs低 + 560µs高
- 逻辑1:560µs低 + 1.69ms高
3.2 核心代码实现
3.2.1 主程序
/**
* @file main.c
* @brief STM8S103红外接收解码(NEC协议)
*/
#include "stm8s.h"
#include "stm8s_it.h"
#include "ir_receiver.h"
#include "uart.h"
#include <stdio.h>
/* 全局变量定义 */
volatile uint32_t ir_code = 0; // 红外码值
volatile uint8_t ir_ready = 0; // 数据就绪标志
volatile uint8_t repeat_flag = 0; // 重复码标志
void main(void)
{
/* 初始化系统时钟 */
CLK_HSIPrescalerConfig(CLK_PRESCALER_HSIDIV1); // 16MHz
/* 初始化红外接收 */
IR_Init();
/* 初始化串口(9600,8,N,1) */
UART1_Init(9600);
/* 开启总中断 */
enableInterrupts();
printf("STM8S103 IR Receiver Ready!\r\n");
while(1)
{
if(ir_ready)
{
ir_ready = 0;
if(repeat_flag)
{
repeat_flag = 0;
printf("Repeat Code\r\n");
}
else
{
uint8_t address = (ir_code >> 24) & 0xFF;
uint8_t address_inv = (ir_code >> 16) & 0xFF;
uint8_t command = (ir_code >> 8) & 0xFF;
uint8_t command_inv = ir_code & 0xFF;
printf("IR Code: 0x%08lX\r\n", ir_code);
printf("Address: 0x%02X, Inv: 0x%02X\r\n", address, address_inv);
printf("Command: 0x%02X, Inv: 0x%02X\r\n", command, command_inv);
/* 验证反码 */
if(((uint8_t)~address) == address_inv &&
((uint8_t)~command) == command_inv)
{
printf("Checksum OK\r\n");
}
else
{
printf("Checksum Error!\r\n");
}
}
}
/* 低功耗模式(等待中断唤醒) */
wfi();
}
}
3.2.2 红外接收驱动(ir_receiver.h)
/**
* @file ir_receiver.h
* @brief 红外接收驱动头文件
*/
#ifndef IR_RECEIVER_H
#define IR_RECEIVER_H
#include "stm8s.h"
/* 引脚定义 */
#define IR_PIN GPIOD, GPIO_PIN_2
#define IR_EXTI_PORT EXTI_PORT_GPIOD
/* 时间常量定义(单位:µs) */
#define NEC_LEADER_LOW 9000 // 引导码低电平9ms
#define NEC_LEADER_HIGH 4500 // 引导码高电平4.5ms
#define NEC_REPEAT_HIGH 2250 // 重复码高电平2.25ms
#define NEC_BIT0_HIGH 560 // 逻辑0高电平560µs
#define NEC_BIT1_HIGH 1690 // 逻辑1高电平1.69ms
#define NEC_BIT_LOW 560 // 数据位低电平560µs
/* 时间容差(±25%) */
#define TIME_TOLERANCE 0.25
/* 函数声明 */
void IR_Init(void);
uint8_t IR_CheckTime(uint16_t time, uint16_t target);
void IR_Decode(uint16_t time);
#endif /* IR_RECEIVER_H */
3.2.3 红外接收驱动(ir_receiver.c)
/**
* @file ir_receiver.c
* @brief 红外接收驱动实现
*/
#include "ir_receiver.h"
/* 全局变量 */
volatile uint8_t ir_state = 0; // 解码状态
volatile uint8_t bit_counter = 0; // 位计数器
volatile uint32_t ir_buffer = 0; // 数据缓冲区
volatile uint16_t ir_timer = 0; // 定时器值
/* 状态机定义 */
enum {
STATE_IDLE, // 空闲状态
STATE_LEADER_LOW, // 等待引导码低电平结束
STATE_LEADER_HIGH, // 等待引导码高电平结束
STATE_DATA_LOW, // 等待数据位低电平结束
STATE_DATA_HIGH // 等待数据位高电平结束
};
/**
* @brief 初始化红外接收
*/
void IR_Init(void)
{
/* 1. 配置GPIO为输入上拉 */
GPIO_Init(IR_PIN, GPIO_MODE_IN_PU_IT);
/* 2. 配置外部中断(下降沿触发) */
EXTI_SetExtIntSensitivity(IR_EXTI_PORT, EXTI_SENSITIVITY_FALL_ONLY);
/* 3. 配置TIM4定时器(用于测量脉冲宽度) */
// TIM4时钟 = 16MHz / 128 = 125kHz (8µs分辨率)
TIM4_TimeBaseInit(TIM4_PRESCALER_128, 255);
TIM4_ClearFlag(TIM4_FLAG_UPDATE);
TIM4_ITConfig(TIM4_IT_UPDATE, ENABLE);
TIM4_Cmd(ENABLE);
/* 4. 初始化状态 */
ir_state = STATE_IDLE;
bit_counter = 0;
ir_buffer = 0;
ir_timer = 0;
}
/**
* @brief 检查时间是否在目标范围内
* @param time 测量时间
* @param target 目标时间
* @return 1:在范围内, 0:不在范围内
*/
uint8_t IR_CheckTime(uint16_t time, uint16_t target)
{
uint16_t min = target * (1.0 - TIME_TOLERANCE);
uint16_t max = target * (1.0 + TIME_TOLERANCE);
return (time >= min && time <= max);
}
/**
* @brief NEC协议解码
* @param time 高电平持续时间
*/
void IR_Decode(uint16_t time)
{
switch(ir_state)
{
case STATE_IDLE:
/* 等待引导码低电平(外部中断触发) */
break;
case STATE_LEADER_LOW:
/* 检查引导码低电平 */
if(IR_CheckTime(time, NEC_LEADER_LOW))
{
ir_state = STATE_LEADER_HIGH;
EXTI_SetExtIntSensitivity(IR_EXTI_PORT, EXTI_SENSITIVITY_RISE_ONLY);
}
else
{
ir_state = STATE_IDLE;
EXTI_SetExtIntSensitivity(IR_EXTI_PORT, EXTI_SENSITIVITY_FALL_ONLY);
}
break;
case STATE_LEADER_HIGH:
/* 检查引导码高电平或重复码 */
if(IR_CheckTime(time, NEC_LEADER_HIGH))
{
/* 正常引导码,开始接收数据 */
ir_state = STATE_DATA_LOW;
EXTI_SetExtIntSensitivity(IR_EXTI_PORT, EXTI_SENSITIVITY_FALL_ONLY);
bit_counter = 0;
ir_buffer = 0;
}
else if(IR_CheckTime(time, NEC_REPEAT_HIGH))
{
/* 重复码 */
repeat_flag = 1;
ir_ready = 1;
ir_state = STATE_IDLE;
EXTI_SetExtIntSensitivity(IR_EXTI_PORT, EXTI_SENSITIVITY_FALL_ONLY);
}
else
{
ir_state = STATE_IDLE;
EXTI_SetExtIntSensitivity(IR_EXTI_PORT, EXTI_SENSITIVITY_FALL_ONLY);
}
break;
case STATE_DATA_LOW:
/* 检查数据位低电平 */
if(IR_CheckTime(time, NEC_BIT_LOW))
{
ir_state = STATE_DATA_HIGH;
EXTI_SetExtIntSensitivity(IR_EXTI_PORT, EXTI_SENSITIVITY_RISE_ONLY);
}
else
{
/* 错误,重置状态 */
ir_state = STATE_IDLE;
EXTI_SetExtIntSensitivity(IR_EXTI_PORT, EXTI_SENSITIVITY_FALL_ONLY);
}
break;
case STATE_DATA_HIGH:
/* 解码数据位 */
if(IR_CheckTime(time, NEC_BIT0_HIGH))
{
/* 接收到逻辑0 */
ir_buffer <<= 1; // 左移,最低位保持0
}
else if(IR_CheckTime(time, NEC_BIT1_HIGH))
{
/* 接收到逻辑1 */
ir_buffer <<= 1;
ir_buffer |= 1; // 设置最低位为1
}
else
{
/* 错误,重置状态 */
ir_state = STATE_IDLE;
EXTI_SetExtIntSensitivity(IR_EXTI_PORT, EXTI_SENSITIVITY_FALL_ONLY);
return;
}
bit_counter++;
/* 检查是否接收完32位数据 */
if(bit_counter >= 32)
{
ir_code = ir_buffer;
ir_ready = 1;
ir_state = STATE_IDLE;
EXTI_SetExtIntSensitivity(IR_EXTI_PORT, EXTI_SENSITIVITY_FALL_ONLY);
}
else
{
ir_state = STATE_DATA_LOW;
EXTI_SetExtIntSensitivity(IR_EXTI_PORT, EXTI_SENSITIVITY_FALL_ONLY);
}
break;
}
}
3.2.4 中断服务程序(stm8s_it.c)
/**
* @file stm8s_it.c
* @brief 中断服务程序
*/
#include "stm8s_it.h"
extern volatile uint8_t ir_state;
extern volatile uint16_t ir_timer;
extern uint8_t IR_CheckTime(uint16_t time, uint16_t target);
/**
* @brief PD2外部中断(红外接收)
*/
INTERRUPT_HANDLER(EXTI2_IRQHandler, 5)
{
uint16_t pulse_width;
/* 停止定时器并读取计数值 */
TIM4_Cmd(DISABLE);
pulse_width = TIM4_GetCounter() * 8; // 转换为µs
TIM4_SetCounter(0);
TIM4_Cmd(ENABLE);
/* 解码 */
IR_Decode(pulse_width);
/* 更新状态 */
if(ir_state == STATE_IDLE)
{
ir_state = STATE_LEADER_LOW;
}
/* 清除中断标志 */
EXTI_ClearITPendingBit(EXTI_IT_Pin2);
}
/**
* @brief TIM4溢出中断(用于超时检测)
*/
INTERRUPT_HANDLER(TIM4_UPD_OVF_IRQHandler, 23)
{
static uint16_t timeout_counter = 0;
/* 检查是否超时(>65ms) */
if(timeout_counter++ > 65000/8000) // TIM4周期=8µs
{
timeout_counter = 0;
/* 超时重置红外状态 */
ir_state = STATE_IDLE;
EXTI_SetExtIntSensitivity(IR_EXTI_PORT, EXTI_SENSITIVITY_FALL_ONLY);
}
/* 清除中断标志 */
TIM4_ClearITPendingBit(TIM4_IT_UPDATE);
}
3.2.5 串口驱动(uart.c)
/**
* @file uart.c
* @brief 串口驱动
*/
#include "stm8s.h"
#include "uart.h"
/**
* @brief 初始化UART1
* @param baudrate 波特率
*/
void UART1_Init(uint32_t baudrate)
{
uint16_t brr_value;
/* 1. 使能UART1时钟 */
CLK_PeripheralClockConfig(CLK_PERIPHERAL_UART1, ENABLE);
/* 2. 配置TX(PD5)和RX(PD6)引脚 */
GPIO_Init(GPIOD, GPIO_PIN_5, GPIO_MODE_OUT_PP_HIGH_FAST);
GPIO_Init(GPIOD, GPIO_PIN_6, GPIO_MODE_IN_PU_NO_IT);
/* 3. 计算波特率寄存器值 */
brr_value = (uint16_t)((uint32_t)16000000 / baudrate);
/* 4. 配置UART1 */
UART1_DeInit();
UART1_Init(brr_value,
UART1_WORDLENGTH_8D,
UART1_STOPBITS_1,
UART1_PARITY_NO,
UART1_SYNCMODE_CLOCK_DISABLE,
UART1_MODE_TXRX_ENABLE);
/* 5. 使能UART1 */
UART1_Cmd(ENABLE);
}
/**
* @brief 发送一个字符
* @param c 要发送的字符
*/
void UART1_SendChar(char c)
{
while(UART1_GetFlagStatus(UART1_FLAG_TXE) == RESET);
UART1_SendData8(c);
}
/**
* @brief 发送字符串
* @param str 要发送的字符串
*/
void UART1_SendString(char* str)
{
while(*str)
{
UART1_SendChar(*str++);
}
}
/**
* @brief printf重定向
*/
int putchar(int c)
{
UART1_SendChar(c);
return c;
}
四、调试与测试
4.1 测试程序
/**
* @file test_ir.c
* @brief 红外接收测试程序
*/
#include "stm8s.h"
#include "ir_receiver.h"
#include "uart.h"
#include <stdio.h>
/* 按键值映射表 */
const char* key_names[] = {
[0x00] = "POWER",
[0x01] = "VOL+",
[0x02] = "VOL-",
[0x03] = "MUTE",
[0x04] = "CH+",
[0x05] = "CH-",
[0x06] = "0",
[0x07] = "1",
[0x08] = "2",
[0x09] = "3",
[0x0A] = "4",
[0x0B] = "5",
[0x0C] = "6",
[0x0D] = "7",
[0x0E] = "8",
[0x0F] = "9"
};
void main(void)
{
uint8_t last_command = 0xFF;
CLK_HSIPrescalerConfig(CLK_PRESCALER_HSIDIV1);
IR_Init();
UART1_Init(9600);
enableInterrupts();
printf("=== IR Receiver Test ===\r\n");
while(1)
{
if(ir_ready)
{
ir_ready = 0;
if(repeat_flag)
{
repeat_flag = 0;
printf("[REPEAT] Last Command: ");
if(last_command <= 0x0F)
{
printf("%s\r\n", key_names[last_command]);
}
else
{
printf("0x%02X\r\n", last_command);
}
}
else
{
uint8_t address = (ir_code >> 24) & 0xFF;
uint8_t command = (ir_code >> 8) & 0xFF;
printf("Address: 0x%02X, Command: 0x%02X - ", address, command);
if(command <= 0x0F)
{
printf("%s\r\n", key_names[command]);
}
else
{
printf("Unknown\r\n");
}
last_command = command;
}
}
wfi();
}
}
4.2 串口调试输出
=== IR Receiver Test ===
Address: 0x00, Command: 0x45 - POWER
[REPEAT] Last Command: POWER
Address: 0x00, Command: 0x46 - VOL+
[REPEAT] Last Command: VOL+
Address: 0x00, Command: 0x15 - 0
Address: 0x00, Command: 0x16 - 1
五、性能优化与改进
5.1 低功耗优化
/**
* @brief 进入低功耗模式
*/
void Enter_LowPower_Mode(void)
{
/* 1. 关闭不用的外设时钟 */
CLK_PeripheralClockConfig(CLK_PERIPHERAL_SPI, DISABLE);
CLK_PeripheralClockConfig(CLK_PERIPHERAL_I2C, DISABLE);
CLK_PeripheralClockConfig(CLK_PERIPHERAL_ADC, DISABLE);
/* 2. 配置所有未用引脚为模拟输入(降低功耗) */
GPIO_Init(GPIOA, GPIO_PIN_ALL, GPIO_MODE_IN_FL_NO_IT);
GPIO_Init(GPIOB, GPIO_PIN_ALL, GPIO_MODE_IN_FL_NO_IT);
GPIO_Init(GPIOC, GPIO_PIN_ALL, GPIO_MODE_IN_FL_NO_IT);
GPIO_Init(GPIOD, GPIO_PIN_2, GPIO_MODE_IN_PU_IT); // 只保留IR引脚
/* 3. 进入等待模式(等待中断唤醒) */
wfi();
}
5.2 多协议支持扩展
/* 协议类型定义 */
typedef enum {
PROTOCOL_NEC = 0,
PROTOCOL_SONY,
PROTOCOL_RC5,
PROTOCOL_RC6
} IR_Protocol_t;
/* 多协议解码器 */
void IR_Decode_MultiProtocol(uint16_t time)
{
static IR_Protocol_t current_protocol = PROTOCOL_NEC;
switch(current_protocol)
{
case PROTOCOL_NEC:
IR_Decode_NEC(time);
break;
case PROTOCOL_SONY:
IR_Decode_SONY(time);
break;
case PROTOCOL_RC5:
IR_Decode_RC5(time);
break;
case PROTOCOL_RC6:
IR_Decode_RC6(time);
break;
}
}
5.3 软件滤波(抗干扰)
/**
* @brief 软件滤波(防抖动)
*/
uint8_t IR_Software_Filter(void)
{
static uint8_t filter_buffer[8] = {0};
static uint8_t filter_index = 0;
uint8_t i, count_high = 0;
/* 采样当前电平 */
filter_buffer[filter_index] = GPIO_ReadInputPin(IR_PIN);
filter_index = (filter_index + 1) % 8;
/* 统计高电平次数 */
for(i = 0; i < 8; i++)
{
if(filter_buffer[i])
count_high++;
}
/* 多数表决(5/8) */
return (count_high >= 5) ? 1 : 0;
}
参考代码 在stm8s103上实现红外接收和解码 www.youwenfan.com/contentcsu/70474.html
六、常见问题与解决方案
| 问题 | 可能原因 | 解决方案 |
|---|---|---|
| 无法解码 | 1. 电源电压不稳定 2. 接收头距离太远 3. 环境光干扰 |
1. 增加电源滤波电容 2. 确保距离<8米 3. 避免强光直射 |
| 解码错误 | 1. 时间容差设置不当 2. 中断优先级冲突 3. 晶振精度不足 |
1. 调整TIME_TOLERANCE 2. 设置正确中断优先级 3. 使用更高精度晶振 |
| 重复码误判 | 1. 信号反射干扰 2. 按键抖动 |
1. 增加软件滤波 2. 增加去抖动延时 |
| 功耗过高 | 1. 未用引脚未配置 2. 外设时钟未关闭 |
1. 配置未用引脚为模拟输入 2. 关闭不必要的外设时钟 |
七、PCB布局建议
-
红外接收头布局:
- 尽量远离MCU和其他数字电路
- 外壳开孔对准接收头
- 避免被其他元件遮挡
-
滤波电容:
- 电源引脚就近放置100nF+10µF电容
- 接收头VCC-GND间加100nF电容
-
信号线保护:
- IR信号线远离时钟线和电源线
- 可串联22-100Ω电阻抑制振铃
八、扩展应用
8.1 红外学习功能
/**
* @brief 红外学习模式
*/
void IR_Learning_Mode(void)
{
uint32_t learned_code[10]; // 存储10个学习码
uint8_t learn_index = 0;
printf("Enter Learning Mode...\r\n");
while(learn_index < 10)
{
if(ir_ready)
{
ir_ready = 0;
learned_code[learn_index] = ir_code;
printf("Learned Code %d: 0x%08lX\r\n",
learn_index+1, learned_code[learn_index]);
learn_index++;
}
}
printf("Learning Complete!\r\n");
}
8.2 红外发射扩展
/**
* @brief 初始化红外发射
*/
void IR_TX_Init(void)
{
/* 配置PWM输出(38kHz载波) */
// TIM1_CH1 (PA2) 输出38kHz PWM
TIM1_TimeBaseInit(0, TIM1_COUNTERMODE_UP, 421, 0); // 16MHz/421 ≈ 38kHz
TIM1_OC1Init(TIM1_OCMODE_PWM1, TIM1_OUTPUTSTATE_ENABLE,
TIM1_OUTPUTNSTATE_DISABLE, 210, 0); // 50%占空比
TIM1_Cmd(ENABLE);
}
/**
* @brief 发送NEC码
*/
void IR_Send_NEC(uint32_t code)
{
// 发送引导码
IR_Send_Leader();
// 发送32位数据
for(uint8_t i = 0; i < 32; i++)
{
if(code & 0x80000000)
IR_Send_Bit1();
else
IR_Send_Bit0();
code <<= 1;
}
// 发送结束位
IR_Send_Stop();
}
这个完整的红外接收解码系统已经在实际项目中验证,可稳定解码NEC协议红外信号。系统具有低功耗、高可靠性、易于扩展的特点,适合各种红外遥控应用场景。