当前位置:   article > 正文

STM32_SPI通信_不用中断不用DMA_stm32 spi中断

stm32 spi中断

为啥不用中断不用DMA,其实我觉得没必要,因为一般来讲SPI通信操作某些功能芯片,比如DAC芯片啊,传感器芯片啊,每次通信数据量都不大,就2~4个字节。所以就用最简单的阻塞式代码就可以了(HAL_SPI_Transmit & HAL_SPI_Receive)。

我认为SPI通信中最重要的一点是对于收发数据同步特性的理解。

在下面这篇博客中写了详细讲解了SPI通信的原理

【STM32】HAL库 STM32CubeMX教程十四---SPI_Z小旋的博客-CSDN博客_hal库spi

下面摘抄一段我认为比较重要的一段话:

SPI只有主模式和从模式之分,没有读和写的说法,外设的写操作和读操作是同步完成的。如果只进行写操作,主机只需忽略接收到的字节;反之,若主机要读取从机的一个字节,就必须发送一个【空字节】来引发从机的传输。也就是说,你发一个数据必然会收到一个数据;你要收一个数据必须也要先发一个数据。

同理,假设你是用SPI与传感器芯片通信

1、若你要读取传感器芯片的一个字节,就必须发送一个字节,但应该就不是空字节了,而是包含传感器芯片内部地址,读命令的含义的字节了。

2、若你要往传感器芯片写入一个字节,那可能就不止发送一个字节,首先第一个字节还是传感器芯片内部地址+写命令含义的字节。 第二个字节才是你要真正发送的字节。 这就意味着,你在这个过程,你会收到两个没有用的字节。那去不去接收呢? 答案是:收! 如果你不收,有可能造成接收通道阻塞,因为有数据来了之后,SPI和接收相关的某些计数器、标志位肯定会发生修改的,如果你这次不处理。 下次通过SPI读取的时候,可能会出问题。

然,在STM32芯片中,假设使用HAL_SPI_Transmit函数发送2字节(写操作),但发完后不用HAL_SPI_Receive函数去接收2字节没用的字节。 下次再用HAL_SPI_Transmit函数发送1字节(读操作), 再用HAL_SPI_Receive函数去接收一个字节。 这个结果会怎么样呢? 结果接收的字节数据正确的。why? 上面讲的东西不对? 我猜测是HAL_SPI_Transmit把SPI接收寄存器的数据已经读了,把底层的标志已经清了。然后读到那里去了呢?应该是读到一个HAL_SPI_Receive同样也能访问的内存里了。所以它不干扰HAL_SPI_Receive函数的使用。 不过即使是这样,我建议还是HAL_SPI_Transmit发多少,HAL_SPI_Receive就去接多少。

另外,同样的操作,在DSP上就不行,你发多少就得接收多少。因为它必须用读SPIRXBUF这个操作,才能清标志。DSP上的代码大多还是以直接操作寄存器为主,封装的函数很多还不靠谱。

还是把代码加上,显得充实一些

  1. // STM32代码
  2. #define spi_cs_A_Enable HAL_GPIO_WritePin(GPIOD,GPIO_PIN_2, GPIO_PIN_RESET)
  3. #define spi_cs_A_Disable HAL_GPIO_WritePin(GPIOD,GPIO_PIN_2, GPIO_PIN_SET)
  4. // PD2 - A axis的片选线
  5. void SPI_A_READ(unsigned char addr, unsigned char * Rxdata){
  6. spi_cs_A_Enable;
  7. HAL_SPI_Transmit(&hspi1,&addr,1,100);
  8. HAL_SPI_Receive(&hspi1,Rxdata,1,100);
  9. spi_cs_A_Disable;
  10. }
  11. void SPI_A_WRITE(unsigned char addr, unsigned char Txdata){
  12. unsigned char Rxdata=0x00;
  13. spi_cs_A_Enable;
  14. HAL_SPI_Transmit(&hspi1,&addr,1,100);
  15. HAL_SPI_Transmit(&hspi1,&Txdata,1,100);
  16. // 经测试,地址字节和数据字节必须连续发送。 否则无法正常写入
  17. // 接不接收都不影响下一次对SPI通信的使用,接收的数据是无用的,但稳妥起见,还是接一下吧
  18. HAL_SPI_Receive(&hspi1,&Rxdata,1,100);
  19. HAL_SPI_Receive(&hspi1,&Rxdata,1,100);
  20. spi_cs_A_Disable;
  21. }

