资讯详情

资讯详情

STM32智能路灯控制系统:红外检测、外部中断与串口指令的工程实践拆解

简介这是基于STM32的智能路灯控制系统完整设计文档源于某大学电气工程学院电子设计大赛D题适合电子竞赛参赛者、嵌入式初学者及STM32开发者参考。文档围绕低成本、高性能、易操作的设计目标从硬件与软件两方面展开硬件涵盖单片机最小系统、红外检测执行电路、LED驱动电路、数码管显示及CH340g串口通信模块软件包含路灯控制、光电检测、时间显示等程序模块并给出STM32F103RCT6外部中断与定时器实现思路。方案对比中详细比较了STC89C52、STM32F103RB与STM32F103RCT6的优劣有助于读者理解选型逻辑。压缩包内含1个docx文档约183KB内容包含系统方案论证、电路原理图、控制流程图、Proteus仿真测试结果及主要程序代码结构完整且便于直接查阅。已有1134人学习下载适合需要快速了解智能路灯系统设计流程或准备电子竞赛方案的人群。1. 从课设题目到可复制方案这盏路灯控制系统的真正价值做电子设计竞赛或者课程设计的人大概率都遇到过这类题目要做一个能感知环境亮度、检测行人通过、自动控制灯亮灭的系统。市面上很多方案用STC89C52或者纯逻辑电路堆功能做完能跑就算赢但真要加定时开关、串口调参、速度显示这些功能代码和电路就开始失控。这套完整的智能路灯控制系统主控选的是STM32F103RCT6硬件上有红外检测、LED驱动、数码管显示和LCD1602字符屏软件上把外部中断、定时器、串口指令解析和状态机调度串在一条主循环里物体经过时靠红外下降沿打断主程序串口下发N1 20:30:00这类指令就能定点开灯比单纯的光控路灯多了一层可以远程设置的灵活性。这份文档适合三类人正在做竞赛题、想把课程设计做成实物、或者想了解STM32工程里中断与定时器怎么配合的从业者下文我会把工程结构、关键代码和调试坑一次拆透。2. 系统选型与整体设计为什么是STM32F103RCT6而不是更便宜的51这个系统的核心矛盾是响应要及时、逻辑要能扩展。题目要求检测物体通过后立刻动作同时还要能显示时间、速度和接收上位机指令这些需求决定了主控的选型方向也决定了整个工程的代码组织方式。2.1 单片机选型对比与最终结论文档列了三套方案我把它整理成一张对比表方便你理解作者的选择逻辑对比项STC89C52STM32F103RBSTM32F103RCT6主频12MHz左右72MHz72MHz中断资源6个中断源丰富丰富支持外部中断EXTI定时器2个16位3个4个16位定时器存储8KB Flash128KB Flash256KB Flash48KB RAM串口1个2个2个USART适合场景简单逻辑中小系统多外设中断指令解析STC89C52周期慢是致命伤红外检测触发中断后还要快速处理速度计算和时间记录51在中断里做太多事会导致主循环卡顿STM32F103RB虽然速度快但Flash偏小上LCD1602、数码管、串口指令解析、定时器中断这套组合下来很容易捉襟见肘F103RCT6的256KB Flash给了很宽裕的余量而且它的外部中断可以做到每个引脚独立触发这正好契合红外对射检测的下降沿触发场景。选型结论在低成本范围内RCT6是这类多外设、多中断、还要带显示和通信的设备里最稳的选择。2.2 红外检测模块的工作原理红外感应部分用的是对射式红外检测方案而不是热释电人体感应。热释电传感器只能感知人体移动没法精确给出物体经过的时刻但对射式红外不一样发射管持续发射红外线接收管正常收到光时输出高电平物体从中间穿过时红外线被遮挡接收端输出立刻跳变产生下降沿。这个下降沿接到单片机的EXTI引脚上就能触发外部中断。设计上有一个关键联动红外检测模块的供电由LED驱动电路控制。环境亮度低的时候给红外模块供电此时有人经过就能触发开灯环境亮度高的时候直接切断红外模块电源进入省电模式这时候无论怎么遮挡红外对射管路灯也不会响应。这个逻辑避免了白天有人经过时误触发亮灯的问题同时也省掉了单独测光的传感器成本用环境光敏电路本身去管理整个检测通道的供电。这类设计在真实路灯节能场景里很常见相当于把光控和人控两级闸门串在了一起。2.3 LED驱动与双路控制逻辑路灯负载部分设计成两路LED灯组每路LED的亮灭由两个条件共同决定时间条件和明暗条件。只有当前时刻落在用户设定的时间窗口内并且环境光线低于阈值LED才会点亮。这个逻辑在代码里体现为Judge函数和Control函数的配合状态标志位State_Flag1、State_Flag2分别记录两路LED是否处于定时开启状态Control_Flag1、Control_Flag2标记是否进入了可控制区间。两路LED的好处是可以模拟真实双车道或者人行道双侧路灯的场景同时也能分别设置不同的定时开启时间。比如1号灯从晚上18:30开启2号灯从19:00开启这就是文档里Fixed_time模块干的事。主程序循环里调用Control()完成所有条件判断这种双路设计对后续扩展很有帮助要加第三路路灯只需要复制一组标志位和判断逻辑。3. 硬件电路与引脚分配从原理图到GPIO的对应关系很多人拿到这类工程源码最大的障碍是不知道每个外设接在哪个引脚上。这里我按原工程的头文件和初始化代码把整个引脚分配表完整梳理出来。3.1 单片机最小系统与电源设计STM32F103RCT6属于LQFP64封装电源部分需要3.3V供电典型做法是用AMS1117-3.3从5V降压。最小系统电路包含8MHz主晶振、32.768KHz的RTC晶振如果做时钟、复位电路10K电阻104电容按键、以及BOOT0下拉到GND让芯片从Flash启动。文档里提到用CH340g串口通信CH340g负责USB转TTL把电脑USB信号转成单片机可识别的UART电平。这里有一个常见做法值得注意CH340g模块的TXD接单片机USART1的RXPA10RXD接PA9TX共地一定要接否则串口收发会不稳定。烧录时把BOOT0跳线接到1进入ISP模式写完程序再跳回0也可以直接用SWD下载器省去跳线操作。3.2 全工程引脚分配表根据原工程GPIO初始化代码引脚分配如下外设引脚模式说明LCD1602数据线PA0-PA7推挽输出8位并口数据LCD1602控制线PB5(RS)、PB6(EN)、PB7(RW)推挽输出读写控制数码管段码PC0-PC7推挽输出共阳极段码数码管位选1PB14推挽输出最低位数码管位选2PB15推挽输出第二位数码管位选3PB8推挽输出第三位数码管位选4PA12推挽输出最高位红外输入1PC10浮空输入对射管1下降沿红外输入2PC11浮空输入对射管2下降沿红外输入3PC13浮空输入备用检测点红外输入4PC14浮空输入备用检测点红外输入5PC15浮空输入备用检测点LED1控制PB10推挽输出1号路灯LED2控制PB11推挽输出2号路灯需要特别说明的是PC13、PC14、PC15这三个引脚在F103上默认用于RTC但在做普通GPIO输入时也是可用的前提是关闭RTC相关功能。这个工程用它们做了额外的红外检测输入说明作者对引脚复用处理得干净没有让RTC和EXTI抢占同一资源。3.3 红外对射管的硬件接法红外发射管串联一个100Ω限流电阻后接3.3V或5V电源注意不同供电电压要重新计算限流电阻阻值。红外接收管光电三极管接法更讲究集电极接3.3V发射极接一个10K下拉电阻到地输出信号从发射极引出。正常情况下接收管导通输出为高电平红外被遮挡时接收管截止输出被下拉到低电平产生下降沿。这个接法决定了信号跳变的方向正好匹配EXTI配置的下降沿触发模式。有个细节容易忽略红外发射管和接收管之间距离拉长后输出信号会出现毛刺抖动物体边缘不完全遮挡时可能产生多次跳变。常见做法是在接收管输出端加一个100nF滤波电容同时在代码里做200ms左右的消抖延时。工程代码里专门写了EXTI_PC10_Config这类初始化函数如果中断里加了消抖逻辑那么处理速度测量时要考虑消抖延时会吃掉一部分时间窗口。4. 程序设计核心主循环、外部中断、串口指令的协作机制这套系统的软件架构可以从主函数入口拆开看。主函数干了几件事配置各外设GPIO、初始化LCD和数码管、设置外部中断、串口初始化为9600波特率、配置TIM3定时器作为时间基准然后进入一个while(1)大循环。中断负责感知定时器负责计时串口负责设置主循环负责执行分工很清晰。4.1 主函数骨架与状态机主函数原文较长我整理成一个精简可读的版本逻辑等价int main(void) { u8 t, len; u8 Time_Set[] Set; u8 Time_Set_Flag 0; u8 Fix_Flag 0; u8 *P_Fix_Flag Fix_Flag; /* 1. 外设初始化 */ LCD_Data_GPIO_Config(); // LCD数据口 PA0-PA7 LCD_Com_GPIO_Config(); // LCD控制口 PB5/PB6/PB7 Digital_LED_GPIO_Init(); // 数码管段码位选 LED_GPIO_Config(); // LED1/LED2控制口 LCD1602_Init(); // LCD初始化 delay_init(); // 延时函数初始化 NVIC_Configuration_sys(); // 中断优先级分组 Alarm_Config(); // 报警相关初始化 /* 2. 外部中断初始化红外对射下降沿 */ EXTI_PC10_Config(); EXTI_PC11_Config(); EXTI_PC13_Config(); EXTI_PC14_Config(); EXTI_PC15_Config(); /* 3. 串口 9600 波特率 */ uart_init(9600); /* 4. TIM3 定时器做时间基准 */ Timer_Init(); Clock_Init(); // 给时钟赋初始时间 LED1(OFF); LED2(OFF); /* 5. 主循环 */ while(1) { Judge(); // 判断环境亮度与时间条件 if(USART_RX_STA 0x8000) // 串口收到一帧完整数据 { len USART_RX_STA 0x3fff; printf(...); for(t 0; t len; t) { USART1-DR USART_RX_BUF[t]; while((USART1-SR 0X40) 0); } USART_RX_STA 0; Time_Set_Flag 1; Fix_Flag 1; Control_Flag1 1; Control_Flag2 1; } /* 解析 Set HH:MM:SS 时间设置指令 */ if(Strcmp(USART_RX_BUF, Time_Set) 0 USART_RX_BUF[0] S) if(Time_Set_Flag) { hour (USART_RX_BUF[3]-48)*10 (USART_RX_BUF[4]-48); min (USART_RX_BUF[6]-48)*10 (USART_RX_BUF[7]-48); sec (USART_RX_BUF[9]-48)*10 (USART_RX_BUF[10]-48) - 1; Time_Set_Flag 0; } Control(); // 根据标志位执行开灯/关灯 Fix(P_Fix_Flag); // 处理 N1/F1/N2/F2 定时指令 Dis_time(); // 显示时间 Fixed_Show(); // 显示定时时间参数 } }这段代码有两个设计点值得学习。第一串口接收采用中断接收方式USART_RX_STA的最高位表示一帧数据接收完成主循环检测到这个标志后才去解析数据不会阻塞主循环的执行。第二所有的标志位在串口收到指令后统一置位Control()和Fix()函数再根据标志位执行具体动作这是一种简单的命令-状态-动作模式虽然没有用状态机表但逻辑划分是清晰的。4.2 外部中断配置与事件响应红外对射检测依赖下降沿中断以PC10为例标准配置是这样的void EXTI_PC10_Config(void) { GPIO_InitTypeDef GPIO_InitStructure; EXTI_InitTypeDef EXTI_InitStructure; NVIC_InitTypeDef NVIC_InitStructure; /* 使能 GPIOC 和 AFIO 时钟 */ RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC | RCC_APB2Periph_AFIO, ENABLE); /* PC10 配置为浮空输入 */ GPIO_InitStructure.GPIO_Pin GPIO_Pin_10; GPIO_InitStructure.GPIO_Mode GPIO_Mode_IN_FLOATING; GPIO_Init(GPIOC, GPIO_InitStructure); /* 选择 PC10 对应的外部中断线 */ GPIO_EXTILineConfig(GPIO_PortSourceGPIOC, GPIO_PinSource10); EXTI_InitStructure.EXTI_Line EXTI_Line10; EXTI_InitStructure.EXTI_Mode EXTI_Mode_Interrupt; EXTI_InitStructure.EXTI_Trigger EXTI_Trigger_Falling; EXTI_InitStructure.EXTI_LineCmd ENABLE; EXTI_Init(EXTI_InitStructure); /* NVIC 配置 */ NVIC_InitStructure.NVIC_IRQChannel EXTI15_10_IRQn; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority 1; NVIC_InitStructure.NVIC_IRQChannelSubPriority 0; NVIC_InitStructure.NVIC_IRQChannelCmd ENABLE; NVIC_Init(NVIC_InitStructure); }注意一个容易踩坑的点PC10、PC11、PC13、PC14、PC15这五个引脚在F103上各自映射到不同的EXTI线中断服务函数的入口也不同。PC10和PC11会共用EXTI15_10_IRQHandler这个中断入口而PC13、PC14、PC15分别有独立的EXTI15_10或EXTI9_5入口。你在中断函数里第一件事就是判断EXTI_GetITStatus对每个引脚分别处理否则会误触发。文档里每个引脚单独写了Config函数这种写法虽然看起来重复但可读性很高调试的时候也容易定位。4.3 速度测量与数码管显示物体通过时经过两个红外对射点两个下降沿中断分别记录TIM3的计数值两者相减得到时间差固定间距除以时间差就是平均速度。数码管显示单位是mm最大值9999代表999.9cm也就是10米以内。void Digital_LED_Write(u32 Speed) // Speed 单位 mm { if(Speed 9999) { /* 千位非零显示4位 */ if(Speed / 1000) { Dig_1_On; DATA_OUT_DIG Digital_LED_Table[Speed / 1000]; Delay(0x000fef); Dig_1_Off; Dig_2_On; DATA_OUT_DIG Digital_LED_Table[(Speed - (Speed/1000)*1000) / 100]; Delay(0x000fef); Dig_2_Off; Dig_3_On; DATA_OUT_DIG Digital_LED_Table[(Speed - (Speed/100)*100) / 10]; Delay(0x000fef); DATA_OUT_DIG 0x7f; // 点亮小数点 Delay(0x000fef); Dig_3_Off; Dig_4_On; DATA_OUT_DIG Digital_LED_Table[Speed - (Speed/10)*10]; Delay(0x000fef); Dig_4_Off; } /* 百位非零显示3位最高位熄灭 */ else if(Speed / 100) { Dig_1_Off; Dig_2_On; DATA_OUT_DIG Digital_LED_Table[Speed / 100]; Delay(0x000fef); Dig_2_Off; /* 其余位类推 */ } /* 十位和个位的分支类似不足10时显示0.0 */ } }这段代码就是一个典型的数码管动态扫描4位共阳极数码管在同一时刻只有一位在显示通过快速轮换产生视觉残留。段码表Digital_LED_Table用的是共阳极编码0xC0是数字00xF9是数字10xA4是数字2依次到0x90是数字9。小数点显示通过段码值0x7f它只点亮DP段不影响数字段。Delay(0x000fef)大约几十微秒扫描频率足够让眼睛看不出闪烁。这里的逻辑本质上是用分段判断除法取余来做位拆分虽然是逐位显示的经典写法但有个性能隐患Delay用的是空循环如果主循环里同时还要做LCD刷新和串口解析数码管扫描会占用大量CPU时间。后面调试到响应变慢时可以把Delay换成定时器中断里的刷新标志但这套工程规模不大空循环也能接受。5. 联调避坑五个容易翻车的细节与排查方法从Proteus仿真到实物联调这个系统有五个高频坑位每一个我都见过有人踩进去出不来。下面按现象→原因→解决写清楚。5.1 数码管亮度不均匀低位明显偏暗现象4位数码管显示速度数值时最右边一位明显比左边暗拍照时能看到闪烁条纹。原因位选扫描顺序是Dig_1到Dig_4逐个导通每一位点亮的时间占比只有25%。前面几位刚熄灭马上轮到后面视觉残留时间不足而且空循环Delay在不同位数之间耗时不均低位的显示时间更短。解决把Delay值统一加大到0x002000以上保证每一位都有足够的点亮时间更稳的做法是把扫描从逐位点亮改成每2ms刷新一位用TIM3的Update中断里置一个标志位主循环检测到标志位后刷新一位这样每一位的显示时间完全均匀亮度就一致了。5.2 红外对射长时间工作后温度漂移现象傍晚刚通电时红外检测灵敏人一过就触发运行一两个小时后需要凑得很近才触发甚至不触发。原因红外发射管长时间导通会发热发射功率随温度上升而衰减接收端的信号裕量变小。尤其是电路板靠近LED驱动部分时热量传导让漂移更明显。解决发射管串联电阻适当减小典型值从100Ω改到82Ω增加发射电流接收管的10K下拉电阻可以换成4.7K提高输出电平的驱动能力另外检查PCB布局把红外发射和接收部分与LED功率电阻拉开距离加一点通风空间。5.3 串口下发时间指令后时间没有更新现象用串口助手发送Set 20:30:00后LCD1602上的时间没有变化回显数据正常但系统不响应。原因指令解析代码依赖USART_RX_BUF[0]S的判断但串口接收中断会在收到回车换行后把USART_RX_STA置位。如果串口助手发送时带了额外的\r\nUSART_RX_BUF数组里第一个字节不是S而是\r或空格解析就被拦住了。解决发送端确保只发纯指令不加回车或者在接收中断处理逻辑里先过滤掉\r和\n字符。我在调试时一般先用串口助手发送不含回车的指令测试确认功能后再决定是否要在固件里加过滤。5.4 两路LED灯同时点亮无法独立控制现象设置N1定时开启1号灯后2号灯也跟着亮了关掉1号灯2号灯也一起灭。原因Control()函数里两路LED的判断条件共用了一组环境亮度标志位和全局标志没有在逻辑上把两路完全隔开另一个可能是LED驱动电路里两路继电器或三极管共用了一个基极电阻电气层面已经并联了。解决代码层面检查State_Flag1和State_Flag2是否在Fix()函数里被串扰赋值确保LED1只操作PB10LED2只操作PB11硬件层面用万用表量PB10和PB11的实际电平如果两路引脚电平始终一致说明驱动电路短路或者GPIO配置重复了。5.5 数码管显示速度永远是0物体经过没反应现象红外对射传感器正常手挡一下数码管仍然显示0.0但LED灯会正常点亮。原因速度计算依赖两个红外检测点的时间差如果PC10或PC11对应的EXTI中断没有正确配置时间差算不出来也可能是两个对射管的间距在代码里写死成0导致除数为零的数据被过滤掉了。解决先串口打印两个中断点的计数值确认两个中断都触发了。如果只有一路触发检查EXTI_PC10_Config和EXTI_PC11_Config的引脚号是否写对PC10和PC11共用一个中断入口EXTI15_10_IRQHandler很容易漏判断其中一路。间距参数在Speed_Cal模块里确认是否和实际安装距离一致。6. 验证方法与进阶思路从Proteus仿真到串口协议扩展这套系统做完不是终点怎么证明它工作可靠才是关键。文档里的测试思路我整理成一个可以在自己项目里直接套用的验证流程。第一步是Proteus仿真。仿真时重点观察的是逻辑链路而不是电流参数红外遮挡信号下降沿是否能触发LED状态翻转、定时时间到是否能自动开灯、数码管数值是否按预期跳动。因为Proteus里看不到真实的红外光学特性所以仿真通过只代表逻辑正确。第二步是硬件分模块测试。把电源、最小系统、红外检测、LED驱动、数码管显示分成五个独立模块分别供电测试。电源模块看3.3V输出纹波红外检测模块用示波器看输出信号的下降沿是否干净LED驱动模块量继电器动作时是否有拉低系统电压的现象。每个模块独立跑通后再合到一起出现问题时能快速缩小范围。第三步是串口联调。接上CH340g后用串口助手以9600波特率分别发送这几类指令做验证指令预期行为验证点Set 18:30:00LCD时间跳转到18:30:00时间解析与赋值N1 19:00:0019:00时1号灯自动点亮定时开灯逻辑F11号灯立即关闭强制关闭指令N2 20:00:0020:00时2号灯自动点亮双路独立定时串口指令协议本身也有扩展空间。现有指令集覆盖了时间设置和两路灯的定时开关没有覆盖手动强制点亮和查询状态。我一般会加一条Q指令让系统返回当前环境亮度状态、两路灯的开关状态和内部时钟值调试时能少猜很多问题。另外一个实用的改动是把固定间距值做成可配置项通过串口指令修改这样改变两个红外检测点的安装间距时不用重新编译烧录。还有一个容易被忽视的指标是功耗。系统白天让LED驱动电路切断红外模块供电这个省电设计是好的但主控本身一直在跑LCD1602和数码管常亮也耗电。如果要做电池供电的升级版本可以在数码管进入待机模式后只保留1Hz刷新的时分显示红外检测模块在环境亮度高时用MOS管完全断电主控进入Stop模式用RTC闹钟唤醒做周期性巡检。STM32的Stop模式电流可以降到微安级别比现在的全速运行省得多。这套工程我从头跟到尾之后最大的习惯变化是再做任何带外部中断和串口指令的板子我都会先画一张引脚占用表和指令协议表贴在调试台前面。中断入口重复、引脚复用冲突、指令带回车解析失败这三类问题在这套工程里全遇见过与其靠记忆硬扛不如让表和代码保持同步。希望这篇拆解能帮你在做类似系统时少走这几段弯路。本文还有配套的精品资源点击获取
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →