有人使用STM32F303芯片开发产品,用到SPI1外设。SPI工作在主模式,不定期地通过SPI接口读取外部ADC芯片的数据。他发现在MDK KEIL调试模式下并打开SPI外设寄存器窗口时就能正常工作;其它状态下SPI就不正常工作。也就是说只有通过进行下面操作并打开SPI1的寄存器窗口时才能正常工作。
客户还补充道,他的软件代码之前在STM32F103VE上使用是没问题的,移植到32F303这个片子上才出现问题。【注:后来的结果证明,这个补充往往无意中把人往沟里带了。移植过程中前后工程代码细节上的差异客户自己其实也未必清楚。】
从客户反馈的问题现象来看,根据经验判断很可能是某些SPI相关的标志位在他的代码里没有及时做清除操作导致了异常,尤其那些被读取后内容发生变化的寄存器或寄存器标志位。所以,一边提醒该用户认真检查SPI有关标志寄存器的变动情况,一边自己去查看STM32F3参考手册中有关SPI的寄存器读写特性以及STM32F303相关的勘误手册。
从勘误手册上没看到相关问题的内容。从SPI各个寄存器的读写属性,尤其读属性上没有很快发现读与不读而导致内容差异的寄存器或寄存器位。但基本可以肯定问题出在代码上。客户是基于之前的标准库移植过来的,便建议他干脆基于Cube库和他现有硬件重新建立个简单的SPI读写工程项目。
很快客户进一步反馈,他在我的提示下重点检查了SPI_SR状态寄存器的内容及变化情况,并找到解决办法,至于原因不是很清爽。
他反馈说,当他把所有SPI寄存器用变量显示出来,发现其中SR寄存器确实有置位的情况,即那个数据溢出标志位OVR@SPIx_SR 被置位,提示发生接收溢出。
既然这个标志位置1了,说明SPI_DR寄存器数据因为没被及时读取又来了下一个数据而发生溢出事件,导致后续数据无法正常接收。如果此时开启了OVR相关中断的话,会进一步触发中断。鉴于此,客户对其代码稍微做了调整,保证每次开始读外部数据之前OVR是被清零的。
注意,对于OVR位的清零必须遵循如下软件序列:
即先读SPI_DR寄存器,然后读SPI_SR寄存器才能达到清除OVR的目的。
看到这里,异常问题解决了,原因也似乎找到了。原因就是数据溢出而导致后续数据没法正常读取。但是,数据溢出为什么偏偏只发生在SPI寄存器窗口没打开的情况或者是非调试状态下呢?看来,这还是个问题。
我们知道,发生溢出事件是缘于SPI_DR寄存器的数据没有被及时读取而导致。在非调试状态下,用户程序没有通过CPU及时读取DR寄存器导致溢出,这不难理解。那调试状态下用户代码依然会发生没有及时读取DR寄存器的情形,怎么在SPI外设寄存器窗口打开调试状态下就不会发生溢出事件呢?难道还有其它组件浏览过DR寄存器了?
除了CPU可以访问DR寄存器外,真还有其它组件可能访问它,比方DMA。显然,这里DMA可以排除在外,因为DMA访问DR寄存器不分调试和非调试状态。那还有个东西,就是调试组件,它也可以访问外设寄存器。下面是STM32F3所属ARM Cortex M4内核的框图,内核框图中的黄色区域我把它称之为调试组件,它跟NVIC一样属于核内部件。
我们的程序调试、下载一般离不开这个调试组件。
在我们的代码调试过程中,当打开相应外设的寄存器窗口时,IDE为了实时显示各个寄存器的内容,自然就得实时地通过调试组件读取外设寄存器的内容,并显示在我们面前。
具体到这里,我们在调试状态下打开SPI外设的寄存器窗口时,外设组件就会不停的去读取SPI各个寄存器内容并显示出来。显然,当SPI从外部ADC芯片读到数据后,对于SPI_DR数据寄存器的内容,即使用户代码没有去读,调试组件一定会去读,这样就避免了发生数据溢出的情况。换句话说,如果没有开启SPI寄存器的查看窗口,当SPI收到数据而没用被户程序及时读取的话,下次新来数据时就会发生溢出事件。
这就是为什么开启SPI寄存器查看窗口时功能正常,而关闭SPI寄存器查看窗口时功能异常的真正原因。
稍微小结下:
在基于STM32的数据通信过程中,因数据溢出导致异常是比较常见的事情,也是比较容易忽视的一个地方。发生溢出事件后,往往会导致后续数据无法正常接收,还可能触发溢出中断,如果忽略了溢出标志的清零还会导致没完没了的进中断,同时它还会影响数据传输的DMA请求的正常触发。
在STM32的开发过程中,除了CPU、DMA可以访问各类寄存器外,调试组件也可以访问各类寄存器。在程序运行过程中,有些寄存器会因为“被读了”或“没有被读”而衍生出不同结果或不同状态位。这点要注意,这些细节往往离不开对技术手册内容的把握。