我要提问
ARTICLE DETAIL

资讯详情

前沿编程新知与开发实战干货的深度解读。

STM32寄存器手册第五章精讲:核心外设配置与避坑指南

STM32寄存器手册第五章精讲:核心外设配置与避坑指南 1. 为什么还要啃寄存器手册很多人第一次接触STM32都是从库函数或者图形化配置工具开始的。点几下鼠标初始化代码就生成了编译下载灯就亮了。这确实很爽但爽完之后呢一旦遇到时序对不上、中断响应慢半拍、功耗降不下来这类问题光靠翻库函数手册往往找不到答案。这时候寄存器手册就成了绕不开的一关。我见过不少朋友库函数用得挺溜但一问他某个外设到底是怎么工作的就支支吾吾说不清楚。其实这不怪他们因为库函数把太多细节封装起来了。封装是好事提高了开发效率但也像一层毛玻璃让你看不清里面的齿轮是怎么咬合的。而寄存器手册就是把这层毛玻璃擦干净的那块布。第五章这个位置很关键。通常前几章讲的是内核架构、存储映射、电源管理这些基础中的基础到了第五章往往就开始进入具体外设的寄存器细节了。这个阶段最容易出现的情况是每个寄存器单独看都能看懂但连起来用就懵了。为什么因为寄存器之间是有联动关系的时序上是有先后要求的这些在手册里往往分散在不同章节需要你自己去拼图。这篇内容就是帮你拼图的。我会把第五章涉及的核心寄存器拆开揉碎讲清楚每个位域为什么这么设计配置的时候先动哪个后动哪个以及那些手册上没写、但实际调试中一定会遇到的坑。不管你是刚学完基础想进阶的初学者还是已经工作几年想回头补课的老手应该都能从中找到对自己有用的东西。2. 第五章核心外设寄存器全景拆解2.1 时钟使能寄存器一切外设的开关不管你要用哪个外设第一步永远是开时钟。这个道理就像你要用家里的电器得先把电闸推上去。STM32的时钟系统比较复杂有多个时钟源还有多级分频和门控。第五章通常会涉及具体外设的时钟使能位这些位分散在RCC相关的寄存器里。以常见的GPIO为例假设你要用GPIOA那就得先找到RCC_AHB1ENR这个寄存器把对应的位写1。这里有个细节写1是使能写0是关闭但读回来的值不一定是你写进去的值。为什么因为有些时钟使能位是受保护的需要先解锁才能写。这个解锁机制在手册里可能只用一小段话带过但如果你没注意到就会遇到“明明写了1但外设就是不工作”的情况。我个人的习惯是在初始化任何外设之前先写一个时钟使能的宏或者函数把所有要用到的外设时钟一次性打开。这样做的好处是逻辑清晰不会出现某个外设忘了开时钟导致调试半天的情况。但要注意不是所有外设都在同一个寄存器里控制时钟AHB、APB1、APB2各自有独立的使能寄存器别找错了地方。还有一个容易被忽略的点时钟使能之后外设并不是立刻就能用的。从时钟打开到外设稳定工作中间有一个短暂的延迟。对于大多数应用来说这个延迟可以忽略不计但如果你在做低功耗应用频繁开关外设时钟那就得考虑这个延迟带来的影响了。手册上一般不会明确写这个延迟是多少需要你通过实验去测。2.2 GPIO模式寄存器输入输出的四种姿态GPIO是每个嵌入式工程师最早接触的外设但也是最容易被低估的外设。很多人觉得GPIO简单不就是设个输入输出吗但实际上GPIO的模式配置里藏着不少学问。第五章通常会详细讲GPIOx_MODER、GPIOx_OTYPER、GPIOx_OSPEEDR、GPIOx_PUPDR这几个寄存器。MODER负责选择模式输入、输出、复用功能、模拟。OTYPER决定输出是推挽还是开漏。OSPEEDR设置输出速度。PUPDR配置上拉下拉。这里面的关键点在于这几个寄存器是配合使用的不能只看一个。比如你要配置一个引脚为复用功能那MODER要设成复用模式同时还得去AFR寄存器里选择具体复用成哪个外设。如果你只改了MODER没改AFR那引脚就处于一个“复用但不知道复用给谁”的状态实际表现就是引脚不工作。再比如开漏输出。很多人知道开漏可以接总线但不知道开漏输出的时候内部上拉是无效的必须外部加上拉电阻。而且开漏输出的高电平不是推出来的是靠外部上拉拉上去的所以上升沿会比较慢。如果你用开漏输出驱动一个需要快速上升沿的信号那波形就会很难看。这时候要么换推挽要么减小上拉电阻但减小上拉电阻又会导致功耗增加。这些都是需要权衡的。输出速度这个参数也很有意思。手册上会给你几个档位比如低速、中速、高速、超高速。速度越高功耗越大EMI也越严重。所以不是越快越好而是够用就好。我一般的原则是能用低速就不用高速除非信号频率确实需要。比如驱动一个LED低速足够了驱动SPI时钟那就得根据SPI的实际频率来选。2.3 中断相关寄存器让CPU知道发生了什么中断是嵌入式系统的灵魂。没有中断CPU就只能不停地轮询效率极低。第五章涉及的中断寄存器通常包括中断使能、中断屏蔽、中断挂起、中断优先级这几个部分。这里最核心的概念是NVIC也就是嵌套向量中断控制器。NVIC的寄存器不在外设章节里而是在内核章节里但第五章讲外设中断的时候一定会提到它。每个外设的中断线最终都会连到NVIC的某个通道上你需要配置NVIC的使能位和优先级。优先级又分抢占优先级和子优先级。抢占优先级高的可以打断抢占优先级低的中断子优先级只在抢占优先级相同的时候起作用。这个规则听起来简单但实际配置的时候很容易搞混。我见过有人把两个中断的抢占优先级设成一样子优先级设成不一样然后发现高子优先级的中断并不能打断低子优先级的中断就以为芯片有问题。其实不是芯片的问题是规则没理解透。还有一个坑是中断标志位的清除。很多外设的中断标志位需要手动清除如果你忘了清中断就会一直触发程序就卡在中断里出不来了。更隐蔽的是有些标志位是“读后自动清除”的你读一下状态寄存器它就清了不需要额外写操作。但有些标志位必须写1才能清除写0反而没用。这些细节在手册里都有写但分散在不同的寄存器描述里需要你仔细找。我个人的经验是在中断服务函数的开头先清标志位然后再处理业务逻辑。这样做的好处是即使业务逻辑处理时间比较长也不会因为标志位没清而导致中断重复触发。但要注意有些外设的标志位清除是有条件的比如必须在某个操作完成之后才能清提前清会导致状态机错乱。所以清标志位的位置也要根据具体外设来定不能一概而论。2.4 DMA寄存器解放CPU的搬运工DMA是直接存储器访问的缩写它的作用是在外设和内存之间直接搬数据不需要CPU参与。第五章通常会讲DMA的配置寄存器包括源地址、目的地址、传输长度、传输方向、传输模式这些。DMA的配置看起来简单但实际用起来坑不少。第一个坑是地址对齐。源地址和目的地址的数据宽度必须匹配比如源是8位目的也是8位那没问题但如果源是8位目的是16位那就需要配置数据对齐方式否则搬过去的数据就是错的。这个对齐方式在手册里可能叫“数据尺寸”或者“外设/存储器数据宽度”需要你根据实际情况去设。第二个坑是传输完成标志。DMA传输完成之后会置一个标志位你可以轮询这个标志位也可以开中断。但如果你用的是循环模式那传输完成标志会一直置位这时候就不能靠轮询标志位来判断传输结束了得用半传输中断或者传输完成中断来计数。第三个坑是DMA和CPU的总线冲突。DMA在搬数据的时候会占用总线如果CPU这时候也要访问同一个总线上的资源就会产生冲突导致CPU访问变慢。这个冲突在手册里一般不会明说但你在实际调试的时候会发现开了DMA之后CPU的执行速度好像变慢了。这不是错觉是总线仲裁的结果。解决办法是合理规划DMA的传输时机尽量避开CPU密集访问总线的时段。还有一个高级技巧是DMA的双缓冲模式。这个模式允许你在DMA搬一块数据的时候CPU处理另一块数据两块交替进行实现无缝衔接。这个模式在音频处理、高速数据采集这些场景下非常有用。但配置起来也相对复杂需要设置两个内存地址并且要在传输完成中断里切换缓冲区。手册上关于双缓冲的描述往往比较简略需要你结合具体的应用场景去理解。3. 寄存器配置的时序与依赖关系3.1 为什么配置顺序不能乱寄存器配置不是简单的写值而是有严格的时序要求的。这个时序要求来自两个方面一是硬件本身的工作时序二是寄存器之间的依赖关系。先说硬件时序。比如你要配置一个SPI接口必须先配置GPIO的复用功能再配置SPI的控制寄存器最后使能SPI。如果你先使能了SPI再去配置GPIO那SPI在使能的那一刻就会开始工作但此时GPIO还没配置好SPI的时钟和数据线就处于不确定状态可能会导致从设备收到错误的信号。这个顺序在手册里可能不会用大号字体标出来但如果你不按这个顺序来就会遇到各种奇怪的问题。再说寄存器依赖。有些寄存器的某些位是互锁的你必须先写A寄存器才能写B寄存器。比如某些芯片的时钟配置寄存器在修改分频系数之前必须先确保PLL处于关闭状态否则修改不会生效。这个互锁关系在手册的寄存器描述里会有说明但往往藏在“注意事项”或者“硬件特性”这些小字里很容易被忽略。我个人的做法是在写配置代码之前先画一个简单的流程图把每个步骤的先后顺序标清楚。这个流程图不需要很复杂用纸笔勾几笔就行但能帮你避免很多低级错误。特别是对于复杂的系统比如同时用到多个外设、多个时钟域的时候这个流程图就是你的施工图纸。3.2 读-改-写操作的陷阱寄存器的操作通常有三种直接写、读-改-写、位带操作。直接写就是给整个寄存器赋一个值读-改-写是先读出当前值修改某些位再写回去。位带操作是Cortex-M内核提供的一个特性可以单独操作某一个位不用读-改-写。读-改-写看起来简单但有一个经典的陷阱如果你在读和写之间发生了中断而中断里也修改了同一个寄存器那你写回去的值就会覆盖掉中断里的修改。这个问题在单线程环境下不会出现但在中断驱动的系统里就很常见。举个例子主循环里在读-改-写某个控制寄存器刚读完还没写回去中断来了中断里也修改了这个寄存器然后中断返回主循环把之前读到的旧值写回去中断里的修改就丢了。这个bug非常隐蔽因为大部分时候不会出问题只有在特定的时序下才会触发调试起来很头疼。解决办法有两个一是用位带操作代替读-改-写位带操作是原子的不会被中断打断二是在读-改-写的时候关中断写完再开中断。位带操作更优雅但不是所有寄存器都支持位带只有特定地址范围的寄存器才可以。关中断更通用但会影响中断响应时间需要权衡。还有一种情况是硬件自动修改寄存器。比如某些状态寄存器硬件会在特定事件发生时自动置位或清零你读到的值可能和你上次写进去的不一样。这时候如果你还用读-改-写的方式去操作就会把硬件自动修改的位也一起写回去导致状态错乱。对于这种寄存器要么用直接写要么只操作你关心的位其他位保持原样。3.3 时钟树与分频系数的计算时钟是外设工作的基准时钟不对外设肯定不工作。第五章通常会涉及具体外设的时钟来源和分频配置。STM32的时钟树比较复杂有多个时钟源经过多级分频和选择最终送到各个外设。以定时器为例定时器的时钟可以来自内部时钟、外部时钟、其他定时器的溢出信号等。如果你用内部时钟那就要看这个定时器挂在哪个总线上APB1还是APB2然后看总线的分频系数是多少。如果APB的分频系数是1那定时器时钟就是总线时钟如果APB的分频系数大于1那定时器时钟就是总线时钟的2倍。这个规则在手册的时钟树章节里有写但很多人第一次看的时候会忽略。分频系数的计算也很关键。假设你要得到一个1kHz的定时器中断系统时钟是72MHz预分频器设成72-1自动重装载寄存器设成1000-1那定时器频率就是72MHz / 72 / 1000 1kHz。这个计算过程看起来简单但实际配置的时候预分频器和自动重装载寄存器的值都是要减1的因为硬件是从0开始计数的。如果你忘了减1那频率就会差一点点虽然误差不大但在需要精确计时的场合就会出问题。还有一个容易忽略的点是时钟的稳定性。有些时钟源在上电之后需要一段时间才能稳定比如外部晶振。如果你在晶振还没稳定的时候就切换时钟源系统就会跑飞。所以手册里会有一个“时钟就绪标志位”你需要等这个标志位置位之后才能切换。这个等待时间在手册里叫“启动时间”具体值取决于晶振的频率和负载电容需要查晶振的规格书。4. 从零配置一个外设的完整实操4.1 以通用定时器为例的配置流程光讲理论太枯燥咱们来一个完整的实操。假设我们要配置一个通用定时器让它每1毫秒产生一次中断在中断里翻转一个GPIO实现LED闪烁。这个例子虽然简单但涵盖了时钟使能、GPIO配置、定时器配置、中断配置、NVIC配置这几个核心环节。第一步开时钟。定时器挂在APB1上GPIO挂在AHB1上所以要先使能这两个时钟。这里要注意APB1的时钟使能寄存器是RCC_APB1ENRAHB1的是RCC_AHB1ENR别搞混了。第二步配置GPIO。把连接LED的引脚配置成推挽输出速度选低速就行不需要上拉下拉。这里有个细节如果你用的是开发板LED可能是低电平点亮那初始化的时候就要把引脚设成高电平否则上电的瞬间LED会闪一下。第三步配置定时器。先设置预分频器和自动重装载寄存器然后设置计数模式为向上计数最后使能定时器。这里的关键是预分频器和自动重装载寄存器的值要算对。假设系统时钟是72MHz我们要1kHz的中断频率那预分频器设成72-1自动重装载设成1000-1。为什么是72-1而不是72因为硬件是从0开始计数的写72-1实际分频就是72。第四步配置中断。使能定时器的更新中断也就是计数器溢出时产生中断。这个使能位在定时器的DIER寄存器里。第五步配置NVIC。设置中断优先级使能中断通道。优先级可以根据实际需求来定如果这个中断很关键就设高一点如果不关键就设低一点。第六步写中断服务函数。在中断里先清标志位然后翻转GPIO。清标志位的时候要注意定时器的更新中断标志位是写0清除的不是写1清除。这个和有些外设是反的需要特别注意。整个流程下来代码量不大但每一步都有讲究。我见过有人把预分频器设成72结果中断频率变成了500Hz查了半天才发现是分频系数算错了。也见过有人忘了清中断标志位结果程序一直在中断里出不来。这些坑我都踩过所以现在写配置代码的时候会习惯性地在每一步后面加一个注释说明这一步的作用和注意事项方便以后回头看。4.2 关键寄存器的值计算与验证配置寄存器的时候最怕的就是算错值。算错了外设要么不工作要么工作不正常。所以算完之后一定要验证。验证的方法有两种一是用调试器看寄存器的实际值二是用示波器或者逻辑分析仪看输出波形。调试器看寄存器值更直接但需要你熟悉调试工具的使用。示波器看波形更直观但需要你有相应的设备。我一般会先用调试器看寄存器的值确认写入的值和预期一致。然后再用示波器看输出波形确认实际效果和设计一致。如果两者都对了那基本就没问题了。如果寄存器值对了但波形不对那可能是硬件连接的问题比如引脚接错了、上拉电阻没焊、晶振没起振等等。还有一种情况是寄存器值不对但波形对了。这种情况比较少见但也不是没有。比如有些寄存器的某些位是只读的你写进去的值读回来还是原来的值但功能已经生效了。这时候如果你只看寄存器值就会以为配置错了其实是对的。所以验证的时候要结合实际情况不能只看寄存器值。对于定时器来说还有一个验证方法是测量中断的实际频率。你可以在中断里翻转一个GPIO然后用示波器测这个GPIO的频率如果频率是500Hz那说明中断频率是1kHz因为翻转一次是半个周期。这个方法很实用能直接反映中断的实际执行情况。4.3 中断服务函数的编写要点中断服务函数是嵌入式程序里最需要小心的地方。因为它是在中断上下文里执行的不能调用可能会阻塞的函数不能做太耗时的操作否则会影响其他中断的响应。首先中断服务函数要尽量短。能在主循环里做的事就不要放在中断里做。中断里只做最紧急、最必要的事比如清标志位、置一个标志变量、把数据存到缓冲区。剩下的处理逻辑放到主循环里根据标志变量来判断执行。其次中断服务函数里用到的变量要加volatile关键字。因为编译器不知道这个变量会在中断里被修改可能会把它优化到寄存器里导致主循环读到的值一直是旧的。加了volatile之后编译器每次都会从内存里读保证读到的是最新的值。第三中断服务函数里不要用浮点运算。浮点运算在有些芯片上是通过软件模拟的非常耗时。如果一定要用最好在中断里只做标记把浮点运算放到主循环里做。第四中断服务函数里不要调用printf之类的函数。这些函数通常是阻塞的而且不可重入在中断里调用会导致各种奇怪的问题。如果一定要输出调试信息可以用一个环形缓冲区中断里往缓冲区里写主循环里从缓冲区里读并输出。第五中断服务函数的命名要和启动文件里的向量表一致。这个在库函数里通常是固定的比如TIM2_IRQHandler你写成别的名字中断就不会进你的函数。这个错误很低级但新手很容易犯。我个人的习惯是在中断服务函数的第一行就清标志位然后再做其他事。这样做的好处是即使后面的代码出了问题至少标志位清了不会导致中断重复触发。但要注意有些外设的标志位清除是有条件的比如必须在读取数据寄存器之后才能清提前清会导致数据丢失。所以清标志位的位置也要根据具体外设来定。5. 常见问题与排查技巧实录5.1 外设不工作的排查思路外设不工作是最常见的问题原因可能有很多。我一般会按照“时钟-配置-引脚-中断”这个顺序来排查。先查时钟。用调试器看RCC的使能寄存器确认外设的时钟位是不是1。如果不是1那就是时钟没开或者开错了寄存器。有时候时钟开了但时钟源选错了比如该用外部晶振的用了内部RC那频率就不对外设工作就不正常。再查配置。看外设的控制寄存器确认模式、分频、使能位这些是不是符合预期。这里要注意有些寄存器的某些位是受保护的需要先解锁才能写。如果你没解锁写进去的值是无效的但读回来可能还是你写的值这就很有迷惑性。然后查引脚。看GPIO的配置寄存器确认引脚模式、复用功能、上下拉这些是不是对。如果引脚配置不对外设的信号就出不来或者进不去。可以用万用表量一下引脚的电压看看是不是符合预期。最后查中断。如果外设配置对了但中断没进那就看NVIC的使能位和优先级。有时候中断进了但标志位没清导致中断一直触发程序看起来像死机了一样。这个排查顺序不是绝对的但大多数情况下能帮你快速定位问题。我见过有人一上来就怀疑芯片坏了换了好几块芯片最后发现是时钟没开。所以排查的时候要从最简单的可能性开始不要一上来就搞复杂的。5.2 寄存器读写异常的典型原因寄存器读写异常通常有几种表现写进去的值读出来不对、读出来的值一直在变、写操作没有效果。写进去读出来不对最常见的原因是寄存器是只读的或者受保护的。只读寄存器你写任何值都不会改变读回来还是原来的值。受保护的寄存器需要先解锁解锁的密钥通常在手册里有写是一串特定的值。如果你没解锁就写写操作会被忽略。读出来的值一直在变可能是寄存器反映了硬件的实时状态。比如状态寄存器硬件会在事件发生时自动更新你读到的值取决于读的时机。这种寄存器不能用来做配置只能用来查询状态。写操作没有效果可能是时钟没开或者外设没使能。有些外设的寄存器在时钟关闭的时候是写不进去的必须先把时钟打开。还有些外设有一个总使能位总使能位没开其他配置寄存器写了也没用。还有一种情况是寄存器有写保护需要先写一个特定的序列才能解锁。比如某些芯片的选项字节寄存器需要先写一个密钥再写数据最后再写一个确认密钥三步都对了才能写入。这种设计是为了防止误操作但如果你不知道这个序列就会觉得寄存器坏了。5.3 中断不触发的排查清单中断不触发也是高频问题。我整理了一个排查清单按照这个清单走一遍基本能覆盖90%的情况。排查项检查内容常见问题时钟外设时钟是否使能忘了开时钟或者开错了寄存器外设使能外设总使能位是否置位只配置了模式没使能外设中断使能外设的中断使能位是否置位只使能了外设没使能中断NVIC使能NVIC的中断通道是否使能忘了调用NVIC使能函数优先级优先级是否配置合理优先级设得太低被其他中断屏蔽了标志位中断标志位是否清除标志位没清中断一直触发但进不去向量表中断服务函数名是否匹配函数名写错中断进了默认处理函数全局中断全局中断是否开启在临界区里关了中断忘了开这个清单里的每一项我都遇到过。最隐蔽的是向量表不匹配因为编译不会报错程序也能跑就是中断不进。后来我养成了一个习惯每次新建工程先把所有可能用到的中断服务函数都写上函数体先空着等用到了再填。这样就不会出现函数名写错的问题了。还有一个容易忽略的是优先级分组。STM32的中断优先级有抢占优先级和子优先级分组方式决定了各占几位。如果分组设错了优先级的实际效果可能和你想的不一样。比如你设了抢占优先级为1子优先级为0但分组方式把大部分位都分给了子优先级那抢占优先级的效果就很弱高优先级中断可能打断不了低优先级中断。5.4 低功耗模式下的寄存器注意事项低功耗是嵌入式系统里绕不开的话题。第五章涉及的外设寄存器在低功耗模式下有一些特殊的行为需要特别注意。进入低功耗模式之前要把不用的外设时钟关掉把GPIO配置成模拟输入或者低功耗状态避免漏电流。有些外设即使在低功耗模式下也会消耗电流比如某些模拟外设如果不用最好彻底关闭。唤醒之后外设的状态可能和进入低功耗之前不一样。有些寄存器会复位有些会保持。这个在手册的电源管理章节里有详细说明但很多人只看外设章节忽略了电源章节结果唤醒之后外设不工作查了半天才发现是寄存器复位了。还有一个坑是中断唤醒。有些低功耗模式只能通过特定的中断唤醒比如外部中断或者RTC中断。如果你用的是其他中断可能唤醒不了。这个在手册里会写但需要你仔细看。我个人的经验是在做低功耗设计的时候先用调试器单步跟踪看进入低功耗之前和唤醒之后寄存器的值有什么变化。把变化的地方记下来在唤醒之后重新配置。这样虽然麻烦一点但能保证系统的稳定性。6. 从寄存器到实际项目的进阶思路6.1 如何用寄存器思维优化库函数代码库函数用久了容易形成依赖什么问题都想着找库函数。但有些场景下库函数确实不够用或者效率不够高。这时候寄存器思维就能派上用场。比如库函数的GPIO初始化通常会一次性配置好几个寄存器包括MODER、OTYPER、OSPEEDR、PUPDR。如果你只需要改其中一个引脚的某一个属性用库函数就会把其他引脚也重新配置一遍虽然结果是一样的但效率不高。这时候你可以直接用寄存器操作只改你需要改的那一位其他位保持不变。再比如中断服务函数库函数的中断处理流程通常比较长有很多判断和分支。如果你对性能要求很高可以自己写中断服务函数只做最必要的操作把处理时间降到最低。我做过一个测试同样的功能自己写的寄存器版中断服务函数比库函数版快了将近一倍。当然寄存器操作也不是万能的。对于复杂的初始化流程比如USB、以太网这些库函数或者官方提供的中间件还是更方便也更可靠。我的原则是简单的、性能敏感的地方用寄存器复杂的、协议相关的地方用库函数。两者结合取长补短。6.2 寄存器手册的高效阅读方法寄存器手册通常很厚几百页甚至上千页从头看到尾是不现实的。我一般会带着问题去查手册而不是通读。比如我要配置一个外设先看这个外设的概述章节了解它的功能和框图。然后看寄存器映射表找到我需要的寄存器。再看每个寄存器的详细描述了解每个位域的含义。最后看时序图了解操作的先后顺序。查手册的时候我会用PDF的搜索功能直接搜寄存器名字或者位域名。这样比一页一页翻快得多。另外我会在手册上做标注把常用的寄存器页码记下来下次直接翻到那一页。还有一个技巧是看手册的“注意事项”和“硬件特性”部分。这些部分通常用斜体或者小字印刷容易被忽略但往往包含了最重要的信息。比如某个寄存器的某个位在特定条件下才能写或者某个操作需要等待多少个时钟周期这些都在注意事项里。6.3 调试工具在寄存器级开发中的妙用调试器不只是用来单步执行的它还能直接查看和修改寄存器的值。这个功能在寄存器级开发中非常有用。比如你怀疑某个寄存器的值不对可以在调试器里直接看这个寄存器的当前值和手册上的预期值对比。如果不对可以手动改一下看外设的行为有没有变化。如果有变化说明问题就出在这个寄存器上。有些调试器还支持寄存器位的实时监控你可以把关键的位加到监控窗口里程序运行的时候实时看这些位的变化。这对于理解外设的工作过程非常有帮助。逻辑分析仪也是寄存器级开发的好帮手。你可以把外设的引脚接到逻辑分析仪上看实际的波形。波形和寄存器配置是对应的通过波形可以反推寄存器的配置是否正确。比如SPI的时钟极性、相位通过看波形就能判断出来。我个人的习惯是在调试外设的时候同时打开调试器和逻辑分析仪。调试器看寄存器逻辑分析仪看波形两者对照问题很快就定位了。6.4 从寄存器手册到数据手册的延伸阅读寄存器手册讲的是怎么配置外设数据手册讲的是外设的电气特性和时序参数。两者是互补的缺一不可。比如你要驱动一个外部器件寄存器手册告诉你STM32的引脚怎么配置数据手册告诉你这个引脚的驱动能力、输入输出电平、时序要求。如果你只看寄存器手册可能会忽略电气特性导致驱动能力不够或者时序不满足。数据手册里的时序图特别重要。比如I2C的时序图会告诉你起始条件、停止条件、数据建立时间、数据保持时间这些参数。如果你不满足这些参数通信就会失败。而这些参数在寄存器手册里通常不会详细讲需要查数据手册。我一般会在项目开始的时候先把用到的外设的寄存器手册和数据手册都过一遍把关键的参数记下来。然后在设计电路和写代码的时候对照这些参数来。这样能避免很多后期调试的问题。7. 一些踩坑之后的真心话寄存器开发这条路入门的时候确实比较痛苦。看着密密麻麻的位域和缩写很容易产生“这玩意儿谁记得住”的想法。但熬过最初的阶段之后你会发现寄存器其实是最诚实的。你写什么就是什么没有库函数那些隐藏的行为也没有中间层的意外。我刚开始学的时候也走过弯路。比如喜欢背寄存器的地址觉得这样显得很厉害。后来发现地址根本不用背手册上都有而且不同型号的芯片地址可能不一样背了也没用。真正需要理解的是每个位域的含义和它们之间的配合关系。还有一个体会是不要怕犯错。寄存器配置错了最多就是外设不工作不会烧芯片。所以大胆试试错了看现象查手册改配置再试。这个过程本身就是最好的学习。最后分享一个小技巧如果你在调试一个复杂的外设比如USB或者以太网觉得寄存器太底层了不好入手可以先找一个能工作的例程用调试器看它的寄存器配置然后对照手册理解每个配置的含义。这样比从零开始啃手册要快得多。等理解透了再自己从头写一遍印象就深刻了。寄存器手册不是用来通读的是用来查的。把它当成一本字典遇到问题的时候去查查多了自然就记住了。第五章只是一个开始后面的章节还有更多外设等着你去探索。保持耐心保持好奇你会发现底层的东西虽然难但掌握了之后上面的一切都变得清晰了。
返回列表