数码之家

 找回密码
 立即注册
搜索
查看: 8797|回复: 26

[STM] 实验证明,STM32F103的SPI响应时间是能在指令级别时间粒度感知到的

[复制链接]
发表于 2021-9-17 14:15:01 | 显示全部楼层 |阅读模式

爱科技、爱创意、爱折腾、爱极致,我们都是技术控

您需要 登录 才可以下载或查看,没有账号?立即注册

x
题目可能有点绕,说白了就是内核运行过快,进行了数据发送后立即访问SPI状态寄存器会因为SPI响应不过来导致状态读取失败。所以提交数据给SPI发送后,最好延迟几个指令周期再去访问SPI当前状态,然后可以知道SPI是否发送完了数据,是否出错啥的。:sweat:

我现在搞个DAC7311的工程,需要通过SPI接口写入数据从而改变DAC的输出。我的程序逻辑就是先开启SPI,然后写入要发送的数据到发送寄存器中,等待SPI状态寄存器的Busy位置0代表发送完成;然后再写一个数据,再次进行Busy位检查,最后关闭SPI接口,完成一次写DAC的操作。结果DAC输出不正常,用示波器一看好家伙,SPI的时钟只输出了8-9个脉冲就结束了,而且有时候8个,有时候9个。当时就怀疑是不是程序判断逻辑出错没有正确等待Busy置0完成当前数据发送就进行下一步操作了,检查了几遍,没发现问题。

后来又进行在线调试,直接在检测Busy的部分加断点,然后运行到该处分析状态。好家伙,直接得到了下图结果:
B.PNG

结果显示,如果全速运行到读取SPI_SR那一步,会发现读出来的数据全0,当时你可以通过Keil的外设窗口看到SPI_SR的寄存器是有TXE和RXNE置位的,证明这次读取出错了。然后如果在写入发送数据那一步增加一个断点,先运行到发送数据那一步,停止,然后再全速运行到读取SPI_SR那一步,可以看到,读出的值变成0x02了,正好SPI_SR中的TXE置位了,证明读取没有出错。

基于上面的实验,接着给写入数据到读取状态这一系列操作增加空指令延时,结果显示当NOP的数量为3时,全速运行到读取SPI_SR那一步数据仍然全0出错,当NOP数量大于4时,就可以得到非0的正确的结果,如下图所示:
C.PNG

于是,最终加了4个NOP,故障排除,DAC输出正常波形,示波器检查了SPI接口时序检测没有再出现任何问题。:victory:

打赏

参与人数 2家元 +80 收起 理由
jf201006 + 20 謝謝分享 没遇到过
家睦 + 60

查看全部打赏

 楼主| 发表于 2021-9-17 14:19:33 | 显示全部楼层
当然,我觉得这个问题在使用C语言基于库编程的时候不存在,因为反复的函数调用返回带来的时间开销已经充当了写入-读取这个过程的必要延时。:titter:
回复 支持 反对

使用道具 举报

发表于 2021-9-17 14:41:34 | 显示全部楼层
看起来好牛X的样子,这是汇编么???
回复 支持 反对

使用道具 举报

发表于 2021-9-17 15:40:27 | 显示全部楼层
1、如果你要连续写入数据,应该判断发送缓冲器空闲标志(TXE)而不是判断BUSY位。
2、BUSY位状态不止和是否发送完成有关,还和其他因素有关,具体要看设置。
3、
当时你可以通过Keil的外设窗口看到SPI_SR的寄存器是有TXE和RXNE置位的,证明这次读取出错了。
TXE为1表示发送缓冲区为空,可以继续往里发送数据。RXNE为1表示接收缓冲区非空。不知道你说的证明读取出错什么意思?

微信截图_20210917153205.png

打赏

参与人数 1家元 +20 收起 理由
家睦 + 20 歡迎探討

查看全部打赏

回复 支持 2 反对 0

使用道具 举报

发表于 2021-9-17 16:37:47 | 显示全部楼层
本帖最后由 inthsunshine 于 2021-9-17 17:57 编辑

stm32的SPI用了很多, 表示没碰到lz的问题,应该是没正确使用造成的异常结果

