简介:本资源是2013年全国大学生电子设计竞赛(F题)——红外光通信装置的STM32系统板完整开发套件,面向嵌入式初学者、电赛备赛学生及STM32进阶开发者,聚焦红外通信原理实现与工程落地。包内含316个文件,以75个.h头文件和71个.c源码为主,覆盖STM32F10x标准外设库驱动、红外PWM发射模块、NEC协议解码逻辑、EXTI中断接收处理等核心代码;辅以36个.crff编译中间文件、11个.pdf技术文档及KEIL工程配置文件(.uvproj/.uvopt)、调试脚本(.bat)和清除工具,结构完整,便于直接编译调试与逆向学习。资源包大小38.22MB,目录组织清晰,含多版本备份(.bak)与工程环境适配文件,显著降低移植门槛。目前已有309人下载学习,提供从协议解析、硬件驱动到抗干扰滤波的全链路实现方案,是深入理解STM32在光通信场景中编码/解码、时序控制与外设协同的优质实践范例。
1. 这不是遥控器,而是国赛级红外光通信装置:用STM32F103C8T6实现双向、抗干扰、可校验的串行光链路
2013年全国大学生电子设计竞赛(F题)要求参赛队在无物理连线条件下,完成两套独立系统间10米距离、5kbps以上速率、误码率低于10⁻⁴的可靠数据传输——题目明确限定使用红外LED与光电二极管构建光通信链路,禁用蓝牙/WiFi/射频模块。这套“STM32系统板全套”压缩包,正是当年获奖队伍交付的完整工程:它不依赖外部库或黑盒驱动,全部逻辑基于寄存器级配置与状态机调度;不靠环境光滤波片,而用软件时序+曼彻斯特编码+循环冗余校验(CRC-16)三重保障抗干扰能力;更关键的是,它把“红外通信”从遥控协议(如NEC)升维为可编程、可调试、可组网的嵌入式通信子系统。适合正在备赛蓝桥杯嵌入式组、智能车竞赛或准备毕业设计的学生——你不需要懂光学原理,但必须清楚TIM定时器如何生成40kHz载波、GPIO如何模拟开漏输出、USART如何被复用为红外解调输入源。本文将带你从零复现这套装置的核心通信链路,重点落在可验证、可修改、可移植到STM32F103系列任意型号的实操路径上。
2. 为什么选STM32F103C8T6做红外光通信主控?从国赛约束反推硬件选型逻辑
2.1 国赛F题硬性约束倒逼出的芯片特性匹配表
国赛题目隐含三项不可妥协的技术边界:
- 功耗与供电限制:装置需由单节锂电池(3.0–3.6V)供电,峰值电流≤200mA;
- 实时性要求:接收端必须在200μs内完成一帧数据的边沿捕获与解码;
- 资源最小化:PCB面积≤5cm×5cm,禁止外挂FPGA或专用编解码芯片。
STM32F103C8T6在此场景下成为事实标准,其匹配逻辑如下表所示(对比同价位常见替代方案):
| 特性 | STM32F103C8T6 | STC15W4K32S4(国产8051) | ESP32-WROOM-32 | 匹配国赛需求原因 |
|---|---|---|---|---|
| 主频/指令周期 | 72MHz(Cortex-M3) | 1T模式下最高20MHz | 240MHz(双核) | 72MHz足以在中断中完成曼彻斯特解码(需≤1.2μs/bit),而8051需多周期指令拖慢实时性;ESP32功耗超标且体积过大 |
| 定时器精度 | 4个16位通用定时器,支持PWM互补输出 | 2个16位定时器,无死区控制 | 2个64位定时器,但无红外专用外设 | TIM2/TIM3可精确生成38–40kHz载波(误差<±0.5%),并同步控制LED驱动MOSFET开关时序 |
| GPIO驱动能力 | 推挽/开漏模式可选,最大20mA灌电流 | 10mA灌电流,需外置三极管 | 12mA,但IO电压兼容性差 | 直接驱动红外LED(典型IF=100mA)需外置MOSFET,而开漏模式可无缝对接IR LED阳极共接方案 |
| 低功耗模式 | Stop模式电流≤10μA(RTC运行) | Idle模式电流≈500μA | Modem-sleep模式≈1.5mA | 满足电池供电下待机72小时要求,Stop模式下仍能通过EXTI唤醒解码 |
| 开发工具链 | Keil MDK-ARM v5.36+ST-Link V2 | Keil C51 v9.56+USB转串口 | ESP-IDF v4.4+JTAG | 国赛指定Keil环境,且ST-Link烧录速度>100KB/s,避免调试时反复插拔 |
提示:不要用STM32F407或H7系列替代——它们虽性能更强,但Flash容量(512KB+)远超题目要求,且HAL库初始化耗时长,导致接收中断响应延迟>300μs,直接导致曼彻斯特码元丢失。
2.2 红外通信链路的三层架构:物理层、编码层、协议层必须解耦设计
国赛装置的可靠性源于严格分层:
- 物理层:由红外LED(TSAL6100)、PIN光电二极管(TEMPTD01)和运放(LM358)构成模拟前端,输出TTL电平信号送入MCU;
- 编码层:采用曼彻斯特编码(而非NRZ),确保每个bit周期内必有电平跳变,解决红外接收头易受环境光干扰导致的直流漂移问题;
- 协议层:定义帧结构为
[SYNC:8bit][ADDR:4bit][CMD:4bit][DATA:32bit][CRC:16bit],其中SYNC固定为0x55(二进制01010101),天然满足曼彻斯特编码的跳变要求。
这种分层使调试可逐级隔离:若通信失败,先用示波器测物理层输出是否为干净方波(载波频率40kHz±1%),再抓取TIM捕获通道波形验证编码层跳变间隔(200μs/bit),最后用逻辑分析仪解析协议层CRC是否匹配。以下代码展示如何用TIM2生成40kHz载波,并通过GPIO模拟开漏输出控制LED:
// 初始化TIM2生成40kHz PWM(占空比50%) void TIM2_PWM_Init(void) { RCC->APB1ENR |= RCC_APB1ENR_TIM2EN; // 使能TIM2时钟 TIM2->PSC = 72 - 1; // 预分频:72MHz / 72 = 1MHz TIM2->ARR = 25 - 1; // 自动重装载:1MHz / 25 = 40kHz TIM2->CCMR1 |= TIM_CCMR1_OC1M_1 | TIM_CCMR1_OC1M_2; // PWM模式1 TIM2->CCER |= TIM_CCER_CC1E; // 使能CH1输出 TIM2->CR1 |= TIM_CR1_CEN; // 启动定时器 } // GPIOA_Pin0配置为开漏输出(驱动IR LED阳极) void IR_LED_GPIO_Init(void) { RCC->APB2ENR |= RCC_APB2ENR_IOPAEN; // 使能GPIOA时钟 GPIOA->CRL &= ~(0xF << (0*4)); // 清除PA0模式位 GPIOA->CRL |= (0x1 << (0*4)); // PA0设为开漏输出(推挽会短路IR LED) GPIOA->BSRR = GPIO_BSRR_BR0; // 初始关闭LED(BSRR写1清0) }2.2.1 曼彻斯特编码的寄存器级实现:避免库函数引入不可控延迟
国赛要求所有编码逻辑在中断中完成,因此必须绕过标准库的printf或memcpy。以下函数将32位数据data按曼彻斯特规则编码为64位数组manchester_buf(每bit扩展为2bit):
// 输入:32位数据;输出:64字节曼彻斯特码流(LSB在前) void manchester_encode(uint32_t data, uint8_t *manchester_buf) { uint8_t bit_pos = 0; for (int i = 0; i < 32; i++) { uint8_t bit = (data >> i) & 0x01; if (bit) { manchester_buf[bit_pos++] = 0x01; // '1' → 高-低跳变 → 0b01 manchester_buf[bit_pos++] = 0x00; } else { manchester_buf[bit_pos++] = 0x00; // '0' → 低-高跳变 → 0b10 manchester_buf[bit_pos++] = 0x01; } } }注意:此实现将
bit 0(最低位)作为第一个发送位,符合国赛参考代码的时序约定。若使用其他MCU平台,需确认其SPI/USART移位方向是否一致,否则会导致整帧数据反转。
3. 用TIM输入捕获+状态机实现抗干扰红外解码:从原始脉冲到有效帧的全链路解析
3.1 红外接收端的信号特征与TIM捕获配置要点
光电二极管经运放整形后,输出为TTL电平脉冲序列。其关键特征是:
- 载波抑制:接收头内部已滤除40kHz载波,输出为包络信号(即原始曼彻斯特波形);
- 脉宽抖动:因环境光干扰,同一bit的高/低电平持续时间偏差可达±15%;
- 起始同步头:每帧前有8ms低电平(SYNC引导),后接
0x55对应的曼彻斯特码(16个跳变)。
因此,TIM输入捕获必须配置为双边沿触发+滤波,以消除毛刺。以下代码配置TIM3通道2(PA7)为捕获引脚:
// TIM3_CH2 (PA7) 输入捕获初始化 void TIM3_IC_Init(void) { RCC->APB1ENR |= RCC_APB1ENR_TIM3EN; RCC->APB2ENR |= RCC_APB2ENR_IOPAEN; // PA7复用为TIM3_CH2 GPIOA->CRL &= ~(0xF << (7*4)); GPIOA->CRL |= (0x2 << (7*4)); // 复用推挽 // 捕获配置:上升下降沿都触发,IC2F=0b1000(8采样周期滤波) TIM3->CCMR2 |= TIM_CCMR2_IC2F_3 | TIM_CCMR2_IC2F_1; TIM3->CCMR2 |= TIM_CCMR2_CC2S_0; // 通道2映射到TI2(PA7) TIM3->CCER |= TIM_CCER_CC2E | TIM_CCER_CC2P | TIM_CCER_CC2NP; // 上升下降沿捕获 // 中断使能 TIM3->DIER |= TIM_DIER_CC2IE; NVIC_EnableIRQ(TIM3_IRQn); TIM3->CR1 |= TIM_CR1_CEN; }3.1.1 基于捕获时间差的状态机解码算法
单纯记录电平跳变时间无法直接还原数据,需构建有限状态机(FSM)识别曼彻斯特码元。核心逻辑:
- State_IDLE:等待8ms低电平(SYNC起始);
- State_SYNC:检测后续16个跳变是否符合
0x55(即交替的0101...); - State_DATA:对每个200μs周期内的跳变方向判别bit值(上升沿→0,下降沿→1);
- State_CRC:计算接收到的32bit数据的CRC-16并与帧尾比对。
以下为关键状态转移代码(精简版):
volatile uint8_t ir_state = STATE_IDLE; volatile uint32_t capture_time[64]; // 存储64次捕获的时间戳 volatile uint8_t cap_idx = 0; void TIM3_IRQHandler(void) { if (TIM3->SR & TIM_SR_CC2IF) { uint16_t now = TIM3->CCR2; if (cap_idx < 64) { capture_time[cap_idx++] = now; } TIM3->SR &= ~TIM_SR_CC2IF; // 状态机处理(此处仅示意State_SYNC判断) if (ir_state == STATE_SYNC && cap_idx == 16) { bool sync_ok = true; for (int i = 0; i < 16; i += 2) { // 检查相邻两次捕获时间差是否接近200μs(允许±30μs) uint16_t diff = capture_time[i+1] - capture_time[i]; if (diff < 170 || diff > 230) { sync_ok = false; break; } } if (sync_ok) ir_state = STATE_DATA; } } }提示:实际工程中需在
State_DATA阶段用capture_time[i+1] - capture_time[i]计算每个码元宽度,并根据跳变方向(上升/下降)映射为0/1。国赛参考代码中,跳变间隔<180μs判为噪声,>220μs判为帧结束,这是对抗环境光干扰的关键阈值。
3.2 CRC-16校验的轻量级实现:用查表法替代循环计算
国赛要求CRC-16(MODBUS多项式:0x8005),但循环计算在中断中耗时过长(约120周期)。采用256字节查表法,将32bit数据校验压缩至4次查表+异或:
// CRC-16查表数组(预计算,存于Flash) const uint16_t crc16_table[256] = { 0x0000, 0xC0C1, 0x8081, 0x4040, /* ... 共256项,此处省略 */ }; uint16_t crc16_calc(uint8_t *data, uint8_t len) { uint16_t crc = 0xFFFF; for (uint8_t i = 0; i < len; i++) { uint8_t idx = (crc ^ data[i]) & 0xFF; crc = (crc >> 8) ^ crc16_table[idx]; } return crc; } // 使用示例:校验接收到的32bit数据(4字节) uint8_t rx_data[4] = {0x12, 0x34, 0x56, 0x78}; uint16_t calc_crc = crc16_calc(rx_data, 4); // 得到校验值3.2.1 用逻辑分析仪验证解码正确性的三步法
当硬件联调失败时,优先用Saleae Logic或类似工具抓取PA7波形:
- 第一步:确认SYNC头——观察8ms低电平后是否紧随16个等距跳变(间隔≈200μs);
- 第二步:检查曼彻斯特波形——每个bit应有且仅有1次跳变,高→低为1,低→高为0;
- 第三步:比对CRC——手动计算前4字节的CRC-16,与帧尾16bit比对(注意字节序:国赛采用大端CRC,即高位字节在前)。
若第一步失败,检查运放供电是否稳定(±5V);若第二步出现连续跳变,说明环境光过强,需加装遮光筒;若第三步CRC不匹配,确认crc16_table是否加载到正确地址(Keil中需设置__attribute__((at(0x08005000))))。
4. Keil工程配置与国赛级调试技巧:绕过“no STM32 target found”陷阱的实操清单
4.1 ST-Link V2烧录失败的五大根因及对应解决方案
国赛现场最常遇到error: no STM32 target found!报错,本质是调试器与MCU通信链路断裂。按发生概率排序的排查清单:
| 现象 | 根本原因 | 解决方案 |
|---|---|---|
| Keil提示“Cannot connect to target” | SWD引脚(PA13/PA14)被复用为GPIO | 检查RCC->APB2ENR是否使能AFIO时钟;确认AFIO->MAPR未重映射SWD到其他引脚 |
| ST-Link Utility识别到设备但无法擦除 | Flash保护启用(RDP Level 1) | 用ST-Link Utility的“Target→Option Bytes”清除nRDP位;若Level 2则需专用解锁工具 |
| 下载后程序不运行 | 未配置系统时钟(HSI未切换到HSE) | 在SystemInit()中添加`RCC->CFGR |
调试时PC指针停在HardFault_Handler | 堆栈溢出(Keil默认堆栈仅0x200) | Project→Options→Target→Stack/Heap,将Stack size改为0x400,Heap size改为0x200 |
| 烧录成功但红外无输出 | BOOT0引脚悬空(默认拉高进入系统存储器) | 将BOOT0接地(或通过跳线帽短接到GND),确保从Flash启动 |
提示:国赛指定使用ST-Link V2(非V3),因其固件兼容性更稳定。若使用山寨ST-Link,务必刷入官方固件(STSW-LINK007),否则在72MHz主频下可能出现SWD时序错误。
4.2 用Keil的Event Recorder功能实时追踪红外状态机
国赛调试不允许加printf(会占用UART且影响实时性),推荐启用Keil内置的Event Recorder:
- 在
main.c中添加#include "EventRecorder.h"; - Project→Options→Debug→Settings→Trace,勾选
Enable Trace并设置Core Clock为72MHz; - 在状态机关键分支插入事件标记:
EventRecord2(0x1001, ir_state, cap_idx); // 记录当前状态和捕获点数- 调试时点击View→Analysis Windows→Event Viewer,即可看到状态跳变时序图。
此方法比逻辑分析仪更高效——它直接关联C代码行号与硬件事件,例如当ir_state从STATE_SYNC跳变为STATE_DATA时,可立即定位到if (sync_ok)判断处,无需猜测波形对应哪段代码。
4.2.1 国赛真题复现:F题“红外光通信装置”的最小可运行工程结构
压缩包中的工程目录应严格遵循以下结构(Keil v5.36),否则编译会缺失关键文件:
STM32_IR_Com/ ├── CMSIS/ // ARM Cortex-M3内核文件(core_cm3.h等) ├── FWLIB/ // STM32F10x标准外设库(stm32f10x_tim.c等) ├── USER/ │ ├── main.c // 主函数,含SystemInit()和while(1) │ ├── ir_comm.c // 红外收发核心逻辑(含manchester_encode/crc16_calc) │ ├── stm32f10x_it.c // 中断服务函数(TIM2_IRQHandler/TIM3_IRQHandler) │ └── stm32f10x_conf.h // 外设使能宏定义(USE_STDPERIPH_DRIVER) ├── OUTPUT/ // Keil自动生成,无需提交 └── STM32_IR_Com.uvproj // 工程文件,Target选项卡中Crystal为8MHz关键编译选项:
Project→Options→C/C++→Define添加:USE_STDPERIPH_DRIVER, STM32F10X_MD;Project→Options→Output→Create HEX File必须勾选(国赛验收需提供.hex文件);Project→Options→Debug→Use选择ST-Link Debugger,并确保Settings→Flash Download中勾选Reset and Run。
5. 从国赛装置到量产产品的演进路径:增加串口透传与远程配置能力
5.1 用USART1实现红外与PC的桥接:让调试不再依赖逻辑分析仪
国赛装置本身无UART接口,但可通过复用USART1的TX/RX引脚(PA9/PA10)构建调试通道。关键在于避免与红外通信争用CPU资源:
- 红外收发使用TIM中断(高优先级);
- USART接收使用DMA(中优先级),发送使用轮询(低优先级)。
以下代码实现USART1接收PC指令(如AT+SEND=1234)并触发红外发送:
// USART1初始化(9600bps,8N1) void USART1_Init(void) { RCC->APB2ENR |= RCC_APB2ENR_USART1EN | RCC_APB2ENR_IOPAEN; GPIOA->CRL &= ~(0xF << (9*4)); // PA9复用推挽 GPIOA->CRL |= (0x2 << (9*4)); GPIOA->CRL &= ~(0xF << (10*4)); // PA10浮空输入 GPIOA->CRL |= (0x4 << (10*4)); USART1->BRR = 72000000 / 9600; // 72MHz下波特率9600 USART1->CR1 |= USART_CR1_TE | USART_CR1_RE | USART_CR1_UE; USART1->CR1 |= USART_CR1_RXNEIE; // 使能接收中断 } // USART中断中解析AT指令 void USART1_IRQHandler(void) { if (USART1->SR & USART_SR_RXNE) { uint8_t ch = USART1->DR; if (ch == '\r' || ch == '\n') { // 解析rx_buffer中的指令 if (strncmp((char*)rx_buffer, "AT+SEND=", 8) == 0) { uint32_t data = atoi((char*)rx_buffer + 8); ir_send_data(data); // 触发红外发送 } } } }5.1.1 PC端调试工具链:用Tera Term发送AT指令并捕获红外波形
安装Tera Term(免费串口终端),设置:
- Serial Port: COMx(ST-Link虚拟串口);
- Baud Rate: 9600;
- New-line: CR+LF;
- 发送
AT+SEND=0x55AA55AA后,用示波器探头接PA0,应看到40kHz载波调制的曼彻斯特波形(持续约12ms)。
此桥接方案使学生无需购买逻辑分析仪——用Tera Term发送指令,用万用表测PA0电压变化(发送时LED亮起),即可完成基础功能验证。
5.2 红外通信的工业级增强:增加地址过滤与重传机制
国赛仅要求单对单通信,但真实场景需支持多节点。在ir_comm.c中扩展以下功能:
- 地址过滤:接收帧中
ADDR字段必须匹配本机地址(存于FLASH第2页),否则丢弃; - ACK重传:发送后启动TIM4(100ms定时),若未收到对方ACK帧(CMD=0x01),则重发最多3次;
- 信道侦听:发送前读取PA7电平,若持续低电平>5ms,判定信道忙,随机退避1–10ms再发。
此增强使装置可直接用于智能楼宇的传感器网络,无需额外网关。代码框架如下:
#define LOCAL_ADDR 0x03 // 本机地址(0x00–0x0F) #define MAX_RETRY 3 bool ir_send_with_ack(uint32_t data) { for (int retry = 0; retry <= MAX_RETRY; retry++) { ir_send_frame(LOCAL_ADDR, 0x00, data); // CMD=0x00为数据帧 // 启动TIM4等待ACK(100ms) TIM4->ARR = 7200; // 72MHz / 72 = 1MHz, 1MHz * 0.1s = 100000 → 7200计数 TIM4->CR1 |= TIM_CR1_CEN; while (!(TIM4->SR & TIM_SR_UIF)) { if (ack_received) return true; // 中断中置位ack_received } TIM4->SR &= ~TIM_SR_UIF; } return false; // 重传失败 }注意:
ack_received需在TIM3中断中检测到CMD=0x01的帧时置位,且必须声明为volatile。国赛虽不要求此功能,但掌握它意味着你已具备将竞赛作品转化为产品的能力——这正是蓝桥杯嵌入式国赛和智能车竞赛评委最看重的工程素养。
本文还有配套的精品资源,点击获取