ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

STM32F103C8T6智能红绿灯实战:GPIO与状态机完整方案

STM32F103C8T6智能红绿灯实战:GPIO与状态机完整方案 STM32F103C8T6这颗芯片在嵌入式圈子里算是名副其实的“入门神U”。一块十几块钱的最小系统板加上几个发光二极管、电阻、按键、面包板和杜邦线就能把课堂上的GPIO、定时器、状态机这些抽象概念变成看得见摸得着的东西。智能红绿灯这个项目我前前后后做了不下五遍每一遍都能踩出点新坑也都能琢磨出点新优化。这篇文章就把我最终跑通的一套完整方案整理出来硬件连接、状态机设计、完整代码一次性给全最后再把我踩过的坑原原本本复盘一遍你照着做大概率能一次点亮。1. 项目概述为什么用一块50块的板子做红绿灯1.1 STM32F103C8T6凭什么是入门首选先聊芯片。STM32F103C8T6属于意法半导体的主流F1系列Cortex-M3内核主频最高72MHz内置64KB Flash和20KB SRAM。放在2025年看这个配置确实不算亮眼但它的生态太成熟了标准外设库、HAL库、寄存器开发三种玩法都有海量教程芯片引脚少、封装是LQFP48手工焊接也能搞定价格更是跌到了十几元级别。更关键的是这颗芯片的国产替代方案已经非常成熟比如GD32F103C8T6、HK32F103C8T6、MM32F103系列引脚基本兼容代码在绝大多数场景下可以直接移植。对于学生、转行做嵌入式的人、或者想快速验证一个Idea的工程师来说它依然是性价比极高的选择。智能红绿灯这个项目用这颗芯片来做的原因很直接它需要控制的资源刚好覆盖F103C8T6的常用外设。三个LED对应三个GPIO输出一个按键对应一个GPIO输入倒计时显示如果要加还可以扩展定时器或者额外驱动数码管。核心的“状态机切换”逻辑用C语言实现非常自然而这颗芯片跑起来毫无压力。做完这个项目你对GPIO输入输出、延时控制、按键消抖、状态机设计这些嵌入式基本功的理解会比看一百遍教程都扎实。1.2 智能红绿灯要解决哪些真实需求红绿灯的“智能”体现在哪里最基本的交通灯控制逻辑是这样的红灯亮一段时间然后切换绿灯绿灯亮一段时间中间必须有黄灯过渡黄灯闪几下再切回红灯。这个周期循环往复不能乱不能卡死。在此基础上我们这次做的“智能”版本加入了一个非常实用的功能夜间模式。深夜车流量小红绿灯如果还按白天的固定周期跑会导致车主在空荡荡的十字路口干等红灯体验很差。真实的交通系统在夜间通常会切换成黄灯闪烁模式提醒过往车辆减速观察、自行通过。我们这个项目就用一个按键来模拟这个夜间/白天切换长按按键进入黄闪模式三盏灯中黄灯以固定频率闪烁再长按一下退出黄闪恢复到正常的红绿黄循环。这个设计虽然简单但它涉及到的技术点非常典型非阻塞延时。你不能在主循环里用Delay(5000)值死等否则按键响应会变得极其迟钝按下半天没反应。正确的做法是用一个系统时基tick来计时主循环不断扫描按键和状态时间到了就切换这样按键随时都能响应。这个思路在后续做多任务、做RTOS的时候会反复用到属于必须掌握的思维模式。1.3 硬件清单一览做这个项目你需要准备的硬件非常简单序号器件名称型号/规格数量备注1主控板STM32F103C8T6最小系统板1块蓝色Pill板或国产替代均可2LED发光二极管红色、黄色、绿色各1个3个5mm直插式选高亮款3限流电阻220Ω1/4W直插3个阻值选取原因见2.3节4轻触按键6mm×6mm直插式1个四脚按下导通5面包板400孔或830孔1块必须跨中间凹槽插元件6面包板跳线公对公若干多准备几根不同颜色7数据线Micro-USB或Type-C1根取决于板子的接口必须支持数据传输提示LED有正负极之分长脚是阳极接正极短脚是阴极接负极内部金属片较小的一端也是阴极。如果接反灯不会亮而且反向电压超过一定值可能损坏LED。2. 硬件连接与电路原理先把电路搭对代码才有意义2.1 引脚分配与连接表引脚分配的核心原则是避开下载口、避开串口、避开晶振引脚。STM32F103C8T6的PA13、PA14、PA15以及PB3、PB4是SWD调试端口PA9、PA10是USART1的收发引脚PB2是BOOT1引脚上电瞬间电平状态不能确定。如果把这些引脚拿去控制LED轻则初始化报错重则下载器直接连不上芯片。我这次选用PA0、PA1、PA2分别控制红黄绿三个LEDPA3作为按键输入。这几个引脚都是普通的GPIO没有复用功能冲突在最小系统板上也都已经引出了排针接线非常方便。板载引脚信号名称连接目标说明PA0RED_LED红色LED阳极串220Ω电阻LED阴极接GNDPA1YELLOW_LED黄色LED阳极串220Ω电阻LED阴极接GNDPA2GREEN_LED绿色LED阳极串220Ω电阻LED阴极接GNDPA3KEY轻触按键一端按键另一端接GND3V3VCCLED正极供电端板载3.3V稳压输出GNDGND电源地和LED阴极公共端必须与板子共地2.2 面包板实操接线步骤面包板这关新手最容易翻车。核心规则每5个孔是一组纵向导通中间凹槽两侧的孔不导通。所以芯片、LED、电阻这些元件必须跨过中间凹槽插否则电路根本不通。具体的接线顺序我习惯按照“先电源后信号最后检查”的步骤来将STM32最小系统板的排针插入面包板一侧让板子横跨面包板中间的凹槽。插入时要保证双排排针分别插在凹槽两侧的孔位上两边各一排这样每根引脚都能独立引出。用红色跳线从板子的3V3引脚接到面包板一侧的电源轨道这条轨道就是后续LED正极的公共供电线。用黑色跳线从GND引脚接到另一侧的地轨道。在面包板上找一块空白区域插入红色LED阳极朝上。在阳极这一行的相邻孔位插入220Ω电阻的一端电阻的另一端插入另一个空余孔位然后用跳线把这行孔和电源轨道连起来。LED的阴极直接通过跳线接到地轨道。注意LED和电阻要串在一条回路上不能并。黄色和绿色LED重复第3步操作。按键的一端插入面包板其中一个引脚用跳线连到PA3同一侧另一个引脚用跳线连到GND。注意按键的四个脚其实是两两导通的要确认是“同一组引脚”分别接信号和地否则按下没反应。最后用跳线把PA0、PA1、PA2分别接到三个LED阳极所在的行电阻前面即靠近电源那一端。接完之后不要急着上电。用万用表的蜂鸣档测一遍电源轨道和地轨道之间不应该短路每个LED的阳极到地之间应该能测到电阻值而不是直接导通PA3对地应该是断开状态按下按键后导通。这些检查只需要一分钟能避免烧板子的惨剧。2.3 限流电阻的计算过程LED不能直接接电源否则电流过大会烧毁。计算限流电阻需要知道三个参数供电电压、LED的正向压降、希望流过LED的电流。STM32F103C8T6最小系统板上的3.3V稳压输出作为LED供电。普通红色LED的正向压降约为1.8V黄色和绿色LED约为2.0V到2.2V。GPIO输出高电平时的电压理论上接近3.3V。我取一个保险值按3.3V供电、LED压降1.8V至2.2V计算电阻上的电压为3.3V减去LED压降结果在1.1V到1.5V之间。驱动普通指示LED电流控制在5mA到15mA就足够亮且安全取中间值10mA。以红色LED为例R (3.3 - 1.8) / 0.01 150Ω。黄色和绿色LED压降更高算出来大概是110Ω到130Ω。电阻买不到这么细分的规格所以统一选择最接近且稍大的常用标称值220Ω。此时红色LED的电流约为(3.3-1.8)/220 ≈ 6.8mA绿色LED约为(3.3-2.0)/220 ≈ 5.9mA亮度完全够用也留足了安全余量。这里有一个实战要点STM32F103C8T6的GPIO灌电流和拉电流能力都不是无限的。虽然F103的GPIO理论上每个引脚最大能输出25mA但整个芯片的功耗限制在那一次性驱动太多高电流负载会出现电压跌落导致芯片复位。本设计每路LED电流不到20mA三个灯同时点亮的总电流不到60mA这对电源和芯片来说毫无压力。3. 核心代码实现状态机驱动完整可运行工程3.1 工程结构与标准库配置代码部分我使用**标准外设库Standard Peripheral Library**来实现因为相比HAL库标准库的代码更直观寄存器操作半透明非常适合理解硬件原理。无论你用的是Keil MDK还是STM32CubeIDE只要能编译ARM Cortex-M3的工程代码结构都一样。工程结构不复杂核心就一个main.c。但前提是你得把标准库的启动文件、系统初始化文件和外围设备驱动文件都正确添加到工程里并且定义好启动宏。如果你用的是Keil在Options for Target里的C/C选项卡中Preprocessor Symbols的Define栏填入STM32F10X_MD,USE_STDPERIPH_DRIVER两个宏缺一不可。第一个宏告诉固件库你用的是中等容量的F103芯片第二个宏启用标准外设库的驱动函数。系统时钟初始化部分最小系统板通常外接8MHz晶振通过PLL倍频到72MHz作为系统时钟。标准库的SystemInit()函数在启动文件调用main之前就会执行它会配置Flash等待周期、PLL倍频系数把系统时钟稳定到72MHz。这些不需要你自己写但要确保启动文件里的SystemInit外部声明存在。3.2 延时函数与时间基准这个项目我使用非阻塞延时核心是借助SysTick定时器。SysTick是Cortex-M3内核自带的24位递减计数器配置好之后每个系统时钟周期减一减到0可以触发中断。我们把它配置成每1ms产生一次中断在中断服务函数里递增一个全局变量uwTick这个变量的值就代表了系统从上电开始经过的毫秒数。volatile uint32_t uwTick 0; void SysTick_Init(void) { // SystemCoreClock 72000000设置SysTick时钟源为HCLK并开启中断 if (SysTick_Config(SystemCoreClock / 1000)) { while (1); } } void SysTick_Handler(void) { uwTick; } uint32_t GetTick(void) { return uwTick; }SysTick_Config这个库函数做了两件事设置重装载值RELOAD为SystemCoreClock / 1000即72000这样每隔72000个时钟周期产生一次中断正好对应1ms同时把SysTick的中断优先级设为最低并启动定时器。有了这个毫秒时基判断时间间隔就变得非常简单uint32_t start GetTick(); // 做其他事情... if (GetTick() - start 5000) { // 5秒时间到 }这个写法有一个精妙之处因为uwTick是32位无符号整数即使它回绕到0GetTick() - start依然能得到正确的时间差。这就是经典的“无符号数减法回绕”技巧在嵌入式开发中广泛使用。3.3 按键扫描与软件消抖按键的机械结构决定了它按下和释放的瞬间触点会发生多次快速通断这就是抖动。如果不做处理一次按键可能被系统识别成多次触发状态机会乱跳。软件消抖的标准做法检测到按键引脚变低按下后延时20ms再次读取如果仍然是低说明确实是按下而不是抖动。接着要等待按键释放释放后还需要再次延时20ms消抖这样一次完整的按键动作只被记录一次。长按检测的实现逻辑和短按不同短按是“按下→释放”长按是“按下→持续按住”。本项目的夜间模式切换我用的是短按但为了演示我加了一个长按功能在按键保持按下超过2秒后切换夜间模式。这样同一个按键可以承担两种功能也算是一种实用的交互优化。uint8_t Key_Scan_ShortPress(void) { static uint8_t key_state 0; // 0表示未按下1表示已按下未处理 if (GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_3) RESET) { Delay_Ms(20); if (GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_3) RESET) { // 确认按下 if (key_state 0) { key_state 1; // 等待释放 while (GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_3) RESET); Delay_Ms(20); return 1; } } } else { key_state 0; } return 0; }这段代码里static uint8_t key_state是重点。它保证了在按键释放之前无论主循环扫描多少次都不会重复返回“按下”事件。while等待释放是阻塞式的对于这种单按键场景完全够用但如果你的系统里还有别的实时任务建议改成非阻塞的按键状态机。3.4 红绿灯状态机完整代码状态机是这个项目的灵魂。我们把红绿灯的运行过程抽象成几个状态红灯、绿灯、黄灯每个状态的持续时间和切换条件都是明确的。用switch-case实现状态机代码清晰、易扩展。以后想加左转灯、行人灯只要增加枚举值和对应case分支就行。完整的main.c代码如下这份代码在Keil MDK环境下配合标准外设库V3.5版本可以直接编译下载运行。我把关键逻辑都写在注释里了。#include stm32f10x.h #include stm32f10x_gpio.h #include stm32f10x_rcc.h #include stm32f10x_exti.h #include misc.h // 引脚定义 #define LED_RED_PIN GPIO_Pin_0 #define LED_YELLOW_PIN GPIO_Pin_1 #define LED_GREEN_PIN GPIO_Pin_2 #define LED_PORT GPIOA #define KEY_PIN GPIO_Pin_3 #define KEY_PORT GPIOA // 状态定义 typedef enum { STATE_RED 0, STATE_GREEN, STATE_YELLOW, STATE_YELLOW_FLASH } TrafficState; // 时间参数毫秒 #define RED_TIME 5000 #define GREEN_TIME 5000 #define YELLOW_TIME 2000 #define FLASH_HALF_TIME 500 // 全局变量 volatile uint32_t uwTick 0; TrafficState currentState STATE_RED; uint32_t stateStartTick 0; uint8_t yellowFlashMode 0; // 0正常模式1夜间黄闪模式 uint8_t flashToggle 0; // 函数声明 void SysTick_Init(void); void GPIO_Config(void); void LED_Control(TrafficState state); void Delay_Ms(uint32_t ms); uint8_t Key_Scan_ShortPress(void); // SysTick中断服务函数 void SysTick_Handler(void) { uwTick; } uint32_t GetTick(void) { return uwTick; } // 毫秒延时阻塞式用于消抖和简单等待 void Delay_Ms(uint32_t ms) { uint32_t start GetTick(); while (GetTick() - start ms); } // GPIO初始化 void GPIO_Config(void) { GPIO_InitTypeDef GPIO_InitStructure; // 开启GPIOA时钟 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); // PA0、PA1、PA2配置为推挽输出50MHz GPIO_InitStructure.GPIO_Pin LED_RED_PIN | LED_YELLOW_PIN | LED_GREEN_PIN; GPIO_InitStructure.GPIO_Mode GPIO_Mode_Out_PP; GPIO_InitStructure.GPIO_Speed GPIO_Speed_50MHz; GPIO_Init(LED_PORT, GPIO_InitStructure); // PA3配置为带上拉输入 GPIO_InitStructure.GPIO_Pin KEY_PIN; GPIO_InitStructure.GPIO_Mode GPIO_Mode_IPU; GPIO_Init(KEY_PORT, GPIO_InitStructure); } // LED控制 void LED_Control(TrafficState state) { // 先全部熄灭 GPIO_ResetBits(LED_PORT, LED_RED_PIN | LED_YELLOW_PIN | LED_GREEN_PIN); switch (state) { case STATE_RED: GPIO_SetBits(LED_PORT, LED_RED_PIN); break; case STATE_GREEN: GPIO_SetBits(LED_PORT, LED_GREEN_PIN); break; case STATE_YELLOW: GPIO_SetBits(LED_PORT, LED_YELLOW_PIN); break; case STATE_YELLOW_FLASH: // 黄闪模式由外部逻辑控制闪烁 break; default: break; } } // 按键扫描短按带消抖 uint8_t Key_Scan_ShortPress(void) { static uint8_t key_state 0; if (GPIO_ReadInputDataBit(KEY_PORT, KEY_PIN) RESET) { Delay_Ms(20); // 消抖 if (GPIO_ReadInputDataBit(KEY_PORT, KEY_PIN) RESET) { if (key_state 0) { key_state 1; while (GPIO_ReadInputDataBit(KEY_PORT, KEY_PIN) RESET); // 等待释放 Delay_Ms(20); return 1; } } } else { key_state 0; } return 0; } int main(void) { // 初始化系统时钟启动文件已调用SystemInit SysTick_Init(); GPIO_Config(); currentState STATE_RED; stateStartTick GetTick(); while (1) { // 按键检测 if (Key_Scan_ShortPress()) { yellowFlashMode !yellowFlashMode; currentState STATE_RED; stateStartTick GetTick(); } // 夜间黄闪模式 if (yellowFlashMode) { if (GetTick() - stateStartTick FLASH_HALF_TIME) { stateStartTick GetTick(); flashToggle !flashToggle; if (flashToggle) { GPIO_SetBits(LED_PORT, LED_YELLOW_PIN); } else { GPIO_ResetBits(LED_PORT, LED_YELLOW_PIN); } } } else { // 正常红绿灯状态机 switch (currentState) { case STATE_RED: LED_Control(STATE_RED); if (GetTick() - stateStartTick RED_TIME) { currentState STATE_GREEN; stateStartTick GetTick(); } break; case STATE_GREEN: LED_Control(STATE_GREEN); if (GetTick() - stateStartTick GREEN_TIME) { currentState STATE_YELLOW; stateStartTick GetTick(); } break; case STATE_YELLOW: LED_Control(STATE_YELLOW); if (GetTick() - stateStartTick YELLOW_TIME) { currentState STATE_RED; stateStartTick GetTick(); } break; default: break; } } } }这段代码有几个地方需要额外注意。第一LED_Control每次被调用都会先把三盏灯全部熄灭再点亮目标灯这样能避免因为上次状态残留导致多盏灯同时亮的逻辑错误。第二状态切换时stateStartTick记录的是进入这个状态的时刻每次只判断时间差而不是用累加计数这样即使主循环偶尔被其他事卡住状态切换的时刻依然精确。第三夜间黄闪模式我用了独立的flashToggle变量来交替亮灭闪烁周期是FLASH_HALF_TIME即500ms一个完整的亮灭周期是1秒。这不是标准交通规范中的黄闪频率但作为功能演示已经足够。真要做产品建议把闪烁频率调到1Hz到2Hz之间并配合硬件定时器而非阻塞延时。4. 常见问题与排查技巧实录4.1 引脚冲突排查程序烧不进去的前三名原因PA13、PA14、PA15、PB3、PB4这五个引脚在F103上默认是SWD调试端口。如果你把它们配置成了普通GPIO第一次烧录时还能正常下载但程序跑起来之后调试口功能被改写了第二次想烧录就提示找不到设备。这个坑我用PA15踩过一次后来养成一个习惯任何新工程先把这五根引脚避开的方案画出来再分配功能。还有BOOT0和BOOT1引脚。如果BOOT0被拉高芯片上电后会进入ISP引导模式而不是运行Flash里的程序现象是程序下载成功但怎么跑都跑不起来。最小系统板上BOOT0通常默认下拉但如果你自己在面包板上飞线接错了就会出这种问题。排查方法很简单按复位键如果芯片还是没反应用万用表量BOOT0引脚的电压应该接近0V。另一个常见问题是下载器供电不足。一些廉价ST-Link的3.3V输出能力很弱接上板子和外围电路后电压跌落导致芯片反复复位。我用过不少国产ST-Link遇到最多的问题不是通信失败而是VCC输出电流不够。解决办法不要用ST-Link给整块面包板供电用USB转TTL模块的5V输出或者独立的USB电源给板子的5V引脚供电ST-Link只负责SWD通信这样最稳定。4.2 供电与干扰问题为什么LED一多芯片就复位这个小标题描述的场景很典型只点亮一盏灯的时候一切正常三盏灯同时点亮系统就重启。问题几乎可以肯定是供电不足。虽然3.3V稳压芯片在理论上能提供300mA以上的电流但很多蓝色Pill板上的AMS1117-3.3是从USB的5V取电的而USB口本身就有500mA的限流保护再加上劣质USB线线阻大压降严重芯片端的实际电压可能只有4.5V甚至更低。这个时候一旦三个LED同时点亮瞬时电流冲击就会把电压拉到3.3V以下MCU检测到欠压直接复位。解决思路有三个层面。第一控制单路电流把限流电阻从220Ω换成470Ω甚至1kΩ牺牲一点亮度换取系统稳定性对指示灯来说完全够亮。第二在芯片的3.3V和GND引脚之间并联一个10μF电解电容和一个100nF陶瓷电容靠近引脚放置能够明显抑制电压跌落。第三供电不要从USB取直接用外部5V电源适配器接到板子的5V引脚或者用面包板专用的电源模块。排查这类问题有一个高效的办法用示波器看3.3V引脚的波形或者退一步用万用表的电压档测量按下按键和切换LED瞬间的电压变化能看到明显的跌落。如果没有示波器把LED换成高亮型并加大限流电阻是成本最低的解法。4.3 按键失灵与误触抖动不是唯一元凶按键问题分两种表现按一下,程序跳了好几次或者怎么按都没反应。跳多次的原因十有八九是消抖没做好。我见过不少教程里的消抖代码只做了“按下延时确认”却没有等待释放。用户按一下键按下确认后函数马上返回1但此时手指还没松开主循环又执行了一次按键扫描按键引脚依然是低电平于是又返回一次1。一次物理按键就变成了两次逻辑按键。所以消抖代码里while (GPIO_ReadInputDataBit RESET);等待释放这一步是必须的。补充说明等待释放用阻塞式while有一个潜在风险——如果用户按住按键不放主程序会卡死在这个循环里。严肃项目里应该用带超时机制的非阻塞按键状态机但对于学习项目阻塞等待的代码更简单直观不引入额外复杂度。没反应的原因除了接线错误还有一个容易忽视的点PA3默认复用功能。PA3在F103上同时也映射为USART2的RX引脚和TIM2的CH4通道。如果你在初始化GPIO之前开启了串口或者定时器那么PA3的输入路径可能被复用功能占据普通GPIO读取不到正确的电平。解决办法GPIO初始化代码放在外设初始化代码之前并且确认没有其他地方对PA3执行了GPIO_PinRemapConfig。还有一种情况按键引脚悬空时读到的是不确定电平。配置GPIO_Mode_IPU启用了内部上拉默认高电平按下接地变低这才有了可靠的逻辑电平。如果你漏掉了上拉配置用万用表量按键引脚电压数字会乱跳程序自然无法正确判断。4.4 烧录失败与国产芯片兼容性一次把玄学变科学烧录失败是最让人头疼的问题因为报错信息五花八门“No target connected”最常见“Error: Flash Download failed - Cortex-M3”次之“RDDI-DAP Error”偶尔出现。我的排查顺序非常固定第一步检查ST-Link引脚接线。SWD只需要四根线SWDIO、SWCLK、GND、3.3V3.3V用于电平参考不是必须但接上更稳。这四根线一一对应不能接反。尤其注意SWCLK和SWDIO是板上丝印标注的不同厂家的板子引脚位置可能不同不要凭经验想当然。第二步检查目标板供电。目标板没电ST-Link自然什么都搜不到。单独用USB给目标板供电再连接ST-Link。第三步如果以上都排除了还是连不上把BOOT0拉高芯片进入ISP模式然后再连接下载器很多情况下能连上。连上后先执行整片擦除Erase把芯片里可能存在的错误程序清掉再把BOOT0拉回低电平重新上电烧录。国产替代芯片在这里有一个额外需要注意的点GD32、MM32这些芯片虽然宣称“兼容STM32”但烧录算法FLM文件不同。如果你用Keil烧录GD32器件型号选成STM32F103C8大概率能烧进去但也有概率烧一半报错。解决办法是去对应厂商官网下载专门的器件支持包Pack和烧录算法在Keil的Device选项里选对应的国产型号一次到位。另外有些GD32芯片的Flash写入速度要求比较严格下载器速度设置太高可能导致校验失败。在Keil的Settings里把SWD Clock从默认的4MHz降到1MHz能解决大多数“烧录成功但校验失败”的诡异问题。4.5 调试技巧没有屏幕怎么看清状态机很多初学者遇到程序不按预期跑第一反应是加打印语句但串口打印在这个项目里有点麻烦因为你得额外接USB转TTL模块。其实有一个更优雅的办法用调试器的Watch窗口直接看变量。在Keil的Debug模式下把currentState、uwTick、yellowFlashMode这几个关键变量添加到Watch窗口然后全速运行。你可以实时看到状态机当前停在哪个状态、uwTick是否在递增、按键标志有没有置位。如果发现uwTick不动说明SysTick配置有问题如果状态一直停在STATE_RED说明时间判断条件没满足。如果连调试器都没有也没关系用LED本身就能反馈。在调试阶段我习惯在临时加的测试代码里让某个LED跟随特定事件闪烁比如每次按键按下时让绿灯闪一下这样不用屏幕也能确认事件是否发生。调试完再把这些临时代码删掉。5. 项目扩展思路从红绿灯到智能交通红绿灯能做到这一步已经覆盖了嵌入式开发中最核心的几个知识点。但如果你想让这个项目更有竞争力有几个扩展方向非常值得尝试。倒计时显示外接两个7段数码管用STM32的定时器实现1秒定时刷新把当前状态剩余秒数显示出来。这个扩展能练习段码表设计、动态扫描原理、定时器中断优先级设置。多路口联动用两块STM32F103C8T6一块做主控一块做从控通过串口或者I2C通信模拟一个十字路口两个方向红绿灯的时序配合。这个扩展能锻炼通信协议设计能力也是真实的交通信号控制系统的简化版。光敏传感器自动切换夜间模式在PA4引脚接一个光敏电阻模块当环境光照低于阈值时自动进入黄闪模式光照恢复后自动退出。这个扩展比按键切换更贴近“智能”的定义锻炼ADC采集和阈值判断逻辑。这些扩展用到的外设资源F103C8T6都齐备定时器、串口、ADC、I2C、SPI一个不差。这也是我强烈推荐拿这颗芯片练习的原因——它不是最强的芯片但它能让你把所有常用外设都练一遍而且成本极低烧坏了也不心疼。根据我的经验做这个项目最容易掉进去的陷阱一是急着写代码硬件还没确认就开干结果接线错误浪费大量时间二是把所有逻辑都堆在延时里结果按键响应体验极差三是遇到问题不按逻辑排查上来就换板子换芯片换电脑。把本文的硬件检查步骤走一遍把非阻塞状态机的代码吃透把常见的几个坑提前避开这个项目一天之内做完没有压力。做得多了你会发现红绿灯背后这套“状态机时间基准事件响应”的框架几乎可以套用到所有嵌入式交互设备上。
返回列表