发送数据前,必须先检查TXE是否为1,是的话可以发送,不是等需等到为1, 同理,读取接收数据前,必须先判断RXNE,为1才能读取

连续发送不需要人为延时,只要TXE为1,马上就可以发送下一个数据(即使此时上一个数据还未完全发送完毕)
回复 支持 反对

使用道具 举报

发表于 2021-9-17 18:23:55 | 显示全部楼层
inthsunshine 发表于 2021-9-17 16:37
stm32的SPI用了很多, 表示没碰到lz的问题,应该是没正确使用造成的异常结果

发送数据前,必须先检查TXE是否 ...

正解!
代码其实是while查询这些状态位而不是nop去等,因为不知道什么时候能ready
回复 支持 反对

使用道具 举报

发表于 2021-9-17 21:10:43 | 显示全部楼层
妈呀,用汇编写ARM?牛
回复 支持 反对

使用道具 举报

 楼主| 发表于 2021-9-18 00:46:45 | 显示全部楼层
本帖最后由 la45088d1 于 2021-9-18 00:48 编辑
inthsunshine 发表于 2021-9-17 16:37
stm32的SPI用了很多, 表示没碰到lz的问题,应该是没正确使用造成的异常结果

发送数据前,必须先检查TXE是否 ...

所以你根本没搞明白我在说什么。:lol:
检测标志位的前提是你必须正确读取标志位寄存器SPI_SR,我的实验结果就是刚写入发送的数据,立即去读SPI_SR寄存器是不行的,会直接读到全0的状态位,那你后面判断必然出错。
所以我在写入数据后加了延时,确保SPI准备好了才连续读取状态寄存器判断数据发送情况。至于我检测busy位的目的,我很清楚。
并且如果是我这种情况不加NOP,什么位的标志都读不出来,全是0,你仔细看我上面的图。
回复 支持 反对

使用道具 举报

 楼主| 发表于 2021-9-18 00:52:58 | 显示全部楼层
crazy0qwer 发表于 2021-9-17 15:40
1、如果你要连续写入数据,应该判断发送缓冲器空闲标志(TXE)而不是判断BUSY位。
2、BUSY位状态不止和是否发 ...

所以我通读了原版数据手册,发现在我的设置下读取busy就是可以达成数据不冲突的目的,所以没关系。
这次异常的原因是由于SPI_SR读取的异常导致程序运行错误,所以我才针对读取错误采取了措施,这才是我要表达的。并不是因为读到了标志位但使用了错误的依据,而是标志位根本读不出来。
回复 支持 反对

使用道具 举报

 楼主| 发表于 2021-9-18 00:55:41 | 显示全部楼层
fan.lu 发表于 2021-9-17 18:23
正解!
代码其实是while查询这些状态位而不是nop去等,因为不知道什么时候能ready ...

你想的有点多,NOP只是延时一个很小的时隙确保SPI_SR的结果是可以访问的,然后才进入一个while循环连续判断标志位的变化。否则的话,一进while马上读取寄存器就会得到全0的错误值,while一下就过了根本没起到作用。
回复 支持 反对

使用道具 举报

发表于 2021-9-18 07:50:53 | 显示全部楼层
本帖最后由 inthsunshine 于 2021-9-18 08:00 编辑
la45088d1 发表于 2021-9-18 00:46
所以你根本没搞明白我在说什么。
检测标志位的前提是你必须正确读取标志位寄存器SPI_SR,我的实验结 ...

仔细去看下官方的参考手册吧,从来没有提到发送完马上读SR会出错, 你的问题我也是第一次听说, 要真有这个bug, 以stm32的使用量,st早就修正

连续不断发送数据,我又不是没试过, 上次lcd彩屏刷全屏图像,就是连续发送几十K次数据,发送完马上检测SR, TXE=1就再次发送,一直循环操作
更别说DMA下的数据连续传送,SPI的DMA传送就靠TXE和RXNE触发呢,难不成DMA也要向你那样加延时?:lol:   上次搞音频的录制回放,也是用了SPI/I2S+DMA+中断,翻来覆去随便玩,呵呵,声音都非常好,真没碰到你的问题

