ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

基于STM32的数码管数字时钟制作详解

基于STM32的数码管数字时钟制作详解 1. 项目概述与核心方案选型做电子时钟这件事几乎是每一个玩单片机的人绕不开的入门项目。别小看这个看起来很基础的玩意它里面实际上把GPIO操作、定时器、中断、数码管动态扫描、按键消抖、I2C或SPI通信这些嵌入式开发的核心知识点全串起来了。我用STM32做的这个简易数字时钟算下来断断续续调了一个礼拜中间踩了不少坑比如时间走着走着就慢了、数码管亮度不均、按键按下去没反应这些问题单独拎出来都不难但凑到一起的时候真能把人搞崩。先说清楚这个项目到底是干什么的。简单来说就是用一块STM32单片机控制四位数码管显示当前时间支持通过按键切换小时和分钟、手动校准时间断电后靠备用电池维持时钟芯片继续走时重新上电不用重新设置。我选用的主控是STM32F103C8T6也就是大家常说的蓝丸开发板这颗芯片在淘宝上几块钱一片资源对于这个项目来说绰绰有余而且网上资料极其丰富几乎所有问题都能搜到答案。做出来的效果就是一个独立的桌面小钟放在工位上还挺好看。这个项目适合谁来做如果你是刚学完中断和定时器、想找个综合项目练手的新手这个项目会非常合适。它不是那种照着教程敲一遍就结束的跟练而是真正需要你自己去理解每一行代码为什么这样写。如果你已经有一定基础也可以在这个项目上做扩展比如加个温湿度传感器、做个闹钟功能、把数码管换成OLED屏幕甚至加上ESP8266联网自动校时。后面我会讲到哪些地方可以顺手扩展。1.1 为什么用STM32而不是51单片机这个问题我思考过很久。网上大量数字时钟教程用的是STC89C52这类51内核的单片机51的优点是上手门槛确实低寄存器少逻辑简单但是它的短板也明显主频低、内部资源少、没有硬件RTC、调试手段有限。STM32F103的主频是72MHz内部Flash 64KBSRAM 20KB还有丰富的外设比如多路定时器、RTC、多路ADC、I2C、SPI、USART。用STM32做同样的功能本质上是在用杀鸡用牛刀的方式换学习价值但同时你也在用更接近工业级开发的思维来写代码。STM32的另一个优势是HAL库和CubeMX工具链让外设初始化变得非常直观。以前用51的时候定时器要手写一堆寄存器配置稍不注意就翻车。而STM32配合STM32CubeMX生成工程图形化界面上把引脚和时钟一配代码框架自动生成你再往里面填业务逻辑。这样做的直接好处是你不需要在繁琐的初始化上浪费时间而是把精力集中在理解外设工作原理和数据流向上。不过我也要说句公道话直接上来就用STM32也不是没有代价的最大的代价就是复杂度上升。中断优先级、时钟树、HAL库的API封装都让新手有相当的认知负担。所以这个项目的学习路径我建议是先会点灯再会用定时器产生中断然后再来做时钟直接跳到这一步会有点吃力。1.2 时钟源怎么选内部RTC、DS1302还是DS3231这是整个项目最核心的方案决策点直接决定了你做的时钟准不准、断电后时间能不能保存。市面上常见的时钟方案有三种我逐一分析。第一种方案是用STM32芯片内部的RTC模块。STM32F103内部有一个RTC配合32.768kHz的外部低速晶振LSE工作断电后由VBAT引脚接的电池供电可以保持计时。这个方案的优点是硬件成本最低不需要额外的时钟芯片缺点是F103的RTC模块设计得比较老操作起来有些别扭而且LSE晶振如果PCB布线不好很容易起振失败导致时间不走。很多人在这个坑里卡了好几天。第二种方案是用DS1302时钟芯片。DS1302是Dallas公司现在被Maxim收购出的串行实时时钟芯片三线接口SCLK、I/O、CE带涓流充电功能可以给后备电池充电。它的价格很便宜一两块钱一片教程资料也够多很多开发板和模块都是这个方案。缺点是走时精度一般温飘比较大一个月下来偏个几十秒很正常。第三种方案是用DS3231这是I2C接口的高精度时钟芯片内部集成了温度补偿晶振TCXO走时精度能达到±2ppm也就是一年下来误差在一分钟左右比DS1302高了一个数量级。缺点是价格相对贵模块大概十块钱左右。如果追求走时精准DS3231是更好的选择而且I2C总线以后接其他传感器也用得上学习价值更高。我最终选择的是DS1302原因是它最经典、最有代表性三线时序协议写一遍就能打通对时序的理解而且这个项目定位是简易数字时钟够用就行。如果你手头正好有DS3231或者以后打算做带闹钟、带温度显示的进阶版本建议直接上DS3231代码我会在后面提到移植思路。1.3 显示方案数码管、LCD1602还是OLED显示方案同样有三个主流选择我分别说优缺点。最传统的是四位数码管LED数码管分共阴和共阳两种驱动方式有静态显示和动态扫描两种。数码管的优点是亮度高、看起来有电子设备的复古感非常适合做桌面时钟缺点是引脚占用多、动态扫描需要时刻刷新、段码表需要自己查。我用的是共阳四位数码管模块。LCD1602是一个16列2行的字符液晶屏能直接显示字符和数字使用起来简单直观驱动芯片是HD44780可以通过I2C转接板减少引脚占用。缺点是屏幕小、看起来不如数码管有时钟感而且LCD1602本身没有中文库只能显示ASCII字符。OLED屏幕现在也特别火0.96寸的I2C接口OLED模块只要几块钱显示分辨率128x64可以显示大号数字、汉字要做出好看的表盘界面尤其合适。缺点是耗电相对大虽然OLED本身省电但小尺寸一直全亮也费、看久了眼睛累而且没有复古时钟的氛围。我选的是数码管。原因很纯粹数字时钟搭配数码管是经典组合动态扫描也是嵌入式面试里高频问到的考点用一次项目把它彻底弄明白以后做LED点阵屏、灯带控制都是同一个思路。如果你打算做带菜单设置界面的版本OLED是更好的选择。2. 硬件设计与电路搭建2.1 最小系统与供电设计STM32F103C8T6的最小系统其实很简单一个8MHz主晶振HSE、两个20pF左右的负载电容、一个复位电路、一组电源滤波电容以及BOOT0引脚接10k电阻到地。如果直接用现成的蓝丸开发板这些都已经做好了你只需要关心供电和外部电路的连接。供电方面需要注意蓝丸开发板上有USB口可以直接用5V供电板载的AMS1117-3.3稳压芯片会把5V转成3.3V给MCU供电。但这里有个很多新手容易忽略的细节如果数码管是共阳的段选和位选信号由3.3V的STM32驱动而数码管公共端接的是5V那么IO口高电平3.3V和公共端5V之间的压差可能不足以让LED正常点亮。解决方法是数码管的驱动电源也接3.3V或者用三极管做电平转换和电流放大。我实测下来最稳的接法是让整个系统统一用3.3V供电USB的5V经过板载稳压后输出3.3V数码管的公共端接3.3VSTM32的IO口直接驱动段选和位选中间串联220Ω限流电阻。这样做的好处是电平完全匹配不存在压差问题缺点是每段LED的电流会被限流电阻限制亮度略有下降但室内环境下完全够用。供电电路的另一个要点是电容滤波。我给整个系统加了三个电容一个100μF电解电容并在电源入口做低频滤波一个0.1μF的陶瓷电容放在STM32的VDD引脚附近做高频去耦还有一个同样的0.1μF电容放在DS1302模块的电源引脚旁边。表贴陶瓷电容要尽量靠近芯片电源引脚这是PCB上的铁律面包板上做不到那么严格但也要保持走线尽量短。2.2 按键电路设计时钟肯定需要按键来设置时间我用了三个独立按键分别是模式切换数值加和数值减。按键电路看着简单实际上有讲究。最好的接法是每个按键一端接IO口另一端接地而IO口配置为内部上拉输入。这样按键没按下时IO读到高电平按下接地后读到低电平逻辑清晰而且不需要外部上拉电阻省了三个元器件。要注意不能把按键的一端接3.3V另一端接IO口然后配置下拉输入虽然功能上也能实现但IO口内部下拉电阻阻值偏大约40kΩ抗干扰能力差容易误触发。按键IO口上我串了一个100Ω的小电阻别小看这个电阻的作用。在实验环境或者线比较长的场景下按键线就像一根天线容易耦合静电和干扰串个小电阻能起到一定的限流和阻尼作用减少静电损坏IO口的概率。另外三个按键最好分散在不同GPIO端口上不要全挤在同一个端口这样万一某个端口初始化出错不至于三个按键全部失效。2.3 数码管动态扫描电路数码管有共阴和共阳之分驱动方式差别很大。共阴数码管是把所有LED的阴极连在一起作为公共端需要IO口输出高电平来点亮段码共阳数码管则是所有阳极连在一起做公共端IO口输出低电平来点亮。我用的是共阳四位数码管。所谓动态扫描简单说就是利用人眼的视觉暂留效应让四位数码管轮流点亮每个位在20ms内至少被点亮1ms以上这样人眼看过去就像四位数同时亮着。这个原理和动画片一帧一帧播放是一个道理单看某一帧画面是静止的连续快速切换大脑就给脑补成了动态画面。动态扫描最大的好处是省IO口四位八段数码管如果用静态方式显示需要4×832个IO而动态扫描只需要12个8个段选加4个位选一下就省了20个引脚。我的接线方案是这样的段选a到dp分别接STM32的PC0到PC7位选1到4分别接PA0到PA3每个段选IO口串220Ω限流电阻。这里有一个大家容易犯的错误——限流电阻一定要串在段选上而不是位选上。原因很简单同一个位选同时导通多个段如果电阻串在位选上那么电流会随着亮段数量变化不但亮度不稳定而且电阻上的压降也会波动导致显示不均匀。3. 工程配置从CubeMX到代码骨架3.1 CubeMX引脚分配与工程生成工程创建的第一步是安装STM32CubeMX和对应的固件包我用的是CubeMX 6.x版本搭配F1系列固件包。打开CubeMX后选择芯片型号直接搜STM32F103C8T6双击选中进入配置界面。在System Core菜单里配置RCCHSE选择Crystal/Ceramic Resonator这样系统时钟会使用外部8MHz晶振作为主时钟源。然后SYS选择Serial Wire保留SWD调试接口方便后续用ST-Link下载调试。时钟树那儿有个常见坑点。STM32F103的最高主频是72MHz而外部晶振是8MHz需要经过PLL倍频到72MHz。CubeMX的Clock Configuration界面里你只需要把HCLK那栏直接填成72并回车软件会自动帮你配好PLL参数。但是有个容易踩的坑如果HSE没有成功配置为Crystal模式比如误选了BYPASS那么你会发现无论怎么填系统时钟始终上不去72MHz或者编译烧录后程序跑飞。这个问题的排查方法是用示波器或者万用表频率档去量PC14引脚量不到8MHz波形就说明HSE没起来。然后是GPIO配置。PA0到PA3设为GPIO_Output用来控制位选初始电平根据你的三极管电路来定如果位选是低电平选通初始就设High以便关闭所有位。PC0到PC7设为GPIO_Output用来控制段选初始全部设为High共阳数码管高电平灭。按键对应的三个IO口PA4、PA5、PA6设为GPIO_Input选择Pull-up内部上拉。DS1302的三根线我接在PB0CE、PB1SCLK、PB10I/OPB0和PB1设为输出PB10这里因为要双向读写数据CubeMX里先设为GPIO_Output读写方向在代码里用函数动态切换。3.2 定时器配置1ms系统时基数字时钟的逻辑核心是时间基准也就是怎么知道一秒过去了。方案有两种一种是用DS1302内部晶振计时MCU每隔一段时间去读一次另一种是直接用MCU的定时器产生秒中断。我采用的是两者结合DS1302负责绝对时间的保持和断电续走MCU定时器提供1ms的系统时基用来做数码管动态扫描和按键消抖。我用TIM2来做1ms的定时器中断。TIM2是通用定时器它的时钟源来自APB1总线而APB1的最大频率是36MHz。CubeMX里我配置TIM2的Prescaler为35Counter Period为999这样通过公式计算36MHz ÷351÷99911kHz也就是1ms触发一次更新事件。这里要注意Prescaler和Period都需要用实际值减1因为这两个寄存器的值从0开始计数这个细节踩过一次坑之后我再也不会忘了。中断优先级也要安排好。我把TIM2的中断优先级设为抢占优先级1、子优先级0把DS1302需要用到的延时依赖放在这个中断里。关于中断服务函数在HAL库中TIM2的更新中断回调函数是HAL_TIM_PeriodElapsedCallback你需要在这个函数里处理扫描和计数逻辑。但要注意不要把耗时长的操作放进中断里中断服务函数越短越好这是嵌入式开发的一条铁律我在后面讲代码的时候会展开说。3.3 DS1302驱动移植DS1302的数据手册只有十几页协议也不复杂但细节陷阱不少。它本质上是一个三线串行接口CE片选拉高后开始通信SCLK提供时钟I/O线上一位一位地传输数据。读写操作都是先发送8位命令字再读写8位数据命令字的格式是bit7固定为1bit6用来选择RAM还是时钟寄存器bit5到bit1是寄存器地址bit0是读写方向标志1代表读0代表写。移植DS1302驱动时我建议直接写一个精简版的操作函数不要拿网上那种几百行的万能驱动直接复制因为很多万能驱动为了兼容各种MCU做了大量条件编译和重定义可读性很差出了问题很难排查。我自己写的核心就三个函数DS1302_SetTime写入时间、DS1302_GetTime读取时间、DS1302_Ini初始化。底层用HAL库的HAL_GPIO_WritePin和HAL_GPIO_ReadPin来控制三根线。注意读写数据的时候I/O线方向切换要留出足够的时序余量我实测在72MHz主频下每次时钟沿之间至少要加几个周期的延时否则高速模式下DS1302容易读回错误数据。还有一个细节DS1302的初始化时序要求上电后先让CE拉低、SCLK拉低保持至少几个毫秒再开始第一次通信。网上很多驱动直接上来就写数据在上电瞬间容易遇到通信失败。我在初始化函数开头加了一个10ms的延时实测对稳定性的提升非常明显。4. 核心代码实现与逻辑讲解4.1 数码管动态扫描驱动代码动态扫描的代码是整个项目里最锻炼思维的部分。我先定义好段码表共阳数码管的段码是低电平点亮所以数字0到9对应的段码需要取反比如数字0点亮a、b、c、d、e、f六段熄灭g和dp二进制就是11000000十六进制0xC0。我实际用的段码表如下const uint8_t segCode[10] { 0xC0, // 0 0xF9, // 1 0xA4, // 2 0xB0, // 3 0x99, // 4 0x92, // 5 0x82, // 6 0xF8, // 7 0x80, // 8 0x90 // 9 };扫描逻辑放在1ms的定时器中断里。我维护一个全局变量scanPos表示当前扫描到第几位每次进入中断给对应位选引脚送低电平共阳位选低有效然后把该位要显示的数字段码送上段选延时一下其实就是等下一次中断再关掉当前位选、切换到下一位。这个流程用伪代码表示就是关所有位选→选择当前位→送段码→打开位选→scanPos到下一轮。关所有位选这一步非常关键不关的话会造成拖影也就是上一位的数字残影叠加到下一位上看起来像重影肉眼可见的糊。这里要特别说一下为什么我在单次扫描里不做软件延时扫完四位而是让每次定时器中断只处理一位。原因是主循环里还有按键扫描、DS1302的时间读取操作这些操作可能耗时几毫秒如果扫描函数在同一段代码里连续扫完四位并且中间有较长延时那么主循环一忙就会打断扫描节奏造成闪烁。把扫描分散到1ms中断里相当于给扫描任务分配了固定的时间片无论主循环怎么忙数码管都能保证1kHz的总刷新率每位250Hz的刷新率远超人眼能够分辨的闪烁频率大概50Hz以上就看不出闪烁了。4.2 按键处理与状态机按键最基础的处理是消抖。机械按键在按下和松开的瞬间触点会发生物理抖动持续大概5到10ms。如果不消抖按一次可能会被识别成连续按了很多次。我在1ms定时器中断里维护三个按键的计数变量当读到电平为低按下时计数加1电平为高时计数清零只有计数连续达到20即20ms才判定为有效按下。这个算法叫连续扫描消抖比用延时函数死等消抖靠谱得多因为延时消抖会阻塞主循环而且延时时间很难控制。按键的模式逻辑我用了一个简单的状态机。这个状态机有三个状态正常走时TIME_RUN、调节小时ADJ_HOUR、调节分钟ADJ_MIN。模式键按下时状态依次切换。为什么需要状态机而不是用一堆if判断因为时钟的显示逻辑在不同状态下完全不同正常状态显示当前时间调小时状态需要让小时的数字闪烁或定格显示调分钟状态同理。如果把这些逻辑全塞在if判断里代码会越来越乱加功能更是灾难。用状态机后每种状态下按键的处理函数是独立的新增功能只需要在对应状态下增加处理即可。按键读取和状态切换放在主循环里做不做在中断里因为状态切换往往伴随DS1302的写操作而DS1302的时序需要较长延时放进中断会拖垮系统时基。4.3 时间读取与设置流程DS1302的数据存储格式是BCD码也就是用二进制表示十进制数的一个十进制位比如十进制的23在BCD码里就是0x23而不是0x17。读写DS1302寄存器时必须要把十进制时间和BCD码做转换。转换逻辑很简单十进制转BCD用dev (num / 10) * 16 (num % 10)BCD转十进制用num (bcd / 16) * 10 (bcd % 16)。这个转换看似简单但真有不少人因为忘了处理而看到时间显示乱码比如显示00到09以外的数字基本都是BCD转换出错了。我定义了一个时间结构体typedef struct { uint8_t hour; uint8_t minute; uint8_t second; } Time_t;主循环里每500ms读一次DS1302的时间更新显示缓冲区。为什么不每1ms都去读因为DS1302的通信本身要耗时几百微秒频繁读会占CPU资源而且1ms读出来的时间和1s读出来的几乎没差别500ms读一次足够保证秒数不跳变。设置时间时模式切换到调节状态后每按一次加键或减键就对当前调节的字段做加1或减1操作并做边界处理。小时的加1在到23后要回0分钟的加1在到59后要回0这里容易写漏。每次调整完立即把调整后的数值写入DS1302这样即使断电也能保持设置好的时间。我建议调整完当前字段就立刻写入而不是等退出设置模式才写入因为用户可能随时断电数据不落盘就等于白调了。还有一个显示细节秒和分钟的分隔点。四位数码管只能显示四位数字秒没法单独显示我让十位和个位之间的小数点每秒闪烁一次用来区分走时中和卡死两种状态。具体实现是把数码管第二位也就是小时个位的DP段当作秒指示每500ms翻转一次状态。这个细节虽然小但是非常提升使用体验。4.4 主循环与中断的协同方式很多新手写嵌入式程序有一个毛病把所有的逻辑都塞进main函数的while循环里写成一个大泥球。这个项目我采用中断处理时间敏感事务、主循环处理低优先级事务的分层结构。1ms定时器中断里只做三件事数码管扫描推进、按键消抖计数、秒指示闪烁计时。主循环里做三件事检查按键状态并处理模式切换、每500ms读取DS1302时间刷新显示缓存、刷新数码管显示数据。这种分工方式的核心逻辑是所有对时序敏感的操作都被中断固定住了主循环再慢也不影响显示和按键的实时性。代价是显示缓冲区的数据需要设置脏标志来避免主循环反复刷新导致闪烁。我在主循环里定义一个displayBuffer数组读回来的时间写入这个数组中断里的扫描函数只读这个数组对应的段码两者之间通过volatile关键字声明避免编译器优化导致读取不到最新值。关于volatile新手可能觉得玄乎简单理解就是告诉编译器这个变量可能被中断修改每次用都要重新读内存别给我优化到寄存器里不声明volatile的话中断改了变量主循环看不到显示就会卡住或者乱跳。另外主循环里的DS1302读取操作我加了一个防止读写冲突的信号量标志。如果某一次读操作还没结束而按键刚好切到了设置模式要写DS1302两个操作重叠就可能写坏数据和寄存器。我的处理方式是在读写DS1302之前先关闭TIM2中断等通信完成再打开。因为DS1302通信时间短不到1ms中断关闭几百微秒不会导致扫描明显卡顿。这种做法在嵌入式里叫临界区保护等你以后写RTOS的时候会发现思想完全一样。5. 常见问题、踩坑记录与调试技巧5.1 时间不走或走时严重不准这个问题的出现概率极高。先排查硬件DS1302能正常工作必须有3.3V供电和32.768kHz晶振正常起振。DS1302模块上那个晶振如果没焊好或者模块本身是劣质的直接症状就是时间不走。区分方法很简单用万用表量DS1302的X1和X2引脚之间的电压正常起振时应该有一个很微弱几十毫伏级别的振荡信号没有信号基本就是晶振问题。我遇到过模块上晶振虚焊的情况重新补焊了一下就正常了。另一个常见原因是初始化代码里没有正确开启DS1302的时钟振荡器。DS1302的秒寄存器最高位CHClock Halt默认为1表示时钟停止必须在写秒数据时把这一位清零。很多驱动函数封装得太深如果你直接调用一个别人写好的初始化函数时间却一直停在00:00不走第一个怀疑对象就是这个CH位。走时不准问题我写过一次专门排查。用了一个月后慢了大概两分钟排查下来发现是我把DS1302放在了一个发热量较大的模块旁边温度对普通晶振的影响很明显。如果是DS1302对精度有要求可以换DS3231或者每两周手动校准一次。注意DS1302的涓流充电功能如果不需要建议关闭否则备用电池会被内部充电电路慢慢耗尽最后断电后时间照样丢。5.2 数码管闪烁、亮度不均和拖影数码管显示不良通常是这三个原因之一。闪烁优先查扫描刷新率。如果是在主循环里用delay扫的主循环一忙于DS1302通信就卡住扫描这是必然闪烁的。解决办法就是回到我上面说的定时器中断扫描方案。刷新率低于50Hz就会明显闪烁我的方案是250Hz在实际中没有感觉得到频闪。亮度不均表现在某一位比其它位暗或者某一段比其它段暗。常见原因是位选驱动能力不够或者段选电阻阻值不合适。四位数码管动态扫描时任何时刻只有一个位在亮为了让整体亮度够每位瞬间通过的电流比静态显示时要大。这就要求IO口的驱动能力足够。STM32F103的GPIO灌电流能力大概在8mA左右如果八个段全亮的瞬间电流超过这个值IO口就会进入限流保护亮度自然上不去且不均匀。解决方法是位选加PNP三极管比如S8550做开关放大IO口控制三极管的通断公共端电流直接从电源走三极管不经过IO口。拖影或者鬼影多半是位选切换时没有先关闭当前位。也就是我在动态扫描代码里强调的先全灭再点亮下一位的顺序问题。这就像电影换帧时不先黑一下屏就硬切下一帧画面就会糊成一团。我在扫描前先给所有位选引脚输出高电平共阳灭再做段码更新最后才打开目标位选严格执行这三步鬼影问题彻底消失。5.3 按键失灵、误触发和跳变按下去没反应先检查是不是按键接到灌电流需要配置的IO口上。STM32的IO口有输入输出模式的区分还要注意部分IO口默认复用为调试功能或JTAG功能。比如PA13到PA15、PB3和PB4这些引脚在芯片复位后默认是SWD和JTAG引脚如果你把按键接在这些引脚上初始化成普通GPIO后仍然有调试器占用问题很容易出现引脚电平不受控。我看到不少人把按键接到PA15上结果怎么配置都不对就是这个原因。规避方法很简单按键尽量用PA0到PA12和PB0到PB15这些干净引脚。误触发的排查方向有三个。第一个是按键没有加消抖逻辑或者消抖时间太短低于10ms抖动没滤干净。第二个是按键IO口没有配置成内部上拉悬空时电平不稳定手指靠近都会误触发。第三个是按键到MCU的连线太长在工业现场会耦合干扰。我用的20ms连续扫描消抖已经验证过对绝大多数按键有效。还有一个细节很多人不知道按键状态处理完一定要清掉事件标志。我在按键处理函数里用一个全局变量keyEvent记录本次按键的事件类型主循环处理完之后手动清零。如果不清零主循环下一次运行又会把同一个按键事件再处理一遍表现为按一下模式键状态连跳了两次。这个在刚写完代码调试时特别容易出现排查方法是在调试器里看寄存器值确认事件标志是否在每次处理后复位。5.4 ST-Link烧录与调试经验烧录问题也是高频踩坑点。用ST-Link连接蓝丸板时如果提示No target connected或者一直连接失败先检查接线SWDIO接PA13、SWCLK接PA14、GND共地三根线就能下载。确认接线无误仍然连不上检查BOOT0引脚的状态。BOOT0接低电平是从Flash启动接高电平是进入系统存储器启动也就是串口ISP模式。如果BOOT0悬空或者被拉高STM32不会从用户Flash启动ST-Link就可能连不上。把BOOT0用跳线帽短接到GND再试一次基本能解决。还有一个非常隐蔽的坑有些STM32F103C8T6芯片是纸片货或者翻新片芯片内部的读保护默认被打开。烧录时提示Failed to read memory或者Verification error在ST-Link Utility里需要先执行Remove protection操作设置Option Bytes里的RDP级别回0xAA然后再下载程序。这个问题我在二手芯片上遇到过两三次了批量买芯片时一定要留个心眼。调试方面我强烈建议使用ST-Link的在线调试功能在Keil里打断点看变量的值比串口打印效率高太多。我排查DS1302读回错误时就是直接在读取函数后面打断点单步看返回的BCD码一眼就发现了高四位多了一个bit的移位错误。调试的时候要注意在中断服务函数里打断点会导致中断频繁触发Keil会卡在断点处出不来的情况此时可以先用示波器看引脚波形或者临时注释掉中断等调试完再恢复。5.5 项目复盘与扩展方向这个项目做完之后我一直觉得它是所有STM32入门项目里性价比最高的。麻雀虽小五脏俱全一个数字时钟几乎覆盖了嵌入式开发的全部基本功GPIO操作、定时器中断、动态扫描、按键消抖、状态机设计、时序协议分析、临界区保护。把这些东西吃透了以后做智能小车、四轴飞控、平衡机器人这些复杂项目底层的思维框架都是现成的区别只是外设更多、代码量更大。市面上现成的数字时钟模块很多几块钱就有人家画好板子、写好代码的成品买回来就是一个螺丝刀焊两下的组装活。但我始终建议自己从头做一遍这里有价值的部分从来不是那个显示出来的时间而是你踩过的每一个坑。你调通DS1302通讯那一刻获得的成就感还有你第一次目击到数码管稳定显示后长舒一口气的体验是买成品模块根本体会不到的。扩展方向我列几个思路一是加DS18B20温度传感器数码管交替显示时间和温度二是加闹钟功能到点触发蜂鸣器三是加ESP8266模块通过NTP协议联网校时断电重启后自动对时彻底解决走时精度问题四是用OLED屏幕替换数码管做一个多界面切换的桌面时钟。我后来就是在这个基础上扩展了一个温湿度显示版本把家里的温湿度计换成了自己做的东西用到现在一年多了还在稳定工作。做这个项目我最大的体会是嵌入式开发里的很多问题最后都是通过分时复用这个词解决的CPU的时间片需要调度数码管的显示需要分时扫描按键的检测需要分时轮询。只要把这个思维方式练到位遇到任何看似复杂的硬件控制问题你都能先沉住气想清楚谁在什么时候占了谁的时间答案也就八九不离十了。希望这篇分享能帮你少走一些弯路也期待看到你做出自己的版本。
返回列表