STM32交通灯控制系统实战:状态机、定时器与工程避坑指南

📅 发布时间:2026/10/14 17:37:14
STM32交通灯控制系统实战:状态机、定时器与工程避坑指南
简介这是一套面向嵌入式初学者与电子类专业学生的STM32交通灯控制系统完整实践资料围绕ARM Cortex-M内核微控制器展开解决交叉路口信号灯智能化控制这一典型实时应用问题。压缩包共191个文件约9.28MB以c源文件、h头文件、o目标文件、crf编译中间文件及s启动文件为主另含uvproj工程、sct分散加载、hex烧录文件、map映射表以及1份doc报告和1份ppt演示文稿覆盖从底层驱动到工程配置的完整链路。代码部分包含时钟与GPIO初始化、定时器中断切换灯态、车流量传感器处理、控制逻辑与LED显示驱动等模块PPT讲解系统框图、硬件连接、软件流程与控制算法报告则总结项目目标、实施步骤、挑战与解决方案。目前已有7027人学习下载适合通过代码与文档对照理解微控制器在交通控制中的实际工程应用提升硬件设计与软件开发能力。1. 从一份 STM32 交通灯资源说起为什么它值得你花时间拆一遍如果你正在搜「STM32 交通灯」大概率不是想听一遍红黄绿怎么循环——那个逻辑用 51 单片机也能写。真正让人卡住的往往是另一类问题答辩 PPT 里状态机图怎么画才不被导师追问、报告里定时器参数怎么算才自洽、代码烧进去之后灯不亮到底是硬件还是逻辑的锅。这份资源就是冲着这些场景来的它把 STM32 交通灯控制系统拆成了可复现的工程包包含源码、PPT 和报告三部分适合课程设计、嵌入式入门练手以及需要快速搭出一套能讲清楚、能跑起来的演示系统的人。下面我按自己拆包的习惯把这份东西从原理到落地讲透重点放在参数怎么定、坑在哪。2. 交通灯状态机与 STM32 定时器先搞懂为什么这么设计2.1 状态机不是画着好看它决定了代码结构交通灯的本质是一个有限状态机四个状态循环东西绿、东西黄、南北绿、南北黄。很多新手一上来就写四个delay堆在一起结果按键调时间、紧急模式全加不进去。这份资源用的是状态机写法每个状态对应一个结构体里面存当前灯色、持续时间、下一个状态。这样做的直接好处是改配时只改结构体数组不用动逻辑加一个夜间黄闪模式只需要多一个状态分支。我一般会把状态定义成枚举配一个状态表typedef enum { EW_GREEN, EW_YELLOW, NS_GREEN, NS_YELLOW, NIGHT_FLASH } TrafficState; typedef struct { TrafficState state; uint16_t duration_ms; // 该状态持续毫秒 TrafficState next; } StateNode; // 配时表东西绿 8s黄 2s南北绿 8s黄 2s StateNode stateTable[] { {EW_GREEN, 8000, EW_YELLOW}, {EW_YELLOW, 2000, NS_GREEN}, {NS_GREEN, 8000, NS_YELLOW}, {NS_YELLOW, 2000, EW_GREEN}, };逻辑说明duration_ms决定这个状态待多久next指向下一个状态。主循环里只做一件事——查表、计时、切换。参数怎么改把 8000 改成 12000 就是东西向绿灯 12 秒不需要动任何判断语句。注意duration_ms用uint16_t最大只能到 65535也就是 65 秒左右如果要做 90 秒长配时得换成uint32_t这是很多人第一次改参数时会踩的溢出坑。2.2 定时器选型SysTick 还是 TIM差别比你想的大资源里用的是 TIM2 做 1ms 基准中断而不是 SysTick。原因很实际SysTick 通常被 HAL 库拿去做HAL_Delay的时基如果你在中断里再塞状态机计时容易和库函数打架。TIM2 独立出来中断里只做一件事——给每个状态倒计时减 1减到 0 就切状态。配置 TIM2 的关键参数就三个预分频器、自动重装载值、时钟分频。假设系统时钟 72MHz要 1ms 中断一次// 72MHz / 72 1MHz即 1us 计数一次 htim2.Init.Prescaler 72 - 1; // 计数 1000 次 1ms htim2.Init.Period 1000 - 1; htim2.Init.ClockDivision TIM_CLOCKDIVISION_DIV1;逻辑说明预分频器把 72MHz 降到 1MHz自动重装载值决定数到多少溢出。两个参数一乘就是中断周期。这里有个常见翻车点有人把 Prescaler 写成 72结果实际是 72173 分频周期变成 1.0139ms跑一天下来累积误差好几秒。STM32 的预分频器和重装载值都是「写进去的值加一才生效」这个加一必须记住。2.3 GPIO 驱动灌电流还是拉电流决定你烧不烧灯交通灯用 6 个 LED 代表两个方向的红黄绿。资源里 LED 接的是推挽输出低电平点亮。为什么低电平点亮因为 STM32 的 GPIO 灌电流能力比拉电流强一般单个引脚灌电流可以到 20mA 以上拉电流则弱一些。如果你接的是共阳 LED代码里就要写HAL_GPIO_WritePin(GPIOA, GPIO_PIN_0, GPIO_PIN_RESET)来点亮。我见过有人直接把 LED 接在引脚和地之间然后写高电平点亮灯是亮了但亮度很暗还以为是代码问题。其实是拉电流不够。稳妥做法是 LED 串联限流电阻后接 3.3V引脚输出低电平灌电流点亮。限流电阻怎么算红色 LED 压降约 1.8V目标电流 8mA电阻 (3.3 - 1.8) / 0.008 ≈ 187Ω取 200Ω 或 220Ω 都行。这个计算写进报告里导师一看就知道你是真搭过电路。3. 从零跑通工程搭建、代码烧录与配时调试3.1 工程目录怎么组织决定了你后期改起来痛不痛拿到资源包后别急着打开 main.c 就编译。先看目录结构常见做法是分成 Core、Drivers、User 三层。Core 放 CubeMX 生成的初始化代码Drivers 放 HAL 库User 放你自己的状态机、GPIO 封装、按键处理。这样分的好处是CubeMX 重新生成代码时不会覆盖你的业务逻辑。如果你自己从 CubeMX 起工程按这个顺序配RCC 里把 HSE 设为 Crystal/Ceramic Resonator外部晶振比内部 RC 准得多串口通信和定时都靠它。SYS 里 Debug 选 Serial Wire不选的话下次可能连不上下载器。GPIO 里把 6 个 LED 引脚设为 Output Push Pull初始电平 High灭。TIM2 设为 Internal Clock分频和重装载按上面算的填。中断里把 TIM2 global interrupt 勾上优先级设 1 或 2别设 00 留给系统关键中断。生成代码后把状态机逻辑写进while(1)之前中断回调里写倒计时。注意 HAL 库的中断回调函数名是HAL_TIM_PeriodElapsedCallback写错一个字母就不会进中断这是血泪经验。3.2 烧录与调试灯不亮时先查这三处代码编译通过不代表灯会亮。我一般按这个顺序排查第一查时钟。在SystemClock_Config里确认HAL_RCC_GetHCLKFreq()返回的是不是 72MHz。如果外部晶振没起振HAL 库会自动切到内部 HSI频率变成 16MHz定时器周期全乱灯闪得飞快或者根本不闪。第二查引脚。用万用表量 LED 两端电压如果一直是 3.3V 不变说明引脚没翻转。这时候看代码里HAL_GPIO_TogglePin的端口和引脚号对不对GPIOA和GPIO_PIN_5这种参数写反了不会报错但灯就是不亮。第三查中断。在HAL_TIM_PeriodElapsedCallback里翻转一个测试引脚用示波器看有没有 1ms 方波。没有的话检查HAL_TIM_Base_Start_IT(htim2)有没有调用。很多人初始化完定时器忘了启动代码看着全对就是不动。// 主循环里启动定时器中断只调一次 HAL_TIM_Base_Start_IT(htim2); // 中断回调里做倒计时 void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) { if (htim-Instance TIM2) { if (currentDuration 0) { currentDuration--; } else { // 切换到下一个状态 currentState stateTable[currentState].next; currentDuration stateTable[currentState].duration_ms; updateLEDs(currentState); } } }逻辑说明currentDuration是全局变量每 1ms 减一。减到 0 就查表切状态并刷新 LED。参数duration_ms在状态表里改updateLEDs里根据状态写 GPIO。注意中断里不要放HAL_Delay或串口打印会阻塞其他中断导致计时不准。3.3 配时参数怎么定别拍脑袋按路口流量算报告里如果只写「绿灯 8 秒」会被问为什么是 8 秒。常见做法是按路口车流量估算假设东西向车流量是南北向的 1.5 倍总周期 20 秒那东西绿可以给 8 秒南北绿给 5 秒黄灯各 2 秒剩下 3 秒做全红清空路口。这个计算过程写进报告比单纯贴代码有说服力。资源里的配时表可以直接改改完重新编译烧录就行。如果你要加按键调时间在外部中断里改stateTable数组的值注意改的时候先关定时器中断改完再开否则可能改到一半状态切换了出现两个方向同时绿灯的玄学现象。4. 避坑与排查这五个坑我替你踩过了4.1 现象灯全亮或全灭代码逻辑看着没问题原因GPIO 初始化时GPIO_PIN用了GPIO_PIN_All或者HAL_GPIO_Init的Pin字段没按位或正确。另一个可能是 LED 共阳共阴搞反了代码写低电平点亮但硬件是共阴接法。解决用万用表逐个量引脚电平对照代码里HAL_GPIO_WritePin的参数。共阳接法时点亮写GPIO_PIN_RESET灭写GPIO_PIN_SET和共阴完全相反。不确定就写个测试循环让每个引脚轮流翻转看哪个灯对应哪个引脚。4.2 现象定时器中断进不去或者进去一次就死机原因中断优先级配置冲突或者回调函数里调用了阻塞函数。还有一种情况是HAL_TIM_Base_Start_IT在MX_TIM2_Init之前调用了定时器还没初始化就启动直接进 HardFault。解决确认MX_TIM2_Init()在main里先执行再调HAL_TIM_Base_Start_IT。中断回调里只做标志位翻转和计数不要放printf。如果用了 FreeRTOS注意中断优先级不能高于configMAX_SYSCALL_INTERRUPT_PRIORITY否则会触发断言。4.3 现象仿真器能连上但烧录失败报 Flash 写保护原因STM32 的读保护或写保护被意外开启常见于之前烧过带保护的程序或者 Option Bytes 被改过。解决用 ST-Link Utility 或 CubeProgrammer 连接后在 Option Bytes 里把 Read Out Protection 设为 Level 0写保护全部解除然后全片擦除再烧。注意解除保护会清空 Flash代码要重新烧。这个操作不可逆但一般开发板无所谓。4.4 现象按键调时间时状态机乱跳偶尔两个方向同时绿原因按键中断和定时器中断同时访问currentState和currentDuration没有做临界区保护。改配时的时候定时器还在跑改到一半状态切换了。解决在按键中断里先HAL_TIM_Base_Stop_IT(htim2)改完配时表再HAL_TIM_Base_Start_IT(htim2)。或者用__disable_irq()和__enable_irq()包住修改代码。更稳妥的做法是按键只改一个标志位主循环里检测到标志位再改配时避免在中断里做复杂操作。4.5 现象报告里写的定时器参数和实际代码对不上原因CubeMX 里改了分频和重装载值但报告里抄的是旧参数或者忘了把「写进去的值要加一」算进去。解决报告里的参数表直接截图 CubeMX 的配置界面或者从生成的tim.c里复制Prescaler和Period的值然后在旁边注明实际分频 值 1。我一般会在报告里加一列「实际值」避免答辩时被追问。5. 进阶玩法把交通灯做成能讲故事的演示系统5.1 加一个夜间黄闪模式PPT 里多一页状态图基础功能跑通后加夜间模式是性价比最高的进阶。实现方式很简单在状态表里加一个NIGHT_FLASH状态持续 500ms下一个状态指向自己LED 输出改成黄灯翻转。按键切换白天/夜间模式PPT 里画一张状态迁移图标注两个模式之间的切换条件答辩时能讲出「根据时段自动切换」的设计思路。// 夜间模式黄灯 500ms 翻转一次 void nightFlash(void) { HAL_GPIO_TogglePin(GPIOB, GPIO_PIN_0); // 东西黄 HAL_GPIO_TogglePin(GPIOB, GPIO_PIN_3); // 南北黄 }逻辑说明这个函数放在 500ms 定时中断里调用或者单独用一个定时器。注意夜间模式下其他灯要灭掉否则红绿黄一起亮看起来像故障。5.2 用串口打印状态日志报告里多一张时序图把每次状态切换的时间戳和状态名通过串口打印出来波特率 115200格式化成 CSV。跑十分钟把日志导入 Excel 画一张时序图报告里放上去比单纯贴代码直观得多。串口打印注意不要在中断里做放主循环里轮询标志位。// 主循环里检测状态切换标志打印日志 if (stateChanged) { stateChanged 0; printf(%lu,%d\r\n, HAL_GetTick(), currentState); }逻辑说明HAL_GetTick()返回系统运行毫秒数currentState是状态枚举值。打印出来的日志可以验证配时是否准确比如东西绿实际持续了 8000ms 还是 8003ms误差在可接受范围内就说明定时器配置没问题。5.3 验证方法用逻辑分析仪抓波形比肉眼靠谱最后说一个我每次做完嵌入式项目都会走的验证步骤用逻辑分析仪抓 6 个 LED 引脚的波形看状态切换的时序对不对。逻辑分析仪便宜的一百多块采样率 24MHz 足够看毫秒级信号。抓到的波形和状态表对照如果东西绿持续 8 秒、黄 2 秒、南北绿 8 秒、黄 2 秒周期 20 秒那就说明代码和硬件都对了。这个步骤写进报告的「测试与验证」章节比只写「烧录后观察灯亮」专业得多。从那以后我每次做嵌入式演示系统都会强制走一遍「逻辑分析仪抓波形 串口日志对照」的流程肉眼观察太容易漏掉偶发的时序错误。希望这份拆解帮到你资源包里的源码、PPT 和报告可以直接拿去改配时参数和状态表按你的路口场景调整就行。本文还有配套的精品资源点击获取