另外再说下, 就这种DAC数据发送,用C完全就够了,汇编没有任何好处,自己编的可能还不如编译器
回复 支持 反对

使用道具 举报

 楼主| 发表于 2021-9-18 08:41:03 | 显示全部楼层
inthsunshine 发表于 2021-9-18 07:50
仔细去看下官方的参考手册吧,从来没有提到发送完马上读SR会出错, 你的问题我也是第一次听说, 要真有这 ...

就因为官方手册没有才容易出问题。反正就那么几条指令,目的及其明确,你能看出什么原因导致这种诡异的问题吗?
另外你自己成功但是你的是C环境,加上随便调一个库函数损耗就快1,2us了,那肯定轮不到这种问题出现,所以这又引申出一个问题,就是你不能真的认为Keil这个编译器它真比你直接写指令好多少,很多编译结果你拿反汇编去检查都及其SB。
最后是DMA加SPI我又不是没搞过,一点问题都没有,我直接拿来刷屏幕,就是这个数据太少,所以没用SPI。
回复 支持 反对

使用道具 举报

发表于 2021-9-18 09:02:19 | 显示全部楼层
本帖最后由 inthsunshine 于 2021-9-18 09:18 编辑
la45088d1 发表于 2021-9-18 08:41
就因为官方手册没有才容易出问题。反正就那么几条指令,目的及其明确,你能看出什么原因导致这种诡异的问 ...

也许BSY信号翻转有延迟?  但TXE和RXNE肯定不会, 或者你每次发送完,不关闭SPI试试,

我不用库函数,全部寄存器直接编程, 代码比直接汇编多不了多少, 有时也会看会汇编代码, 并不傻瓜,现在编译器的水平已经不低了,人写的汇编未必会超过它, 越是复杂的程序,用C的出错概率越低,stm32的flash普遍配置比较大(相对51系),即使浪费点代码也是值得的,很多时候用不完

手册上从来不推荐用BSY作为发送接收数据的依据, 你可以改成TXE再试试(这也是手册多次强调的,规范操作还是很重要), 我用TXE和RXNE从来没出错过
BSY我一般是作为主模式一次通信结束的标志(多个数据), 手册上也是这么建议的
回复 支持 反对

使用道具 举报

发表于 2021-9-18 09:52:51 | 显示全部楼层
la45088d1 发表于 2021-9-18 00:52
所以我通读了原版数据手册,发现在我的设置下读取busy就是可以达成数据不冲突的目的,所以没关系。
这次 ...

1、手册明确说明不应用BUSY位来处理发送接收,BUSY位应该用来确定SPI是否全部发送完成。只有BUSY位=0才可以拉高片选CS或者关闭SPI,否则可能导致最后一个数据传输不完整。
2、你一直强调读取SR错误,在我看来寄存器是0x02但是读到是0x00这叫读取错误。你这里我认为你读到0X00的时候寄存器本身就是0x00。所以我认为读取没有错误。
3、关于读取错误的验证:你用之前不加NOP的代码连续发两三个数据,不要循环发,直接写。然后断点放到第二或者第三个数据的相同位置,看看读取SR是不是0x81或者0x01(RXNE=1)。如果是说明读取没问题。
4、不知道你有没有去研究SPI发送逻辑。BUSY位:当发送缓冲区和移位寄存器都没有数据的时候=0。
TXE  位:当发送缓冲区没有数据时候=1。
SPI发送过程(第一个数据,按照你判断BUSY位的操作):
>SPI开启                                             ------BUSY=0,TXE=0,RXNE=0。
>软件向发送缓冲区写入数据。                -----BUSY=0,TXE=1,RXNE=0。
>SPI硬件将缓冲区数据转入移位寄存器    ------BUSY=0,TXE=0,RXNE=0。
>SPI硬件开始将缓冲区数据发出              ------BUSY=1,TXE=1,RXNE=0。
>数据发送完成                                      ------BUSY=0,TXE=1,RXNE=1。
>第二个数据写入缓冲区
。。。。。。

捕获.PNG

