
开源智能药盒/老人用药管理系统的完整实现笔记家里有老人的朋友应该都经历过这种场景药盒上写了早中晚三次用药但老人要么忘记吃要么重复吃要么时间完全错乱。尤其是一些需要长期服药的慢性病老人一顿漏服可能看不出什么时间长了问题就大了。我最初做这个STM32智能药盒项目就是想解决“按时吃药”这件看似简单、实际上特别容易出问题的小事。这套方案我命名为“老人用药管理系统”主控用的是STM32F103C8T6配套完成了硬件原理图、驱动代码和Proteus仿真工程全部开源。整个系统的核心价值在于它不只是一个定时器加一个舵机的玩具而是把“提醒—确认—记录—告警”这条完整的用药闭环做通了。老人到点听到蜂鸣器提示指示灯闪烁按一下确认键表示“我吃了”如果超过设定时间没人按键系统会通过串口向家人终端发送未服药告警。整个过程不需要老人有额外的学习成本也不需要子女时刻盯着。这篇文章我会把整个项目的设计思路、硬件选型、电路原理、代码结构、仿真调试以及我实际踩过的坑全部拆开讲清楚。不管你是准备拿这个题目做毕业设计还是真的想给家里老人做一个能用的药盒都可以直接参考这套方案。项目里用到的代码、原理图和仿真文件我做了统一打包文末会说明文件结构和怎么用。1. 项目整体设计思路与方案选型1.1 功能需求到底该怎么拆做嵌入式项目最容易犯的毛病就是一上来先翻数据手册或者找芯片根本不知道自己要做什么。我习惯的做法是先列需求越具体越好然后再倒推硬件和软件。这个智能药盒我拆出了六个核心功能实时时钟显示支持断电走时掉电不丢时间三组用药时间设定对应早中晚三次用药可独立开关到点触发蜂鸣器提醒同时LED闪烁提示患者“该吃药了”按键确认用药按下后消除提醒并在LCD上记录本次服药超时未确认则通过串口/GSM模块向监护端发送告警信息预留温度检测功能实时显示环境温度这是仿真的彩蛋也是实际硬件可以扩展的这六个功能看起来不多但每个都对应一个独立的硬件模块。比如实时时钟需要独立的RTC芯片提醒需要蜂鸣器和LED确认需要按键告警需要串口或通信模块。需求一拆完硬件选型就水到渠成了。1.2 主控芯片选择为什么是STM32F103C8T6STM32F103C8T6这颗芯片在开源项目里的地位差不多相当于手机界的“百元机皇”。72MHz主频、64KB Flash、20KB SRAM集成了USART、I2C、SPI、ADC、定时器等常用外设价格在几块钱到十几块钱之间开发资料多得铺天盖地还能在Proteus里完美仿真这几个条件叠加起来做智能药盒这种外设不算多的项目简直绰绰有余。可能有人会问用51单片机不是更简单我承认纯功能实现用STC89C52也能跑但差距就在“可扩展性”和“开发效率”上。这个项目里我用了多个定时器、RTC通信、串口中断、PWM舵机控制51单片机做这些虽然能行但代码复杂度和调试成本都会明显上升。STM32丰富的库函数和CubeMX图形化配置能省掉大量底层初始化时间后面如果想加联网模块、触摸屏、语音播报性能也不会成为瓶颈。1.3 外围模块搭配与选型思路主控定了接下来就是逐块选型功能模块选型方案选型理由实时时钟DS1302功耗低走时误差可接受日误差约1秒价格便宜Proteus自带模型显示模块LCD1602字符型液晶16列2行能显示时间和状态调试方便仿真友好提醒模块有源蜂鸣器 双色LED蜂鸣器声音大老人能听见LED用于视觉辅助提示输入模块独立按键 x4结构简单去抖可靠一个菜单键三个操作键符合老人使用习惯执行模块SG90舵机模拟自动出药机构在仿真中用PWM波形验证控制逻辑温度传感器DS18B20单总线数字传感器一根线就能读温度扩展方便通信模块USART1串口 / 预留SIM800C仿真中用虚拟终端模拟GSM通信实物可以接SIM800C发短信我特别想强调一下DS1302这个选型。DS3231精度更高但在Proteus里没有现成模型而且价格是DS1302的十倍不止。对于这个项目来说DS1302已经够用而且它在Proteus里仿真效果很好晶振和电池电路也简单。如果你做实物又实在在意精度后面可以换成DS3231代码只需要改底层读写函数业务逻辑完全不用动。1.4 系统整体架构数据怎么流转从数据流的角度看这个系统的逻辑其实很清晰。DS1302负责产生时间基准系统不断读取当前时间并刷新到LCD上用户通过按键设定三组用药时间存入STM32内部Flash的结构体变量中主循环每次读取时间后与用药计划表中的时间进行匹配匹配成功则触发提醒状态机提醒状态机驱动蜂鸣器和LED等待用户按键确认如果超时未确认则通过串口把告警信息发送到监护端。这个架构是典型的“事件驱动状态机”模式没有跑RTOS用裸机主循环加定时器中断就够了。为什么不上FreeRTOS因为这个项目任务数量少、实时性要求不高裸机代码反而更直观学习门槛也低调试更容易。真上了RTOS反而要给每个任务分配栈空间、处理信号量对新手不友好没必要。2. 硬件部分原理图设计与关键电路解析2.1 STM32最小系统这部分不能省最小系统是STM32能跑起来的底线条件包括电源电路、复位电路、时钟电路和BOOT启动配置。看原理图时你会发现STM32F103C8T6其实只需要一个8MHz晶振加两个20pF负载电容复位引脚接一个10kΩ上拉电阻和一个100nF对地电容这里电容的作用是滤除复位引脚的毛刺干扰保证上电时复位信号稳定。电源部分我用了AMS1117-3.3稳压芯片输入5V输出3.3V输入输出各加一个10μF和100nF电容做高低频滤波配合。有些低成本方案会省略稳压直接给3.3V但如果你要接舵机、GSM模块这种功耗较大的外设还是老老实实用稳压芯片否则电压跌落会导致系统莫名其妙复位。这里还有一个细节BOOT0和BOOT1引脚各接一个10kΩ下拉电阻到地保证芯片从Flash启动不会在调试时意外进入ISP模式。2.2 DS1302时钟电路32.768kHz晶振的匹配DS1302外围电路非常简单一个32.768kHz晶振加两个6pF负载电容再接一颗3V纽扣电池做断电备份。晶振频率必须精确匹配32.768kHz是32768Hz正好是2的15次方秒计数器就是靠这个频率分频得到的频率偏了就会走时不准。我在设计时特别注意了晶振两端的电容容值有些参考设计用12.5pF甚至15pF导致振荡电路起振困难或者频率偏移明显。实际测试下来6pF是比较稳的匹配值。另外DS1302的VCC1接主电源VCC2接备份电池主电源掉电后自动切换到电池供电这个切换是芯片内部完成的。仿真时这个电路可以简化但做实物建议完整保留。2.3 LCD1602接口4线模式还是8线模式LCD1602是5V逻辑器件STM32是3.3V电平匹配问题必须处理。我在这版设计中直接用4线模式只用D4~D7四个数据引脚加RS、RW、EN三个控制脚一共7个GPIO。相比8线模式省了4个引脚而且数据传输操作只需要两个周期速度也够用。电平转换方面我做实物时在数据线上串联了1kΩ限流电阻再接到STM32这样能防止5V高电平倒灌进3.3V引脚。Proteus仿真不用纠结电平问题因为仿真器不模拟绝对电压危害但做实际PCB时必须加上这个保护电阻。LCD的对比度调节电位器我选了10kΩ接在V0引脚上调到中间位置显示最清晰。RW引脚因为只写不读直接接地处理既能省一个GPIO也让代码少一个控制引脚要管理。2.4 舵机与蜂鸣器驱动抵消电流冲击舵机SG90的堵转电流能到几百毫安蜂鸣器也有几十毫安这两个负载如果直接从STM32的GPIO口取电芯片绝对扛不住必须加驱动电路。舵机的信号线直接接STM32的TIM引脚输出PWM电源和地单独从5V供电走蜂鸣器我用一个NPN三极管S8050做开关管GPIO通过1kΩ电阻驱动三极管基极集电极接蜂鸣器负极蜂鸣器正极接5V发射极接地。GPIO输出高电平时三极管导通蜂鸣器响输出低电平时截止蜂鸣器停。这样设计的关键原因是STM32的GPIO最大输出电流约25mA根本驱动不了蜂鸣器而三极管只需很小的基极电流就能控制大的集电极电流。至于舵机的5V电源我从USB输入的5V直接取在电源入口加上一个大容量电解电容470μF缓冲电流冲击防止舵机堵转时把5V电压拉垮。别小看这个电容没有它的项目我实测过舵机一转系统就重启。2.5 DS18B20与串口电路一个上拉一个反相DS18B20是单总线设备数据线是开漏输出必须在数据线上接一个4.7kΩ上拉电阻到3.3V否则通信不稳定。这是我做这个模块时踩过的坑一开始没加上拉电阻温度读取总是偶发失败加了之后问题立刻消失。串口部分用CH340G做USB转TTL连接STM32的PA9USART1_TX和PA10USART1_RX注意交叉连接——板子的TX接模块的RX板子的RX接模块的TX。CH340G的TXD和RXD到STM32的连接是相反的对应关系。关于SIM800C GSM模块我在原理图上预留了接口实物阶段可以直接把它的UART接到STM32的USART2上板子就能通过短信发送告警信息了。仿真阶段我用Proteus的Virtual Terminal虚拟终端模拟了这个环节的效果。3. 软件部分代码架构与核心功能实现3.1 工程文件的组织方式这个项目的代码我用了标准的分层结构好处是模块清晰、移植方便找Bug时不用在几千行代码里大海捞针。工程目录按功能分了几个文件夹User/ 主函数 main.c、stm32f10x_it.c 中断服务 Hardware/ DS1302.c、LCD1602.c、DS18B20.c、Key.c、Buzzer.c、Servo.c System/ 延时函数、串口打印、GPIO初始化 App/ 用药逻辑状态机 medicine.c、菜单交互 menu.c这种组织方式是我做多个项目之后固定的习惯。每个外设驱动文件只负责最底层的“怎么操作这个芯片”比如DS1302.c里的函数就是读写寄存器、设置时间、读取时间真正的业务决策放在App层的medicine.c里比如“当前时间到了设定的吃药时间提醒状态机应该进入响铃状态”。这样分层硬件和业务不扯皮后期改任何一层都不影响另一层。3.2 DS1302驱动读时序的顺序敏感DS1302的驱动代码其实不复杂就是一个双向串行通信协议。它有个特点值得新手注意RST引脚拉高后启动通信每个字节先发命令字节最高位固定为1最低位是读写控制位命令字节的第0位决定接下来是读还是写。写一个字节是低位在前读一个字节也是低位在前数据在时钟上升沿被采样。我贴一段核心读取函数方便理解// 读取DS1302某寄存器地址 uint8_t DS1302_ReadByte(uint8_t addr) { uint8_t data 0; uint8_t i; GPIO_WriteBit(DS1302_RST_PORT, DS1302_RST_PIN, Bit_SET); // 先发送命令字节读命令 DS1302_WriteByte(addr | 0x01); // 再读数据字节 for(i 0; i 8; i) { data 1; if(GPIO_ReadInputDataBit(DS1302_IO_PORT, DS1302_IO_PIN)) data | 0x80; GPIO_WriteBit(DS1302_SCLK_PORT, DS1302_SCLK_PIN, Bit_SET); Delay_us(2); GPIO_WriteBit(DS1302_SCLK_PORT, DS1302_SCLK_PIN, Bit_RESET); Delay_us(2); } GPIO_WriteBit(DS1302_RST_PORT, DS1302_RST_PIN, Bit_RESET); return data; }注意这里的顺序读数据时是每个SCLK上升沿从数据线采样一位先读到的是最低位bit0所以我的移位方式是把已有数据右移、新数据放到最高位8次之后data就恢复成正常的字节序了。这个顺序如果搞反了读出来的时间就是乱的而且还不容易发现错在哪我调试时就吃过这个亏。3.3 按键扫描与消抖不要用delay按键消抖是嵌入式入门必踩的坑按键按下去的瞬间机械触点会经历一个约5~10ms的抖动期电平在高低之间快速跳变如果不消抖一次按压会被识别成多次。常见的做法有两种一是延时后再读一次二是状态机扫描。我强烈建议用状态机扫描不要用delay消抖。原因很简单main函数里还有一个大循环要处理LCD刷新、时间匹配、蜂鸣器状态切换如果一个按键用了20ms的delay系统整体响应就会卡顿。我用的消抖方案是定时器中断配合循环扫描每10ms进一次中断读取四个按键状态把状态值存入一个环形缓冲当连续两次采样值相同且持续超过30ms才认为是有效按键事件。这样既消抖又不阻塞主循环。3.4 用药状态机的关键设计从“到点”到“确认”的完整链路整个项目最有价值的逻辑就是用药状态机它处理的是用户的“用药流程”空闲状态IDLE系统正常运行LCD显示时间无提醒。铃响状态RING到了设定吃药时间蜂鸣器每隔500ms响一次LED闪烁LCD显示“TAKE MEDICINE”。确认状态CONFIRMED用户按下确认键蜂鸣器停止LCD显示“TAKEN”这次用药完成。超时告警状态ALARM从进入RING开始计时超过10分钟可配置无确认串口发送告警消息同时蜂鸣器声音模式改变进入更急促的提醒节奏。用状态机而不是用多标志位堆积逻辑最直接的好处是每个状态处理函数只需要关注“当前状态要干什么”和“什么事件会触发状态切换”不会出现在几十个if嵌套中迷失的情况。我实测下来之前用标志位写这个逻辑反复出现倒计时清零后忘记恢复设置位的问题改成状态机之后整个流程一目了然。状态切换的代码骨架大致是void Medicine_StateMachine(void) { switch(med_state) { case MED_IDLE: if(Time_Match(med_plan[match_index].hour, med_plan[match_index].min)) { med_state MED_RING; ring_timer 0; } break; case MED_RING: Buzzer_Control(ring_timer % 20 0); if(Key_ConfirmPressed()) { Led_Green(); med_state MED_CONFIRMED; Save_MedRecord(match_index); } if(ring_timer ALARM_TIMEOUT) med_state MED_ALARM; break; case MED_CONFIRMED: // 确认后维持1分钟显示“TAKEN”之后回到IDLE break; case MED_ALARM: Buzzer_Control(ring_timer % 5 0); Uart_SendString(ALARM: NOT TAKEN!\r\n); if(Key_ConfirmPressed()) med_state MED_CONFIRMED; break; } }3.5 温度采集DS18B20的ROM匹配问题DS18B20占比不大但我还是加了主要用来实时显示环境温度并写入给药盒的“存放环境检查”功能。单总线的操作时序比较严格初始化时主机拉低至少480μs然后释放等待传感器拉低60~240μs响应写“0”就是拉低60~120μs写“1”就是拉低1~15μs然后释放。因为一个总线上只挂了一个DS18B20我直接用了跳过ROMSkip ROM命令不读取64位序列号省去匹配过程。如果以后要挂多个传感器再把读ROM的代码补上。温度转换完成后读回来的是16位二进制补码正数温度直接乘以0.0625就得到摄氏温度负数需要先取反加一再乘。还有一点DS18B20的上电默认精度是12位转换时间约750ms所以读温度的程序不能太频繁我放在了主循环每2秒采一次。3.6 LCD菜单交互三级菜单怎么设计老人操作系统的交互逻辑必须简单直接我设计了两级菜单结构一级菜单是待机界面显示时间二级菜单是设置界面。按下“设置”键进入时间设置此时小时位闪烁按“加”/“减”调整再按“确认”进入分钟设置最后确认退出再按“设置”可以切换到用药时间1/2/3的设置流程和设置当前时间一样。做这个菜单要注意一个细节参数的修改范围要限制小时0~23分钟0~59用户按“加”到上限时应该回绕到0。另外光标闪烁的实现是靠定时器中断里对某个全局变量的取反标志位来完成LCD显示函数里检测到光标所在位置根据闪烁标志决定显示数字还是显示空格。3.7 串口打印与调试一个出色的调试助手我习惯在所有外设初始化完成后通过串口打印一个启动横幅包含固件版本号、RTC初始时间等关键信息。这样串口一开就能立刻判断系统是否正常启动、RTC有没有正确读到时间。开发阶段这个习惯能省不少事。除了启动信息串口还承担了告警消息的输出。实际硬件接GSM模块时可以把告警发送函数替换为GSM的AT指令调用向指定手机号发送短信仿真阶段把串口接到虚拟终端效果一样能演示出来。4. Proteus仿真环境搭建与联合调试4.1 仿真工程搭建的完整步骤Proteus仿真在这个项目里的定位是没有硬件板子时也能完整跑通功能逻辑尤其适合学生交课程设计或者先模拟再打板。搭建步骤如下新建工程选择STM32F103C8T6芯片在图纸上放置从元件库搜索并放置DS1302、LCD1602、DS18B20、BUTTON、BUZZER、LED-RED、LED-GREEN、RESISTOR、CAPACITOR等元件放置了一个Virtual Terminal虚拟终端连接到USART1对应的PA9TX和PA10RX引脚用导线按原理图连线注意芯片电源引脚VDD和VSS要接3.3V和地仿真默认不会自动供电双击STM32芯片在Program File中加载keil编译生成的hex文件设置时钟频率为8MHz启动仿真。Proteus里一个重要的配置项是芯片的“Clock Frequency”要和实际代码里配置的时钟源一致。我用的是外部8MHz晶振仿真里也设置成8MHz时序才真实。4.2 虚拟终端模拟GSM通信因为Proteus没有现成的SIM800C模型仿真里我用虚拟终端来模拟串口通信。虚拟终端接在PA9、PA10上当系统检测到超时未服药时会在串口输出类似“ATCMGS15900000000”这样的AT指令序列接着发送“Attention! Grandfather didnt take medicine at 08:00”这样一条消息。虚拟终端自带时间戳显示我可以看到每次告警消息的发出时刻用来验证超时判断逻辑是否正确。同时虚拟终端还能回显收到的数据调试阶段也可以通过它直接给单片机发数据变相模拟按键输入。4.3 仿真中遇到的两个典型问题仿真不是万能的我遇到的两个问题挺有代表性。第一个是LCD1602不显示检查半天发现是Proteus模型对EN引脚的脉冲宽度要求很严格代码里如果两个写命令之间的延时太短液晶控制器就跟不上。解决方案是每个LCD命令操作之后加至少40μs延时显示数据时延时可以短些。第二个问题是代码在真实硬件上正常在Proteus里却跑飞。后来定位到原因是我的延时函数用了SysTick定时器而Proteus仿真里SysTick的时钟基准跟真实硬件存在差异。解决办法是延时函数不要依赖硬件定时器用简单的循环次数估算实现或者依赖一个固定的系统节拍。我在最终开源版本里的Delay函数采用了一个三次循环的软件延时在仿真和实物上都验证过。5. 实战中踩过的坑与排查技巧5.1 STM32烧录失败排查从驱动到设置的排查顺序很多人第一次用ST-Link烧录时会遇到“No STM32 Target Found”的报错。这个错误信息把很多新手卡在原地我总结了一条排查顺序先看ST-Link的驱动是否装好设备管理器里有没有出现异常的USB设备再检查接线SWDIO接PA13、SWCLK接PA14另外GND必须共地然后确认目标板供电正常3.3V指示灯亮不亮最后调整ST-Link的通信速率目标板有长线或者杂散电容时把频率从4MHz降到1MHz往往就能连上。还有一个经常被忽略的原因芯片的读保护RDP开启后会阻止调试访问需要先用ST-Link Utility执行全芯片擦除。这个不是功能性问题但确实会让人抓狂半天。我在开源文档里专门加了一节“第一次烧录前必看”把这几个步骤都写清楚了。5.2 DS1302走时不准或不走晶振和电容的锅DS1302不走或者时间走得忽快忽慢绝大多数情况出在晶振电路上。市面上很多便宜的32.768kHz晶振精度本身不高偏差可能在20ppm以上日误差会到一两秒。此外晶振两端的负载电容容值要跟晶振规格匹配匹配不当会导致起振失败。如果确认电路没问题但还是不走用示波器量一下晶振引脚是否有波形没波形说明没起振。我实际测试过6pF负载电容在这个项目里表现最稳定换过12.5pF的电容DS1302直接不走了。软件层面还要注意DS1302写保护寄存器0x8E必须先写0x00再操作时间寄存器写完一定要把写保护关闭写成0x80否则时间设置不生效。这个顺序很多资料上没写清楚我是通过看时序才想明白的。5.3 舵机抖动或不转PWM频率的对与错SG90舵机的控制信号是50Hz的PWM也就是周期20ms高电平时间1ms对应0度1.5ms对应90度2ms对应180度。我在代码里用了TIM2的CH1输出PWM设置预分频器使计数频率达到1MHz自动重载值设为20000即20ms比较值设为1500即1.5ms就是中位。很多新手会把STM32的定时器初始化搞混PWM频率不对导致舵机剧烈抖动。排查方法是先用示波器量引脚波形看频率是不是50Hz高电平是不是在1~2ms的范围。如果频率对但舵机没劲多半是电源问题——舵机供电不稳定加个大电容或者独立供电即可。5.4 按键按下没反应从引脚模式开始查STM32的GPIO引脚默认是浮空输入模式如果忘了配置成上拉输入按键接GND时引脚电平就可能处于不确定状态。我建议所有按键引脚都配置为上拉输入模式GPIO_IPD按键一端接引脚、一端接GND按下时引脚被拉低读取到低电平。如果按键接VCC那边就配置成下拉输入。另外要注意的是在Proteus仿真中按键模型默认的弹跳很小而在实物中弹跳明显代码里消抖逻辑就必须有。我用的10ms定时扫描加30ms连续确认的消抖方案在实际按键上测试大约能过滤掉99%以上的抖动干扰。5.5 系统运行一段时间后卡死看门狗要不要上智能药盒是长期运行的设备代码里如果一个模块卡住灯不闪、蜂鸣器不响、屏幕固定不变老人面对这种情况会完全束手无策。所以我加了独立看门狗IWDG主循环每200ms喂狗一次如果循环卡死超过1秒芯片自动复位重新运行。这个改动看起来不起眼但长期运行的设备有没有看门狗稳定性完全两个档次。如果你的代码用到了一个耗时超过1秒的操作比如擦写Flash记得在这段操作前暂停狗或在代码设计时避开否则会误复位。6. 开源文件说明与快速上手指引开源包里的文件不算多我按使用顺序介绍一下方便你拿到手就知道从哪里开始Hardware/STM32F103C8T6_Smart_Med_Box.pdf完整原理图包含最小系统、DS1302、LCD1602、按键、蜂鸣器、LED、舵机、DS18B20、串口及GSM预留接口Software/Keil5工程源码包含所有驱动文件和应用层代码可直接编译生成hexSimulation/Med_Box.pdsprjProteus仿真工程文件需用Proteus 8.9以上版本打开加载hex后可直接运行Docs/引脚分配表.pdf一张表格列出了STM32所有引脚的外设分配硬件接线和写代码时对照查就行Docs/教程.md从零开始搭建环境、编译、烧录、仿真的完整图文教程。拿到项目的建议路径是先打开引脚分配表确认每个外设接到哪个引脚再打开原理图对照引脚表过一遍电路然后打开Proteus仿真文件加载hex运行一遍最后打开Keil源码从main.c开始逐步读到App层的用药状态机。按这个顺序走整套代码和硬件的对应关系就能建立起来了。7. 后续还能怎么扩展这套系统目前能完成基本的用药提醒、确认和告警但我做的时候已经预留了一些扩展口后面你可以按需升级。最大的扩展方向是通信升级。现在告警走的是串口在仿真里只能看到虚拟终端输出实物接GSM模块后可以发短信。如果家里有WiFi可以把串口接到ESP8266告警信息推送到手机App或者微信小程序。用MQTT协议的话药盒作为客户端发布消息手机端订阅延迟可以控制在一秒以内。第二是出药机构的扩展。现在的舵机只做了PWM控制逻辑实物上可以设计一个转盘式药仓舵机转到对应格子自动落下药丸。这个机械结构不用太复杂关键是仓位编码要和用药计划表对应上代码里给每个用药时间组加一个仓位编号就行。第三是数据记录和健康管理。用药确认时间可以存到外部Flash芯片比如W25Q32每周通过串口导出服药统计监护人能清楚地看到这周有没有漏服、有没有准时服。再往后还可以结合心率、血压传感器做成一个真正意义上的健康管理终端。我个人做这个项目最大的体会是嵌入式系统设计里硬件和软件从来都是一体的。原理图上多一颗电容代码里就少一个诡异Bug代码里多一个状态机硬件上就少一堆互相干扰的标志位。智能药盒这个项目难度适中覆盖的外设类型又比较全面——时钟芯片、显示、按键、传感器、PWM控制、通信接口全都有做完一遍基本能把STM32的主流外设都摸熟。如果你正想找个嵌入式项目练手或者准备做毕业设计从这套开源方案开始是个不错的选择。