1. 项目背景与硬件准备
第一次接触STM32交通灯项目时,我被它完整的嵌入式开发生态震撼到了。这个项目完美融合了GPIO控制、定时器中断、数码管驱动等核心知识点,特别适合想从51单片机进阶到ARM Cortex-M3的开发者。下面分享我反复调试后总结的硬件搭建要点。
开发板选择上,建议使用STM32F103C8T6最小系统板,价格亲民且资源充足。需要准备的元器件清单很接地气:
- 红黄绿LED各4个(实际用6个,两组交通灯加备用)
- 四位共阳数码管2个
- 220Ω电阻12个(限流用)
- 面包板+杜邦线若干
- USB-TTL串口模块(用于程序烧录)
电路连接有个易错点:数码管驱动最好采用74HC595移位寄存器。我最初直接用GPIO驱动,发现STM32的驱动电流不足导致显示暗淡,后来改用串行转并行方案完美解决。具体接线时,PA0-PA7接595的数据引脚,PB12接锁存信号,记得在数据线加上拉电阻。
2. 开发环境搭建实战
软件配置是很多新手的第一道坎。推荐使用Keil MDK+STM32CubeMX组合拳,CubeMX能自动生成初始化代码,省去大量寄存器配置时间。安装时要注意这两个坑:
- 务必安装STM32F1的DFP支持包
- CubeMX生成代码时选择MDK-ARM工具链
创建工程时有个关键设置:在SYS调试选项里必须选择Serial Wire,否则无法用ST-Link调试。时钟配置建议直接使用外部8MHz晶振,通过PLL倍频到72MHz主频,这样定时器计算更精准。
我习惯的工程目录结构是这样的:
/Drivers /STM32F1xx_HAL_Driver /CMSIS /Application /User /Proteus /MDK-ARM3. 核心代码实现解析
交通灯逻辑看似简单,但写好状态机需要技巧。我的实现方案采用时间片轮询+状态标志位:
typedef enum { MODE_NS_GREEN_EW_RED = 0, MODE_NS_YELLOW_EW_RED, MODE_NS_RED_EW_GREEN, MODE_NS_RED_EW_YELLOW } TrafficMode; void Traffic_Light_Update(void) { static uint8_t counter = 0; static TrafficMode mode = MODE_NS_GREEN_EW_RED; if(++counter >= mode_duration[mode]){ counter = 0; mode = (mode + 1) % 4; Update_LED_State(mode); } Display_Countdown(mode_duration[mode] - counter); }数码管显示要注意消隐问题。采用动态扫描方式时,我封装了专门的显示函数:
void Seg7_Display(uint16_t num) { uint8_t digits[4]; digits[0] = num / 1000; digits[1] = (num % 1000) / 100; digits[2] = (num % 100) / 10; digits[3] = num % 10; for(int i=0; i<4; i++){ GPIO_Write(GPIOA, digit_codes[digits[i]]); GPIO_Write(GPIOC, 1<<(i+4)); HAL_Delay(2); GPIO_Write(GPIOC, 0<<(i+4)); } }4. Proteus仿真技巧
仿真环节最容易出现器件不响应的问题。分享几个实测可用的技巧:
- 加载HEX文件后,右键单片机选择"Edit Properties",在Clock Frequency里填写72MHz
- 数码管建议使用7SEG-MPX4-CA(共阳)型号
- 添加虚拟终端(VIRTUAL TERMINAL)监控调试输出
仿真时常见三个坑:
- 灯不亮:检查GPIO模式是否设置为Output Push-Pull
- 数码管乱码:确认段码数据线与仿真电路一致
- 定时不准:调整晶振频率和定时器预分频值
我优化后的仿真电路包含这些关键元件:
STM32F103C6 LED-RED ×6 LED-YELLOW ×6 LED-GREEN ×6 7SEG-MPX4-CA ×2 74HC595 ×2 BUTTON ×3(用于模式切换)5. 进阶功能实现
基础功能稳定后,可以增加这些实用功能:
- 紧急车辆优先通行模式(按键触发所有方向红灯)
- 夜间模式(切换为黄灯闪烁)
- 倒计时时间可调(通过按键调整)
夜间模式实现代码示例:
void Night_Mode_Handler(void) { static uint8_t blink = 0; if(++blink >= 10){ blink = 0; GPIO_ToggleBits(GPIOB, ALL_YELLOW_PINS); } }时间调整功能建议配合旋转编码器实现,比普通按键更顺手。要注意防抖处理和边界值判断:
void Encoder_Handler(void) { if(READ_ENC_A() != lastState){ if(READ_ENC_B() != lastState){ if(time_setting < 99) time_setting++; }else{ if(time_setting > 5) time_setting--; } } lastState = READ_ENC_A(); }6. 常见问题解决方案
调试过程中我遇到过这些典型问题:
问题1:仿真时数码管显示残影解决方法:在段选和位选之间增加5ms延时,确保信号稳定
问题2:交通灯状态切换不同步优化方案:改用定时器中断代替延时函数,我通常用TIM2做1ms基准:
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) { if(htim == &htim2){ static uint16_t cnt = 0; if(++cnt >= 1000){ cnt = 0; Traffic_Light_Update(); } } }问题3:Proteus运行卡顿处理步骤:
- 关闭不必要的调试窗口
- 在"System"设置里勾选"Optimize Simulation"
- 降低仿真速度到50%
7. 项目优化方向
完成基础功能后,可以考虑这些升级方案:
- 增加蓝牙/WiFi模块实现远程控制
- 添加光敏电阻自动切换夜间模式
- 移植FreeRTOS实现多任务管理
- 加入车流量检测功能(可用红外对管模拟)
蓝牙控制部分代码结构示例:
void Bluetooth_Handler(uint8_t *cmd) { if(strcmp(cmd, "EMG") == 0){ Enter_Emergency_Mode(); } else if(strcmp(cmd, "NRM") == 0){ Exit_Emergency_Mode(); } }最后提醒初学者,一定要养成版本管理习惯。我在开发过程中用Git保存了这些关键节点:
- v1.0 基础灯光控制
- v1.1 加入数码管倒计时
- v1.2 实现状态自动切换
- v2.0 增加Proteus仿真
- v3.0 添加紧急模式功能