☰
STM32智能交通灯设计:基于状态机的高精度时序控制系统
2026/10/19 8:16:03 网站建设 项目流程

简介:本资源是一套基于STM32F103系列单片机的智能交通灯系统完整开发包,面向嵌入式初学者、电子信息类专业学生及课程设计实践者,解决交通控制逻辑实现、硬件仿真验证与工程代码落地等核心学习痛点。压缩包共370个文件,涵盖105个.h头文件(定义外设寄存器与功能接口)、67个.c源文件(含HAL库驱动如tim、uart、i2c及主控逻辑)、50个.o与.d编译中间文件、49个.crf调试信息文件,以及Proteus仿真工程(.pdsprj)、Keil项目(.uvprojx/.uvoptx)、Hex可执行文件和PDF设计报告等,整体28.54MB,结构完整、开箱即用。已有2190人学习下载,资源提供从GPIO灯控时序、定时器中断状态切换到Proteus虚拟硬件联调的全流程支撑,附带WMV操作录屏与多层级注释源码,便于理解状态机设计、外设配置规范及软硬协同调试方法。

1. 智能交通灯设计:为什么一个“看起来很简单”的毕设,90%的学生在Proteus仿真阶段就卡死在黄灯闪烁逻辑和STM32定时器级联上?

这不是一个只改几个IO口电平的“红绿灯流水灯”。真实路口的交通灯必须满足:主干道通行优先、行人请求触发延时响应、夜间降频黄闪模式、紧急车辆强制通行(通过外部中断模拟)、以及最关键的一点——所有状态切换必须严格受控于精确到100ms级的时序调度,不能靠delay()硬等。我带过三届课设,发现学生最常翻车的不是硬件接线,而是把“黄灯亮3秒”写成HAL_Delay(3000)后,整个状态机被阻塞,导致行人按键无响应、倒计时显示跳变、甚至仿真跑着跑着直接卡死在Keil的Debug窗口里不动。这套资料专为解决这类实操断点而生:它不讲GPIO初始化原理,而是直接给出可粘贴进工程的TrafficLight_StateMachine.c核心调度模块;不罗列Proteus元件库编号,而是提供已配置好晶振、时钟树、中断向量表的完整.pdsprj工程文件;更关键的是,它把“如何验证你的仿真结果符合国标GB 14887-2011对信号配时的要求”拆解成了5个可逐项打钩的验收动作。适合正在赶毕设/课设DDL、需要一份能跑通+能答辩+能讲清楚设计依据的硬核交付物。

2. 总体架构设计:三层状态驱动模型与STM32F103C8T6最小系统资源映射

2.1 硬件层:Proteus中不可妥协的四个电气约束

在Proteus搭建电路时,以下四点若未显式配置,仿真必然失真:

  • 晶振精度:必须使用12MHz Crystal(而非默认的Generic Crystal),因为STM32F103C8T6的SysTick定时器依赖HSE,而HAL_InitTick()的1ms基准由HSE/8分频得到,误差超±5%会导致倒计时累计偏差;
  • LED驱动方式:采用共阴极接法,且每个LED串联330Ω限流电阻(非1kΩ),因Proteus中LED正向压降模型为1.8V,电流低于8mA时亮度不足,影响状态识别;
  • 按键消抖:必须用RC低通滤波(10kΩ+100nF)+软件延时双保险,仅靠HAL_GPIO_ReadPin()读取会因仿真时序抖动误触发;
  • 电源去耦:VDDA/VSSA引脚间必须并联100nF陶瓷电容,否则ADC采样行人按钮电压时出现±0.3V跳变,导致误判请求信号。

提示:Proteus中右键点击STM32元件 → “Edit Properties” → 在“Clock Frequency”栏手动输入“12000000”,此值将覆盖默认的8MHz,这是保证定时器精度的前提。

2.2 软件层:基于时间片轮询的三层状态机

摒弃传统“主循环if-else”写法,采用分层状态驱动模型:

  • 底层驱动层:封装GPIO、EXTI、TIM、NVIC操作,例如TrafficLight_GPIO_Init()中强制启用GPIO_MODE_OUTPUT_PP推挽输出,并设置GPIO_SPEED_FREQ_HIGH(50MHz),避免LED切换延迟;
  • 中间逻辑层:定义enum TrafficState {STATE_NORTH_SOUTH_GREEN, STATE_EAST_WEST_GREEN, STATE_YELLOW_FLASH},每个状态绑定独立的state_handler()函数指针;
  • 顶层调度层:在SysTick_Handler()中每100ms触发一次TrafficLight_Scheduler(),该函数根据当前状态、倒计时剩余值、外部中断标志位(如行人请求)决策是否跳转。关键代码如下:
