在CUBEMX下,使用STM32F103 SPI做从站的笔记

原文链接: http://www.cnblogs.com/stonenox/p/10575117.html

  之前做STM32的项目, 一直都用的是标准固件库。最近有个比较简单的项目,就想试试ST强推的开发工具cubemx。

  用了下来,感觉CUBEMX的 HAL库做得很模块化,让一些用户远离了底层。但是也有缺点:

    1. 各种模块,应用都层次化了,所以调用关系也比自己写繁琐得多。

    2.虽然简化了很多应用的开发过程,但也是因为把驱动模块化了,但不灵活,面对一些特殊点的场合,就容易出现问题。

    3.一旦代码出问题,找起故障来很麻烦,在各种函数中跳来跳去。比如我在SPI中遇到问题,要查故障,从总中断,跳到TX子服务,然后又执行一个注册的中断处理函数,最后去执行用户回调函数。跳来跳去的。

  结论: 即便是用CUBEMX来做项目,还是需要看STM32的用户手册,去了解各种寄存器,各种外设的特点,不然只知道简单用法,不知道执行原理,是没办法排查故障和实现任务的。

  好了,现在说说STM32用来做SPI从站的问题。

一  CUbe MX生成代码

  首先把SYS,时钟等设好(不多说了)。然后就开始SPI的设置。

  我把SPI pin 设置为从站,关闭 NSS,并将PA4作为EXTI4 。使用EXTI4作为一个数据帧的起始标志。

  当然,也可以不用EXTI4做帧起始,而用定时器来识别帧的起始字节( 通信时间间隔大于XXMS,代表开始了新的通信帧)。

    

  在 configuration中,做参数设置:

       

  NVIC Setting中开启SPI中断。

       

  最后在project菜单里,执行Generate Code ,生成代码即可。

二  如何在程序中实现基本的SPI通信。

  对于基础运用,相当简单:

  在主函数中,执行: 

      HAL_SPI_TransmitReceive_IT(&hspi1, TXbuf,RXbuf,CommSize);

  当SPI上出现了 CommSize个字节的数据后,中断函数会调用回调:

      HAL_SPI_TxRxCpltCallback(SPI_HandleTypeDef *hspi)

  这在HAL中是个弱申明的函数,我们只需要做一个自己的同名回调函数,实现预定功能即可,编译时会自动替换。

  就靠这两个函数,基本的通信就没问题了。

三  是不是很简单?!然而事实是残酷的,这还没完。SPI从站的问题:

  如果就这样使用,你会发现从站数据(MISO),有时能准确到达主站,有时却整体后移了一个字节导致数据不完整。并且回调函数也不是很灵,时而执行,时而不执行。

  跟踪代码,你会发现,执行中,有两种情况:

      1. 执行HAL_SPI_TransmitReceive_IT 后,即便主站没有发起通信,从站也立刻跳入了TX_ISR中断。 这种情况,程序执行时正常的。

  2.执行HAL_SPI_TransmitReceive_IT 后,未跳入TX_ISR。当主站发起通信时,从站先跳入TX中断执行DR=S0,然后跳入RX中断执行R0=DR。看起来没问题,然而,结果就是主站收到的数据不正确。而且HAL_SPI_TxRxCpltCallback回调函数页没有执行,HAL认为 “故障:DR寄存器中还有数据没有发出”。 

  情况2是如何产生的呢?简单的说,就是某次通信出现故障后,TXE无法正确清空(置1),导致通信错位。

  具体的说,就要从STM32的SPI的通信原理说起了。

  一次正确的执行流程,时序图是如下的:

  是不是看起来有点懵?

  简单的说,就是需要按照如下顺序做通信:

  1.通信前,执行HAL_TransmationRecive_IT() .

  2. 由于DR此时一般为空,所以产生了TXE中断 ,将SendByte0 先写入DR寄存器。

  3.当第一个CLK到来时,DR中的SendByte0会放到总线上,此时,DR空就会产生一个TXE中断!将SendByte1写入DR寄存器。

  4.再执行7个CLK 。

  5.第8个CLK完成后,发生RX中断,DR= RecByte0!

  6.第9个CLK,又发生了TXE中断...

  上面是正常情况,此时 ,TXE中断需要比 RXE中断先产生两个回合,才能保证SendByte 和 RecByte都依次完整到传输!

  如果由于某种原因(比如上次通信失败),TXE最初是0,则发送DR会丢失第一个中断。这将导致SendByte推迟一个字节!

  可惜的是,TXE是只读寄存器,无法通过手动清理来使其恢复为空。也就是说,一旦通信发生过一次错误,那么,在使用HAL库的情况下, 这个错误就无法消除。

  并且,由于丢失了第一个TXE中断,HAL在计算已发送字节的时候,会发现少发送了一个字节,这会导致回调函数“HAL_SPI_TxRxCpltCallback”无法执行。

四 解决的办法

  一种解决办法:对HAL库做修改

    在HAL_TransmationRecive_IT()中, 增加代码,判断TXE是否空。如果空,则正常执行; 如果为满,则直接把SendByte0写入DR寄存器,并执行TxXferCount--;相当于手动执行了第一次中断服务。

     但这个办法有个麻烦的地方,就是以后每次使用CUBEMX填写功能块时,编译器会自动把HAL_TransmationRecive_IT()函数恢复,我们就需要不断的去修改它的代码。

  另一种解决办法:

    不论TXE的状态,先执行 DR = SendByte0;

    然后,再执行HAL_TransmationRecive_IT()。此后,在SPI通信前,TXE将会一直保持满的状态。

    Hspi-> TxXferCount-- ,手动减少发送倒计数。

    这个方法的好处是,不用修改HAL代码,更新程序不受影响。

  可见,如果对SPI的执行原理不了解, 单纯使用HAL,还是容易出现一些问题的。

       使用HAL,虽然让项目更快,普通情况下也更安全,但是为了解决一些特殊的情况,我们还是要掌握STM32的用户手册,多看看寄存器和执行原理。

  

  

  

  

  

转载于:https://www.cnblogs.com/stonenox/p/10575117.html

猜你喜欢

转载自blog.csdn.net/weixin_30810583/article/details/94837374