回复 支持 1 反对 0

使用道具 举报

发表于 2021-9-18 10:00:39 | 显示全部楼层
忘了说了,你固定加4个NOP再去读取SR判断。
有兴趣的话你可以用控制变量法测试下:最低SPI速率,最高SPI速率,最低系统时钟,最高系统时钟这四个不同组合的情况是不是都没问题。
回复 支持 反对

使用道具 举报

发表于 2021-9-18 10:07:34 | 显示全部楼层
应该不是读取sr的问题,是你刚刚写入到dr的几个周期里面,sr值就是0,我之前的测试,busy应该是要数据从缓存推到移位寄存器的时候才会变1
回复 支持 反对

使用道具 举报

发表于 2021-9-18 10:08:10 | 显示全部楼层
所以似乎用te位来判断就没这个问题
回复 支持 反对

使用道具 举报

 楼主| 发表于 2021-9-18 10:15:43 | 显示全部楼层
crazy0qwer 发表于 2021-9-18 09:52
1、手册明确说明不应用BUSY位来处理发送接收,BUSY位应该用来确定SPI是否全部发送完成。只有BUSY位=0才可 ...

事实上就是错了,你自己看我截的图,读到0x00时Keil的监视窗显示TXE=1,RXNE=1,这很明显读取结果不可能是0x00的。
另外现在程序把数据帧改成16-bit了,比起8-bit数据帧发一次测一次好太多。之前为了回避多次读取SPI_SR的问题直接改成16-bit所以只要发送一次就可以完成一帧传输,这个没写出来。但是依然没看见16个SCLK波形。证明还是状态判别出错,提前关闭了SPI。后面基于这个加了4个NOP就解决了,现在稳定没问题了,就不想去再次踩雷了。
不过第三条问题我可以明确告诉你,之前就是一个字节一个字节发的,共发送两次,第一次写入SPI_DR后,无延迟检测SPI_SR,等待Busy位置低SPI发完以后再写入第二次,然后再次立即循环检测SPI_SR的状态。断电测试显示写完以后立即读取,结果仍旧是0x00,哪怕Keil监视窗上面Busy闪过,TXE=1,RXNE=1.
而修改加了延时后,直接运行到状态检测循环后就出现回读数据为0x80,这时候恰好开始传输所以Busy=1,其它位为0,这样就没错。
至于SPI的信号的时序图,我已经反复了解了N次。之前我自己开荒使用DMA+SPI的时候,愣是几乎没参考已有历程,直接把SPI和DMA这个章节通读完,按照自己理解写的一个正确的程序,所以我觉得我对SPI常用的一些功能特性还是比较熟悉的,包括你说的那些信号建立流程。
回复 支持 反对

使用道具 举报

 楼主| 发表于 2021-9-18 10:17:22 | 显示全部楼层
crazy0qwer 发表于 2021-9-18 10:00
忘了说了,你固定加4个NOP再去读取SR判断。
有兴趣的话你可以用控制变量法测试下:最低SPI速率,最高SPI速 ...

有道理,闲着的时候试试,也算是侧面印证问题。:victory:
回复 支持 反对

使用道具 举报

 楼主| 发表于 2021-9-18 10:24:12 | 显示全部楼层
2545889167 发表于 2021-9-18 10:08
所以似乎用te位来判断就没这个问题

可是SPI_SR如果在写入数据那一瞬间开始计时,要延时几个周期的才出正确状态信息的话,无论检测那个位似乎都不怎么行哎。毕竟BSYy和TXE都在里面。不过根据分析,确实TXE比起BSY建立速度更快一些。因为TXE在缓冲区完成数据写入后很快就检测到了非空,立即置位,然后再把数据搬到发送移位寄存器后BSY才开始置位。
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

APP|手机版|小黑屋|关于我们|联系我们|法律条款|技术知识分享平台

闽公网安备35020502000485号

闽ICP备2021002735号-2

GMT+8, 2024-4-27 03:35 , Processed in 0.343200 second(s), 17 queries , Redis On.

Powered by Discuz!

© 2006-2023 smzj.net

快速回复 返回顶部 返回列表