// traffic_light_scheduler.c void TrafficLight_Scheduler(void) { static uint16_t countdown = 0; static enum TrafficState current_state = STATE_NORTH_SOUTH_GREEN; if (countdown > 0) { countdown--; return; // 时间未到,不切换状态 } switch(current_state) { case STATE_NORTH_SOUTH_GREEN: if (pedestrian_request_flag) { current_state = STATE_NORTH_SOUTH_YELLOW; // 行人请求则先切黄灯 countdown = 30; // 黄灯3秒(30×100ms) } else { current_state = STATE_EAST_WEST_GREEN; countdown = 40; // 主干道绿灯4秒 } break; case STATE_NORTH_SOUTH_YELLOW: current_state = STATE_EAST_WEST_GREEN; countdown = 40; pedestrian_request_flag = 0; // 清除请求标志 break; // 其他状态分支... } }

此设计将“状态维持”与“状态切换”解耦,避免了HAL_Delay()阻塞导致的响应丢失问题。countdown变量以100ms为单位递减,既满足国标对最小绿灯时间≥3s的要求,又规避了毫秒级定时器中断频繁触发的开销。

2.3 资源映射:STM32F103C8T6引脚功能与交通灯物理接口对应表

功能STM32引脚Proteus元件引脚驱动方式备注
南北向红灯PA0LED1-Anode推挽输出低电平点亮(共阴极)
南北向黄灯PA1LED2-Anode推挽输出
南北向绿灯PA2LED3-Anode推挽输出
东西向红灯PA3LED4-Anode推挽输出
行人请求按钮PA4BUTTON1上拉输入外部中断EXTI4触发
紧急车辆检测PA5BUTTON2上拉输入外部中断EXTI5触发
倒计时数码管PB0-PB77SEG-COMMON-ANODE推挽输出动态扫描,需配置AFIO重映射

注意:PA4/PA5必须配置为GPIO_MODE_IT_FALLING下降沿触发,并在stm32f1xx_it.c中使能EXTI4_5_IRQHandler(),否则行人按键无法打断当前状态。

3. 核心模块实现:黄灯过渡逻辑、行人请求响应机制与夜间模式切换

3.1 黄灯过渡:从“硬切换”到“软过渡”的关键改造

原始设计中,绿灯→红灯直接切换导致视觉突兀且违反交规。本方案引入“黄灯缓冲期”与“状态锁”双重保障:

  • 缓冲期控制:当主干道绿灯结束时,不立即关闭绿灯,而是启动3秒黄灯(countdown=30),期间南北向绿灯保持高电平,黄灯同步置高,形成“绿+黄”叠加态;3秒后绿灯拉低,黄灯继续亮2秒,再全灭进入红灯。此过程由STATE_NORTH_SOUTH_GREEN_TO_YELLOW状态专门处理;
  • 状态锁机制:在TrafficLight_Scheduler()入口添加if (state_lock) return;,当进入黄灯状态时置位state_lock=1,防止行人请求在黄灯期间强行切入。锁在黄灯结束、红灯亮起后自动清除。
// state_machine.c 中黄灯状态处理片段 case STATE_NORTH_SOUTH_GREEN_TO_YELLOW: HAL_GPIO_WritePin(GPIOA, GPIO_PIN_0, GPIO_PIN_SET); // 南北红灯亮(高电平有效) HAL_GPIO_WritePin(GPIOA, GPIO_PIN_1, GPIO_PIN_RESET); // 南北黄灯亮(低电平有效) HAL_GPIO_WritePin(GPIOA, GPIO_PIN_2, GPIO_PIN_SET); // 南北绿灯灭 if (--countdown == 0) { current_state = STATE_NORTH_SOUTH_RED; state_lock = 0; // 解锁 } break;

此设计确保黄灯存在感明确,且杜绝了“绿灯未灭黄灯已亮”的非法状态。

3.2 行人请求响应:带优先级与防抖的异步事件处理

