ARTICLE DETAIL

资讯详情

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

STM32F103定时器中断实战:Proteus仿真与Keil配置详解

STM32F103定时器中断实战:Proteus仿真与Keil配置详解 1. 为什么选择定时器中断作为STM32入门的第一个实战项目STM32F103C8T6这颗芯片搞嵌入式的基本没有不知道的。LQFP48封装72MHz主频64KB Flash20KB SRAM两个高级定时器、四个通用定时器、两个基本定时器资源给得相当大方。但很多人拿到最小系统板之后点个灯就跑跑完就不知道该干嘛了。我见过太多人卡在“下一步学什么”这个坎上。定时器中断是我个人认为最值得作为第二个实战项目的内容。原因很简单它承上启下。往上它连接着GPIO操作、时钟树配置、NVIC中断管理往下它是PWM输出、输入捕获、编码器接口、DMA触发这些高级功能的基础。你把定时器中断吃透了后面学PWM就是换个配置的事学输入捕获也是同样的框架。而且定时器中断有个特别实在的好处——它让你第一次真正感受到“单片机在后台自己干活”这件事。之前点灯你写个while(1)里面翻转GPIOCPU全程在那儿空转。但定时器中断不一样你配置好之后主循环该干嘛干嘛时间到了中断自动触发执行完中断服务函数再回到主循环。这种“并行”的感觉是嵌入式开发思维的一次重要跃迁。Proteus仿真在这个环节的价值也值得说一说。很多人觉得仿真没用不如直接上硬件。但我的经验是定时器中断这种涉及精确时序的内容仿真反而比硬件更方便调试。你可以在Proteus里直接看到引脚电平的变化可以精确测量中断周期不用示波器就能验证定时器配置对不对。特别是对于手头没有示波器的初学者Proteus的虚拟示波器简直是救命稻草。这篇文章我会从定时器的基本原理讲起然后一步步带你完成Proteus仿真电路搭建、Keil代码编写、联调验证最后把我这些年踩过的坑和总结的技巧都倒出来。不管你是刚学完点灯的新手还是想复习一下定时器配置的老手应该都能找到有用的东西。2. STM32F103C8T6定时器资源全解析2.1 定时器家族成员与各自分工STM32F103C8T6内部一共有7个定时器很多人用了很久也分不清它们之间的区别。我先把这张表列出来你一看就明白了定时器类型位数计数方向通道数特殊功能TIM1高级16位向上/向下/中央对齐4死区控制、刹车输入、互补输出TIM2通用16位向上/向下/中央对齐4输入捕获、输出比较、PWMTIM3通用16位向上/向下/中央对齐4同TIM2TIM4通用16位向上/向下/中央对齐4同TIM2TIM5通用16位向上/向下/中央对齐4同TIM2TIM6基本16位向上0仅定时中断、DAC触发TIM7基本16位向上0同TIM6对于定时器中断这个需求其实用哪个都行。但如果你后面还想扩展PWM输出或者输入捕获建议直接用TIM2、TIM3或TIM4这样不用换定时器重新配置。我一般习惯用TIM2因为它挂在APB1总线上时钟配置相对简单而且引脚PA0-PA3正好在最小系统板的一侧接线方便。TIM1是高级定时器功能最强但也最复杂它挂在APB2总线上时钟频率和APB1不一样。如果你只是做定时中断没必要用TIM1杀鸡用牛刀反而容易出问题。TIM6和TIM7是基本定时器功能最纯粹只有计数和中断没有引脚输出。如果你纯粹只需要一个定时中断用TIM6或TIM7反而最省事配置项最少。2.2 时钟树与定时器频率的关系这是很多人第一个卡住的地方定时器的时钟到底是多少为什么我算出来的时间和实际不一样STM32F103C8T6的标准配置是外部8MHz晶振经过PLL倍频到72MHz作为系统时钟。但定时器的时钟并不是直接等于72MHz这里有个APB预分频器的关系。系统时钟72MHz出来之后经过AHB预分频器通常不分频72MHz然后分给APB1和APB2。APB1的最高频率是36MHzAPB2是72MHz。关键来了当APB预分频系数不为1时定时器的时钟会是APB时钟的2倍。具体来说标准配置下APB1预分频系数为2所以APB1时钟是36MHz但TIM2、TIM3、TIM4的时钟是72MHz。APB2预分频系数为1所以APB2时钟是72MHzTIM1的时钟也是72MHz。这个“倍频”机制是STM32的一个特色很多人看时钟树图的时候会忽略这个细节。你只要记住在标准72MHz配置下所有通用定时器和高级定时器的时钟都是72MHz基本定时器TIM6和TIM7挂在APB1上时钟也是72MHz。2.3 定时时间的计算方法定时器本质上就是一个计数器它从0开始数数数到我们设定的值ARR寄存器的值就产生一个事件。数数的速度由时钟频率和预分频器PSC寄存器决定。计算公式是这样的定时时间 (PSC 1) × (ARR 1) / 定时器时钟频率为什么都要加1因为PSC和ARR都是从0开始计数的。PSC0表示不分频PSC71表示72分频。ARR0表示计数1次就溢出ARR999表示计数1000次溢出。举个例子我们要定时1ms定时器时钟72MHz先确定PSC72MHz / 72 1MHz所以PSC71再确定ARR1MHz下1ms需要计数1000次所以ARR999代入公式验证(711) × (9991) / 72000000 72 × 1000 / 72000000 0.001秒 1ms。没问题。这里有个经验PSC和ARR的分配要合理。如果ARR太小中断太频繁CPU开销大如果PSC太大ARR太小定时精度会受影响。一般来说让ARR在1000到65535之间比较合适这样既有足够的精度中断频率也不会太高。3. Proteus仿真电路搭建与关键设置3.1 元件选取与电路连接Proteus里面仿真STM32F103C8T6首先要在元件库里面找到这颗芯片。打开Proteus 8 Professional点击左侧的“P”按钮在搜索框输入“STM32F103C8T6”一般能找到“STM32F103C8”这个型号。注意Proteus的元件库更新比较慢有些版本可能只有STM32F103C8而没有带T6后缀的选C8就行仿真上没区别。选好芯片放到原理图里面之后需要连接几个关键部分电源部分STM32F103C8T6的VDD引脚有多个引脚9、24、36、48全部接到3.3V。VSS引脚8、23、35、47全部接地。VBAT引脚1可以接3.3V也可以悬空仿真里面悬空不影响。VDDA引脚9和VSSA引脚8是模拟电源仿真里面直接接数字电源就行。复位电路NRST引脚7接一个10K上拉电阻到3.3V再接一个100nF电容到地。Proteus里面如果不接复位电路芯片可能无法正常启动。晶振电路PD0和PD1引脚5和6接8MHz晶振两端各接一个20pF电容到地。Proteus里面晶振的频率属性要设置成8MHz否则时钟不对。BOOT配置BOOT0引脚44接10K下拉电阻到地BOOT1引脚20也接下拉。这样芯片从主Flash启动也就是从我们烧录的程序启动。LED指示电路我一般用PA0接一个LED到地串联一个220Ω限流电阻。这样定时器中断里面翻转PA0就能直观看到中断在运行。Proteus里面LED的驱动电流可以设置默认的10mA就行STM32的GPIO在推挽输出模式下可以驱动。虚拟示波器Proteus里面有个“OSCILLOSCOPE”元件放在原理图里面把PA0接到通道A这样运行仿真的时候就能看到方波波形直接读出周期。3.2 关键设置加载HEX文件Proteus仿真STM32最关键的一步是加载编译好的HEX文件。双击STM32芯片在弹出的属性对话框里面找到“Program File”一栏点击文件夹图标选择Keil编译生成的HEX文件。这里有个坑Keil默认不生成HEX文件需要在项目选项里面勾选“Create HEX File”。具体路径是Options for Target → Output → 勾选“Create HEX File”。如果你编译完在Objects文件夹里面找不到.hex文件就是这个选项没勾。还有一个坑Proteus加载HEX文件之后如果你在Keil里面重新编译了Proteus不会自动重新加载。你需要手动在Proteus里面重新选择一次HEX文件或者关闭仿真重新开始。我一般习惯在Keil里面改完代码编译通过之后在Proteus里面点一下停止再点开始这样它会重新加载HEX。另外Proteus 8.15版本对STM32的仿真支持比较好如果你用的是更老的版本可能会遇到仿真跑不起来或者外设不工作的问题。建议至少用Proteus 8.9以上版本。3.3 仿真速度与实时性的取舍Proteus仿真STM32的时候默认的仿真速度是“实时”模式也就是尽量模拟真实的时间。但STM32跑72MHzProteus是软件模拟不可能真的跑到72MHz。所以实际仿真速度会比真实硬件慢很多。你可以在Proteus的“System”菜单里面找到“Set Animation Options”调整“Animation Timestep”和“Simulation Speed”。但我不建议调太快太快了虚拟示波器可能抓不到波形。一般用默认设置就行虽然慢一点但波形看得清楚。如果你只是验证定时器中断的周期对不对可以看虚拟示波器的测量值那个是准的不受仿真速度影响。但如果你要测中断响应时间这种微秒级的东西Proteus就力不从心了还是得上真实硬件加示波器。4. Keil代码编写与定时器配置详解4.1 标准库工程搭建我这里的代码基于ST的标准外设库Standard Peripheral Library不是HAL库。为什么用标准库因为标准库的寄存器操作更直观适合理解底层原理。HAL库封装太厚初学者容易知其然不知其所以然。而且Proteus对标准库的兼容性更好HAL库有时候会有一些奇怪的仿真问题。工程搭建的步骤新建Keil工程选择STM32F103C8添加启动文件startup_stm32f10x_md.s中容量添加标准库文件stm32f10x_gpio.c、stm32f10x_rcc.c、stm32f10x_tim.c、misc.c添加头文件路径在stm32f10x.h里面定义USE_STDPERIPH_DRIVER在system_stm32f10x.c里面确认SYSCLK_FREQ_72MHz已定义这些步骤看起来繁琐但做一次之后后面就快了。我建议你把常用的标准库文件放在一个固定的文件夹里面新建工程的时候直接复制省得每次找。4.2 定时器初始化代码逐行解析先看完整的定时器初始化函数void TIM2_Init(void) { TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; NVIC_InitTypeDef NVIC_InitStructure; RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE); TIM_TimeBaseStructure.TIM_Period 999; TIM_TimeBaseStructure.TIM_Prescaler 71; TIM_TimeBaseStructure.TIM_ClockDivision TIM_CKD_DIV1; TIM_TimeBaseStructure.TIM_CounterMode TIM_CounterMode_Up; TIM_TimeBaseInit(TIM2, TIM_TimeBaseStructure); TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE); NVIC_InitStructure.NVIC_IRQChannel TIM2_IRQn; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority 0; NVIC_InitStructure.NVIC_IRQChannelSubPriority 1; NVIC_InitStructure.NVIC_IRQChannelCmd ENABLE; NVIC_Init(NVIC_InitStructure); TIM_Cmd(TIM2, ENABLE); }逐行拆解RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE)使能TIM2的时钟。STM32的外设默认时钟是关闭的不使能时钟后面所有配置都不会生效。这是新手最容易忘的一步忘了这步程序跑起来什么反应都没有。TIM_Period 999这是ARR寄存器的值决定计数到多少溢出。999表示计数1000次。TIM_Prescaler 71这是PSC寄存器的值决定预分频系数。71表示72分频。TIM_ClockDivision TIM_CKD_DIV1时钟分割这个参数只影响数字滤波器不影响定时器时钟。一般填DIV1就行。TIM_CounterMode TIM_CounterMode_Up向上计数模式从0数到ARR然后溢出重新从0开始。TIM_TimeBaseInit(TIM2, TIM_TimeBaseStructure)把上面的配置写入TIM2的寄存器。TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE)使能更新中断。更新中断就是计数器溢出时产生的中断。NVIC配置设置中断优先级。STM32F103的中断优先级分为抢占优先级和响应优先级。抢占优先级高的可以打断抢占优先级低的中断服务函数。这里设置抢占优先级0响应优先级1。对于只有一个中断的简单应用优先级随便设都行但养成好习惯重要的中断给高优先级。TIM_Cmd(TIM2, ENABLE)启动定时器。这步之后TIM2开始计数。4.3 中断服务函数编写要点中断服务函数的名字不能随便起必须和启动文件里面的中断向量表名字一致。TIM2的中断服务函数名字是TIM2_IRQHandler写在stm32f10x_it.c里面或者你自己新建一个文件也行。void TIM2_IRQHandler(void) { if (TIM_GetITStatus(TIM2, TIM_IT_Update) ! RESET) { TIM_ClearITPendingBit(TIM2, TIM_IT_Update); GPIO_WriteBit(GPIOA, GPIO_Pin_0, (BitAction)(1 - GPIO_ReadOutputDataBit(GPIOA, GPIO_Pin_0))); } }这个函数里面做了三件事检查中断标志TIM_GetITStatus(TIM2, TIM_IT_Update)确认是更新中断触发的。虽然这个例子里只有更新中断但养成检查标志的习惯后面多个中断源的时候就不会出问题。清除中断标志TIM_ClearITPendingBit(TIM2, TIM_IT_Update)清除更新中断标志。这步非常关键不清除的话中断会一直触发程序就卡在中断里面出不来了。我见过很多人忘了清标志结果LED闪烁频率完全不对查半天查不出来。执行实际任务翻转PA0引脚。这里用GPIO_ReadOutputDataBit读取当前输出状态然后取反写回去。也可以用GPIO_SetBits和GPIO_ResetBits配合一个静态变量来实现但读-改-写的方式更简洁。4.4 主函数与整体流程int main(void) { GPIO_InitTypeDef GPIO_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); GPIO_InitStructure.GPIO_Pin GPIO_Pin_0; GPIO_InitStructure.GPIO_Mode GPIO_Mode_Out_PP; GPIO_InitStructure.GPIO_Speed GPIO_Speed_50MHz; GPIO_Init(GPIOA, GPIO_InitStructure); TIM2_Init(); while (1) { } }主函数里面先初始化GPIOA的时钟然后配置PA0为推挽输出。注意GPIO的时钟挂在APB2上而TIM2的时钟挂在APB1上两个都要使能。初始化完成之后进入空的while(1)循环。所有的LED翻转都在中断里面完成主循环什么都不用做。这就是中断的魅力——主程序解放了可以去做其他事情。5. 仿真验证与波形分析5.1 运行仿真与观察现象代码编译通过生成HEX文件之后在Proteus里面加载HEX点击左下角的运行按钮。如果一切正常你应该能看到PA0上的LED开始闪烁频率是500Hz——因为1ms中断一次每次翻转所以周期是2ms频率500Hz。用虚拟示波器观察PA0的波形应该看到占空比50%的方波周期2ms。如果周期不对比如是1ms或者4ms那就要检查PSC和ARR的计算。我实测下来Proteus 8.15仿真STM32F103C8T6的定时器中断波形周期和理论计算值基本一致误差在微秒级别。这个精度对于验证定时器配置足够了。5.2 用示波器测量中断周期Proteus的虚拟示波器有测量功能。把PA0接到通道A运行仿真在示波器界面点击“Measure”按钮可以看到频率、周期、占空比等参数。我一般直接看频率500Hz对应2ms周期和理论值对比一下就知道对不对。如果你想验证中断服务函数的执行时间可以在中断里面翻转另一个引脚用双通道示波器同时看两个引脚。两个上升沿之间的时间差就是中断响应时间。不过Proteus的仿真时间精度有限这个方法只能看个大概。5.3 常见仿真异常与处理LED不亮首先检查HEX文件加载了没有然后检查GPIO配置对不对再检查时钟使能了没有。Proteus里面如果芯片没有正常启动所有引脚都是低电平。波形频率不对检查PSC和ARR的值确认定时器时钟是72MHz。如果你用的是TIM1检查APB2的预分频系数。仿真跑不起来检查Proteus版本8.9以下对STM32支持不好。检查芯片型号选对了没有STM32F103C8和C6的Flash大小不一样但仿真上区别不大。中断只触发一次99%是忘了清除中断标志。检查TIM_ClearITPendingBit有没有调用。6. 避坑指南我踩过的那些坑6.1 时钟配置的隐形陷阱第一个大坑SystemInit函数。Keil工程里面默认会调用SystemInit这个函数在system_stm32f10x.c里面负责配置系统时钟。如果你没有正确定义SYSCLK_FREQ_72MHz系统时钟可能不是72MHz那定时器的时钟也就不对了。检查方法在main函数开头调用RCC_GetClocksFreq获取系统时钟通过串口打印出来看。或者更简单在调试模式下面看RCC-CFGR寄存器的值。还有一个坑如果你在SystemInit之后又手动配置了RCC可能会覆盖掉之前的配置。我一般建议要么全用SystemInit的默认配置要么全手动配置不要混着来。6.2 中断优先级分组设置STM32F103的中断优先级分为4位可以分成不同的组。默认情况下是NVIC_PriorityGroup_0也就是0位抢占优先级4位响应优先级。但很多人不知道要设置优先级分组直接用NVIC_Init配置结果抢占优先级设了也没用。正确的做法是在main函数开头调用NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2)设置为2位抢占优先级、2位响应优先级。这样抢占优先级可以设0-3响应优先级也可以设0-3。如果你只有一个中断不设优先级分组也能跑。但一旦你有多个中断优先级分组没设对中断嵌套行为就会和你预期的不一样。6.3 Proteus元件库的版本兼容问题Proteus的元件库版本和软件版本是绑定的。如果你从别人那里拿到的Proteus工程打不开提示缺少元件大概率是元件库版本不一致。解决办法在Proteus里面点击“Library”菜单选择“Library Manager”看看有没有可以更新的元件库。或者直接在自己电脑上重新画一遍原理图反正STM32最小系统的电路很简单几分钟的事。另外Proteus 8.15的元件库里面STM32F103C8的模型和8.9版本有区别8.15的模型更完善支持的外设更多。如果你用8.9打开8.15的工程可能会有一些奇怪的仿真问题。6.4 HEX文件加载的常见错误前面提过HEX文件加载的问题这里再强调几个细节Keil里面要勾选“Create HEX File”否则Objects文件夹里面只有.axf文件没有.hex文件Proteus里面加载HEX文件之后如果修改了Keil代码重新编译需要重新加载HEX文件HEX文件的路径最好不要有中文Proteus对中文路径的支持不太好如果Proteus提示“Unable to load HEX file”检查文件是不是被其他程序占用了6.5 定时器中断频率的实测偏差理论上1ms中断一次但实际测出来可能有微小偏差。这个偏差主要来自两个方面一是晶振频率偏差。Proteus里面的晶振是理想元件频率精确。但真实硬件的晶振有±20ppm的偏差8MHz晶振的实际频率可能是7.99984MHz到8.00016MHz之间。这个偏差会累积长时间运行之后定时会偏。二是中断响应延迟。从中断触发到中断服务函数第一条指令执行需要经过压栈、取向量、跳转等过程大概需要12个时钟周期。72MHz下就是约167ns。这个延迟在1ms的周期里面占0.017%基本可以忽略。如果你需要非常精确的定时可以考虑用定时器的自动重装载特性或者在中断里面补偿响应延迟。但对于大多数应用1ms定时偏差几个微秒完全够用。7. 从定时器中断延伸出去的实战方向7.1 多定时器协同工作一个定时器中断玩明白了可以试试多个定时器同时工作。比如TIM2做1ms系统滴答TIM3做10ms任务调度TIM4做100ms状态检测。三个定时器各司其职互不干扰。多定时器协同的关键是中断优先级要分配好。1ms的定时器优先级最高10ms次之100ms最低。这样1ms中断可以打断10ms中断保证系统滴答的准确性。7.2 定时器中断触发ADC采样这是热词里面提到的“定时器中断触发ADC”的典型应用。用定时器中断定期启动ADC转换转换完成后再触发DMA搬运数据。这样CPU完全不参与ADC以精确的固定频率采样适合音频采集、振动分析等场景。实现思路TIM2配置为1kHz中断中断里面调用ADC_SoftwareStartConvCmd启动ADC。ADC配置为外部触发模式触发源选择TIM2的TRGO事件。这样定时器溢出时自动触发ADC不需要CPU干预。7.3 定时器中断实现软件PWM如果GPIO的硬件PWM通道不够用可以用定时器中断实现软件PWM。原理很简单定时器以远高于PWM频率的速率中断在中断里面维护一个计数器计数器的值和占空比比较决定GPIO输出高还是低。比如要输出1kHz的PWM定时器中断频率设为100kHz也就是10us中断一次。中断里面计数器从0加到99计数值小于占空比就输出高否则输出低。这样就能在任意GPIO上实现PWM输出。不过软件PWM会占用大量CPU时间100kHz中断意味着CPU有相当一部分时间在处理中断。如果PWM通道不多还是建议用硬件PWM。7.4 低功耗模式下的定时器唤醒STM32F103C8T6支持睡眠、停止、待机三种低功耗模式。在停止模式下所有时钟都停止功耗降到微安级别。但定时器可以在停止模式下继续运行定时器中断可以把MCU从停止模式唤醒。这个特性在电池供电的设备里面非常有用。比如你做一个温度记录仪每分钟采集一次温度其他时间MCU都在停止模式。定时器中断唤醒MCU采集完温度再进入停止模式。平均功耗可以做到几十微安一颗纽扣电池能用好几个月。实现要点停止模式下要用LSI或LSE作为定时器时钟因为HSI和HSE都停了。TIM2的时钟源可以切换到LSI但LSI的频率是40kHz左右精度不高。如果需要精确唤醒可以用LSE 32.768kHz。8. 调试技巧与工具链优化8.1 Keil调试模式下的定时器观察Keil的调试模式可以实时查看TIM2的寄存器值。在调试模式下打开“Peripherals”菜单选择“System Viewer”里面的“TIM2”可以看到CR1、SR、CNT、ARR、PSC等寄存器的实时值。我最常用的两个观察点一是CNT寄存器看它是不是在正常计数二是SR寄存器的UIF位看更新中断标志有没有置位。如果CNT不动说明定时器没启动或者时钟没使能。如果UIF一直置位不清除说明中断标志清除有问题。8.2 用GPIO翻转法测量中断执行时间在中断服务函数开头翻转一个GPIO在结尾再翻转一次用示波器测量两个翻转之间的时间就是中断服务函数的执行时间。这个方法简单粗暴但非常有效。我实测过一个只做GPIO翻转的中断服务函数执行时间大概1-2us。如果中断服务函数里面做了浮点运算或者调用了库函数执行时间可能到几十微秒。中断服务函数越短越好这是嵌入式开发的基本原则。8.3 标准库与HAL库的取舍标准库和HAL库的争论一直没停过。我的看法是学习阶段用标准库理解底层原理项目开发用HAL库或者LL库开发效率高。标准库的优点是代码直观寄存器操作一目了然。缺点是ST已经不再维护了新芯片不支持。HAL库的优点是跨系列兼容ST主推工具链完善。缺点是封装太厚代码效率略低。对于STM32F103C8T6这颗芯片标准库和HAL库都支持。如果你只是做课程设计或者练手标准库足够了。如果你要做产品建议直接上HAL库后面换芯片不用重写代码。8.4 代码优化与中断响应速度中断服务函数的执行时间直接影响系统的实时性。几个优化技巧中断服务函数里面不要用浮点运算STM32F103没有硬件浮点单元浮点运算靠软件模拟非常慢中断服务函数里面不要调用printf串口输出是阻塞的会严重拖慢中断响应中断服务函数里面不要做延时需要延时的话用计数器在多次中断里面累加中断服务函数尽量短复杂的处理放到主循环里面中断里面只设置标志位我见过有人在中断里面做FFT运算结果整个系统卡死。中断服务函数的原则是能不在中断里面做的事就不在中断里面做。9. 从仿真到硬件的过渡要点9.1 最小系统板选购建议从Proteus仿真过渡到真实硬件你需要一块STM32F103C8T6最小系统板。市面上最小系统板的价格从十几块到几十块不等主要区别在晶振、稳压芯片和USB转串口芯片。我建议选带8MHz晶振和32.768kHz晶振的版本两个晶振都有后面做RTC实验不用再换板子。稳压芯片选AMS1117-3.3这个最常用输入5V输出3.3V。USB转串口芯片选CH340或者CP2102用来烧录程序和串口调试。9.2 程序烧录方式对比STM32F103C8T6支持三种烧录方式串口烧录用USB转TTL模块连接PA9和PA10BOOT0接高电平复位后进入Bootloader用FlyMcu或者STM32CubeProgrammer烧录。这种方式最便宜但每次烧录都要手动切换BOOT0比较麻烦。ST-Link烧录用ST-Link调试器连接SWD接口PA13和PA14可以在Keil里面直接点下载按钮支持在线调试。这是最推荐的方式ST-Link克隆版只要十几块钱但体验好太多。DAPLink烧录DAPLink是开源的调试器方案支持SWD和JTAG价格和ST-Link差不多。如果你用Keil MDKST-Link和DAPLink都支持。9.3 硬件调试的常见问题从仿真到硬件最容易出问题的地方晶振不起振检查晶振的负载电容是不是匹配8MHz晶振一般配20pF。如果不起振可以试着减小电容值或者换一个晶振。复位电路问题NRST引脚的上拉电阻和电容值要合适10K上拉加100nF电容是标准配置。如果复位电路有问题芯片可能反复复位。电源纹波最小系统板的稳压芯片输出纹波要小如果纹波太大MCU可能工作不稳定。可以在3.3V和地之间加一个10uF钽电容和一个100nF陶瓷电容。BOOT引脚配置BOOT0和BOOT1的电平决定了启动模式。从Flash启动时BOOT00BOOT1任意。如果BOOT0悬空芯片可能从系统存储器启动程序就跑不起来。9.4 仿真验证与硬件验证的差异Proteus仿真和真实硬件有几个重要差异你需要心里有数时序差异Proteus是软件模拟时序和真实硬件有偏差。特别是中断响应时间Proteus里面可能比真实硬件快或者慢。电气特性差异Proteus不模拟GPIO的驱动能力、输入阻抗、上下拉电阻等电气特性。真实硬件里面GPIO驱动LED需要限流电阻Proteus里面不加电阻也能亮。外设差异Proteus对STM32外设的仿真支持有限ADC、DAC、USB等外设在Proteus里面可能无法正常工作。定时器、GPIO、串口这些基本外设仿真没问题。我的建议是Proteus用来验证逻辑和基本时序真实硬件用来验证电气特性和外设功能。两者结合使用效率最高。10. 个人实操心得与后续进阶建议10.1 定时器中断配置的检查清单每次配置定时器中断我都会按这个清单过一遍时钟使能了吗RCC_APB1PeriphClockCmd或RCC_APB2PeriphClockCmdPSC和ARR算对了吗定时时间 (PSC1)×(ARR1)/时钟频率中断使能了吗TIM_ITConfigNVIC配置了吗NVIC_Init中断服务函数名字对吗TIM2_IRQHandler中断标志清了吗TIM_ClearITPendingBit定时器启动了吗TIM_Cmd这七步任何一步漏了定时器中断都不会正常工作。我刚开始学的时候经常忘了第1步或者第6步查半天查不出来。后来养成习惯每次按清单检查效率高很多。10.2 从定时器中断到RTOS的过渡定时器中断玩熟了之后你可能会觉得裸机编程越来越力不从心。多个任务需要不同的时间间隔任务之间有优先级任务之间需要通信——这些都是RTOS擅长的事情。从定时器中断到RTOS的过渡很自然。RTOS的系统滴答就是用一个定时器中断实现的任务调度、延时、信号量、消息队列底层都依赖定时器中断。你把定时器中断理解透了学RTOS的时候就会觉得顺理成章。我建议的进阶路线定时器中断 → 多定时器协同 → 定时器触发ADC/DMA → 移植FreeRTOS → 基于FreeRTOS的多任务开发。每一步都建立在前一步的基础上循序渐进。10.3 我个人的几个小技巧最后分享几个我平时用的小技巧用宏定义管理定时时间不要直接在代码里面写999和71用宏定义比如#define TIM2_PERIOD 999这样改定时时间的时候不用满代码找。中断服务函数里面用static变量如果需要在多次中断之间保持状态用static变量不要用全局变量。static变量作用域限于函数内部不会和其他文件冲突。用逻辑分析仪代替示波器如果你有逻辑分析仪看定时器中断的波形比示波器更方便。逻辑分析仪可以同时抓多个通道还能解码协议。Proteus里面加电压探针在Proteus原理图里面放电压探针运行仿真的时候可以看到引脚电压的实时值比示波器更直观。代码版本管理从第一个STM32工程开始就用Git管理代码每次调通一个功能就提交一次。后面代码出问题了可以回退到上一个可用版本。我吃过没有版本管理的亏代码改乱了想回退都回不去。定时器中断这个内容看起来简单但真正吃透需要动手实践。你看十遍教程不如自己写一遍代码调一遍仿真。遇到问题不要怕一个一个排查排查的过程就是学习的过程。我到现在调定时器中断偶尔还会犯低级错误但排查的速度比以前快多了这就是经验的价值。
返回列表