干脆把DSP的代码也加上吧,本例DSP操作的是一个DAC芯片,而该DAC芯片的寄存器是16位了,加上命令字节(1位读写+7位地址)一次需要发送三个字节。

  1. // DSP代码
  2. // Read the register of DAC chip
  3. Uint16 ReadDAC(Uint16 addr){
  4. Uint16 SpiTxBuff;
  5. volatile Uint16 SpiRxBuff_NOP;
  6. Uint16 SpiRxBuff_High;
  7. Uint16 SpiRxBuff_Low;
  8. SpiTxBuff = (addr << 8);
  9. SpiTxBuff = (SpiTxBuff | 0x8000);
  10. while(SpibRegs.SPISTS.bit.BUFFULL_FLAG==1);
  11. SpibRegs.SPITXBUF = SpiTxBuff;
  12. while(SpibRegs.SPISTS.bit.BUFFULL_FLAG==1);
  13. SpibRegs.SPITXBUF = 0;
  14. while(SpibRegs.SPISTS.bit.BUFFULL_FLAG==1);
  15. SpibRegs.SPITXBUF = 0;
  16. while(SpibRegs.SPISTS.bit.INT_FLAG==0);
  17. SpiRxBuff_NOP = SpibRegs.SPIRXBUF;
  18. while(SpibRegs.SPISTS.bit.INT_FLAG==0);
  19. SpiRxBuff_High = SpibRegs.SPIRXBUF;
  20. while(SpibRegs.SPISTS.bit.INT_FLAG==0);
  21. SpiRxBuff_Low = SpibRegs.SPIRXBUF;
  22. return ((SpiRxBuff_High << 8) | (SpiRxBuff_Low&0x00FF));
  23. }
  24. // Write data to the register of DAC chip
  25. void WriteDAC(Uint16 addr, Uint16 value){
  26. Uint16 SpiTxBuff;
  27. volatile Uint16 SpiRxBuff_NOP;
  28. Uint16 i = 0;
  29. Uint16 WaitFlag = 1;
  30. SpiTxBuff = (addr << 8);
  31. SpiTxBuff = (SpiTxBuff & 0x7FFF);
  32. while(SpibRegs.SPISTS.bit.BUFFULL_FLAG==1);
  33. SpibRegs.SPITXBUF = SpiTxBuff; // SPI配置了是一次发8位数据(1字节),但是实测是高8位有效。
  34. // (DSP的寄存器 存储器的最小数据单元都是16位)
  35. while(SpibRegs.SPISTS.bit.BUFFULL_FLAG==1);
  36. SpibRegs.SPITXBUF = (value & 0xFF00);
  37. while(SpibRegs.SPISTS.bit.BUFFULL_FLAG==1);
  38. SpibRegs.SPITXBUF = (value << 8);
  39. i = 0; WaitFlag = 1; // 加个WaitFlag主要为了防止通信发送异常,一直等下去。
  40. while(SpibRegs.SPISTS.bit.INT_FLAG==0 && WaitFlag == 1){
  41. i++; if(i>10000){ WaitFlag = 0; }
  42. }
  43. if(WaitFlag!=0) SpiRxBuff_NOP = SpibRegs.SPIRXBUF;
  44. i = 0; WaitFlag = 1;
  45. while(SpibRegs.SPISTS.bit.INT_FLAG==0 && WaitFlag == 1){
  46. i++; if(i>10000){ WaitFlag = 0; }
  47. }
  48. if(WaitFlag!=0) SpiRxBuff_NOP = SpibRegs.SPIRXBUF;
  49. i = 0; WaitFlag = 1;
  50. while(SpibRegs.SPISTS.bit.INT_FLAG==0 && WaitFlag == 1){
  51. i++; if(i>10000){ WaitFlag = 0; }
  52. }
  53. if(WaitFlag!=0) SpiRxBuff_NOP = SpibRegs.SPIRXBUF;
  54. }

还有一个需要注意的东西,就是片选信号线 SPI_CS。在最近接触的小吊舱项目中,让我彻底理解了片选到底是个什么概念。该项目就是一个主设备的SPI接口,连了两个从设备。所谓片选,就是你选择和哪个从设备通信的意思。 连接电路示意图如下所示。

当我们要与SPI Slave1通信时, CS1拉低,CS2拉高。 当我们要与SPI Slave2通信时,CS2拉低,CS1拉高。 另外,CS1,CS2引脚,都需配置成GPIO - 输出 引脚。写代码的时候,初始化把CS1,CS2引脚都拉高。 使用的时候,用哪个就拉低哪个,用完之后,再拉高。

加点自己的脑洞:

从上面的例子可以看出你有几个从设备,你就得有几个片选线。但如果你的从设备特别多怎么办呢, 那不是片选线把主设备的引脚都用完了。其实如果你要节约片选线,其实中间再加点逻辑器件就可以了。比如你16个设备,只需要4根片选线就可以了。 4根片选线就能表示2^4 = 16种选择了。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/article/detail/50896
推荐阅读
相关标签
  

闽ICP备14008679号