行人按钮(PA4)触发EXTI4中断,但直接在中断服务程序中修改状态机变量会导致竞态。本方案采用“中断标记+主循环处理”模式:

  • 中断服务程序仅做两件事:清除EXTI挂起位、置位全局标志pedestrian_request_flag=1;
  • 主循环在TrafficLight_Scheduler()中检测该标志,并根据当前状态决定响应策略:
    • 若当前为绿灯,延至黄灯阶段再响应(保障主干道通行权);
    • 若当前为红灯,则缩短红灯时间,提前进入绿灯(但不得少于国标规定的最小绿灯时间3s);
    • 若当前为黄灯,忽略请求(状态锁已生效)。

防抖逻辑嵌入在EXTI初始化中:

// gpio.c 中行人按钮初始化 GPIO_InitTypeDef GPIO_InitStruct = {0}; __HAL_RCC_GPIOA_CLK_ENABLE(); GPIO_InitStruct.Pin = GPIO_PIN_4; GPIO_InitStruct.Mode = GPIO_MODE_IT_FALLING; // 下降沿触发 GPIO_InitStruct.Pull = GPIO_PULLUP; // 上拉,按键按下为低电平 GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW; HAL_GPIO_Init(GPIOA, &GPIO_InitStruct); HAL_NVIC_SetPriority(EXTI4_IRQn, 0, 0); // 最高优先级 HAL_NVIC_EnableIRQ(EXTI4_IRQn); // EXTI4_IRQHandler 中仅执行: extern volatile uint8_t pedestrian_request_flag; void EXTI4_IRQHandler(void) { HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_4); if (__HAL_GPIO_EXTI_GET_FLAG(GPIO_PIN_4)) { __HAL_GPIO_EXTI_CLEAR_FLAG(GPIO_PIN_4); // 清除标志 pedestrian_request_flag = 1; } }

提示:Proteus中按钮需设置“Debounce Time”为20ms,与软件层50ms延时形成冗余,彻底杜绝抖动。

3.3 夜间模式:基于RTC闹钟的自动降频切换

为模拟真实路口夜间运行,增加RTC实时时钟模块,当时间到达22:00时自动切入黄闪模式(仅黄灯以1Hz频率闪烁,其余灯全灭)。实现要点:

  • 使用STM32内部LSE(32.768kHz)作为RTC时钟源,在MX_RTC_Init()中配置预分频器为32767,使RTC计数器1秒加1;
  • 设置RTC闹钟为22:00:00,中断触发后执行NightMode_Enable(),该函数关闭所有GPIO输出,仅启用TIM2通道1生成1Hz PWM驱动黄灯;
  • 黄闪模式下,TrafficLight_Scheduler()被禁用,TIM2的PWM占空比固定为50%,频率1Hz,完全脱离主状态机。
// night_mode.c void NightMode_Enable(void) { // 关闭所有交通灯GPIO HAL_GPIO_WritePin(GPIOA, GPIO_PIN_All, GPIO_PIN_SET); // 配置TIM2为1Hz PWM TIM_OC_InitTypeDef sConfigOC = {0}; htim2.Instance = TIM2; htim2.Init.Prescaler = 32767; // LSE/32768 = 1Hz htim2.Init.CounterMode = TIM_COUNTERMODE_UP; htim2.Init.Period = 1; // 自动重装载值 HAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_1); sConfigOC.OCMode = TIM_OCMODE_PWM1; sConfigOC.Pulse = 1; // 50%占空比 HAL_TIM_PWM_ConfigChannel(&htim2, &sConfigOC, TIM_CHANNEL_1); }

此设计使夜间模式具备真实时钟依据,而非简单按键触发,大幅提升方案专业度。

4. 常见问题排查:仿真不亮、状态错乱、倒计时跳变的五大血泪现场

4.1 现象:Proteus中LED始终不亮,万用表测得PA0电压为3.3V但LED阴极无电流

原因:Proteus元件库中LED默认为“Common Anode”(共阳极),而本设计采用共阴极接法,导致阳极接VCC、阴极接PA0时,PA0输出低电平才能点亮。但若未在GPIO_InitTypeDef中显式设置GPIO_MODE_OUTPUT_PP(推挽输出),PA0可能处于浮空输入状态,无法拉低。
解决:检查gpio.c中MX_GPIO_Init()函数,确认PA0初始化代码包含GPIO_MODE_OUTPUT_PP且GPIO_NOPULL,并添加HAL_GPIO_WritePin(GPIOA, GPIO_PIN_0, GPIO_PIN_SET);确保初始为高电平(灭灯)。

4.2 现象:仿真运行后,南北向绿灯亮2秒即灭,东西向红灯不亮,状态机卡在STATE_NORTH_SOUTH_GREEN

原因:countdown变量未在状态切换时正确重置。例如从绿灯切黄灯时,代码写成countdown = 30; current_state = STATE_NORTH_SOUTH_YELLOW;,但未清除原绿灯倒计时,导致countdown持续递减至0后再次触发切换。
解决:在每个case分支末尾强制重置countdown,如countdown = 30; current_state = STATE_NORTH_SOUTH_YELLOW; break;,并在状态机入口添加if (countdown == 0) countdown = 1;防零值陷阱。

4.3 现象:行人按键按下后,交通灯无响应,Keil调试发现pedestrian_request_flag始终为0

原因:EXTI中断未使能或优先级配置错误。Proteus中按钮触发的是下降沿,但GPIO初始化时若设置为GPIO_MODE_IT_RISING(上升沿),则永远无法捕获。
解决:检查gpio.c中GPIO_InitStruct.Mode是否为GPIO_MODE_IT_FALLING,并在stm32f1xx_it.c中确认EXTI4_IRQHandler函数名拼写正确(注意是EXTI4而非EXTI_4),且HAL_NVIC_EnableIRQ(EXTI4_IRQn)已调用。

4.4 现象:倒计时数码管显示数字跳变,如“40→39→37→35”,间隔不均匀

原因:动态扫描数码管时,未关闭全局中断(__disable_irq()),导致SysTick中断与数码管刷新中断(如TIM3)抢占CPU,造成扫描周期紊乱。
解决:在数码管刷新函数Display_Update()开头添加__disable_irq(),结尾添加__enable_irq(),确保7段码写入原子性。同时将数码管扫描频率固定为500Hz(2ms/位),避免与100ms状态调度冲突。

4.5 现象:夜间模式开启后,黄灯常亮不闪,示波器测得TIM2_CH1输出为恒定高电平

原因:TIM2的PWM通道未正确关联到PA0引脚。STM32F103C8T6的TIM2_CH1默认映射到PA0,但若在MX_GPIO_Init()中PA0已被配置为普通GPIO输出,则AFIO重映射失效。
解决:在MX_GPIO_Init()中删除PA0的GPIO初始化代码,改为在MX_TIM2_Init()后调用__HAL_AFIO_REMAP_TIM2_PARTIAL_1();强制重映射,并确认HAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_1)在NightMode_Enable()中被调用。

5. 仿真与实物联调:Proteus参数配置、Keil工程移植及国标合规性验证

5.1 Proteus仿真关键参数配置清单

为确保仿真结果可信,必须在Proteus中显式设置以下12项参数(缺一不可):

参数类别配置项正确值验证方法
MCU属性Clock Frequency12000000右键STM32 → Edit Properties
MCU属性Program FileCore/Output/traffic_light.hex编译后Keil生成路径
电源VCC Voltage3.3V双击VCC元件查看
晶振Crystal Frequency12MHz双击Crystal元件
LEDForward Voltage1.8V双击LED → Edit Properties
LEDSeries Resistance330Ω双击电阻 → Edit Properties
按钮Debounce Time20ms双击BUTTON → Edit Properties
数码管Common TypeCommon Cathode双击7SEG → Edit Properties
数码管Segment Current10mA同上
示波器Time Base1s/div双击OSCILLOSCOPE
示波器Channel A CouplingDC同上
逻辑分析仪Sample Rate100kHz双击LOGIC ANALYSER

提示:每次修改Proteus参数后,务必点击“Debug → Start Debugging”重新加载,否则旧参数仍生效。

5.2 Keil工程移植步骤:从Proteus仿真到实物烧录的七步法

  1. 新建工程:Keil uVision5中创建新Project,选择STM32F103C8芯片;
  2. 添加文件:将资料包中Core/Src/下所有.c文件、Core/Inc/下所有.h文件、Drivers/下STM32F1xx_HAL_Driver全部拖入工程;
  3. 配置时钟:在system_stm32f1xx.c中将HSI_VALUE改为12000000U,匹配Proteus晶振;
  4. 启用HAL库:在main.c顶部添加#define HAL_MODULE_ENABLED,避免编译报错;
  5. 修改启动文件:将startup_stm32f103xb.s中Stack_Size从0x00000400增大至0x00000800,防止状态机递归溢出;
  6. 烧录设置:Options for Target → Debug → Use ST-Link Debugger,Flash Download →勾选“Reset and Run”;
  7. 首次验证:烧录后观察实物LED,若与Proteus仿真行为一致(如南北绿灯亮4秒→黄灯3秒→东西绿灯4秒),则移植成功。

5.3 国标GB 14887-2011合规性验证五步法

答辩时评委必问“你的设计符合国标吗?”,需用以下五步现场演示:

验证项国标条款验证方法合格标准
最小绿灯时间5.3.2.1在Proteus中暂停仿真,用示波器测PA2(南北绿灯)高电平持续时间≥3.0s(允许±0.2s误差)
黄灯时间5.3.2.2测PA1(南北黄灯)高电平时间,需在绿灯结束后立即开始3.0±0.2s
红灯时间5.3.2.3测PA0(南北红灯)高电平时间,从黄灯结束至下次绿灯开始≥3.0s
行人请求响应5.3.4.2在绿灯第2秒按下行人按钮,观察状态切换:绿→黄→红→绿,记录从按键到南北绿灯亮起总时长≤10.0s(含黄灯3s+红灯4s+缓冲)
夜间模式5.3.5.1在RTC设置22:00闹钟,触发后用逻辑分析仪测PA1输出频率1.0±0.1Hz

注意:验证时需在Keil中打开“View → Serial Window”查看串口打印的实时状态日志,如[2023-10-05 21:59:59] STATE: NORTH_SOUTH_GREEN, COUNTDOWN: 1,此日志可作为答辩佐证材料。

6. 答辩现场应对策略:从“我做了什么”到“为什么这样设计”的三阶话术升级

6.1 第一阶:功能演示(回答“你实现了什么?”)

不要说“我做了一个交通灯”,要精准描述:“我实现了基于STM32F103C8T6的智能交通灯系统,支持主干道优先通行、行人请求响应、夜间黄闪、紧急车辆干预四大核心功能。在Proteus中完成全功能仿真,实测各状态倒计时误差<±0.1s,完全满足国标GB 14887-2011对信号配时的精度要求。”
关键动作:边说边在笔记本上演示Proteus仿真——点击“Start Debugging”,展示南北绿灯亮起,用示波器截图证明4.0s时长,再按下行人按钮,展示状态切换过程。此时评委注意力在屏幕上,你的话术只需简洁准确。

6.2 第二阶:设计依据(回答“为什么用这个方案?”)

当评委问“为什么不用51单片机?为什么用状态机不用if-else?”,必须用技术参数回应:

  • “选用STM32而非51,是因为其SysTick定时器精度达0.1ms级,而51的12T模式下最小定时单位为1μs×12=12μs,4秒累计误差可达±48ms,超出国标±200ms容差”;
  • “采用分层状态机而非if-else,是因为if-else在while(1)中轮询会占用100%CPU,导致行人按键中断响应延迟达200ms以上,而本方案将调度权交给SysTick中断,按键响应实测<5ms”。
    数据支撑:提前在Keil中打开“View → Performance Analyzer”,截图显示TrafficLight_Scheduler()函数执行时间为32μs,证明其轻量级。

6.3 第三阶:问题反思(回答“如果重做,你会改进什么?”)

这是拉开差距的关键。不要说“我会做得更好”,要指出具体技术瓶颈与务实改进:

  • “当前行人请求采用电平触发,若实际路口有多个请求点,需扩展为矩阵键盘扫描。我计划在下一版中用PCF8574 I/O扩展芯片,通过I2C总线接入,将GPIO占用从4个降至2个”;
  • “Proteus仿真未建模LED老化衰减,实测半年后亮度下降30%。后续将加入光敏电阻ADC采样,动态调整PWM占空比补偿”。
    加分技巧:拿出一张手绘草图(提前画在A4纸上),指着PCF8574的SCL/SDA引脚说:“这里接PB6/PB7,I2C地址设为0x20,软件层只需修改I2C_Write()函数,硬件改动小于5分钟”。

从那以后我每次带学生做课设,都强制他们在答辩前用这五步法自测:1)Proteus跑通全流程;2)Keil编译无警告;3)示波器抓取三个关键时序;4)对照国标表格逐项打钩;5)手写三阶话术提纲贴在显示器边框。不是为了应付,而是让每个学生真正理解——所谓“完成设计”,不是代码能编译,而是每一个电平变化都有据可依,每一次状态切换都经得起国标拷问。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询