赞
踩
为啥不用中断不用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上的代码大多还是以直接操作寄存器为主,封装的函数很多还不靠谱。
还是把代码加上,显得充实一些
- // STM32代码
- #define spi_cs_A_Enable HAL_GPIO_WritePin(GPIOD,GPIO_PIN_2, GPIO_PIN_RESET)
- #define spi_cs_A_Disable HAL_GPIO_WritePin(GPIOD,GPIO_PIN_2, GPIO_PIN_SET)
- // PD2 - A axis的片选线
-
- void SPI_A_READ(unsigned char addr, unsigned char * Rxdata){
- spi_cs_A_Enable;
- HAL_SPI_Transmit(&hspi1,&addr,1,100);
- HAL_SPI_Receive(&hspi1,Rxdata,1,100);
- spi_cs_A_Disable;
- }
-
- void SPI_A_WRITE(unsigned char addr, unsigned char Txdata){
- unsigned char Rxdata=0x00;
- spi_cs_A_Enable;
- HAL_SPI_Transmit(&hspi1,&addr,1,100);
- HAL_SPI_Transmit(&hspi1,&Txdata,1,100);
- // 经测试,地址字节和数据字节必须连续发送。 否则无法正常写入
-
- // 接不接收都不影响下一次对SPI通信的使用,接收的数据是无用的,但稳妥起见,还是接一下吧
- HAL_SPI_Receive(&hspi1,&Rxdata,1,100);
- HAL_SPI_Receive(&hspi1,&Rxdata,1,100);
-
- spi_cs_A_Disable;
- }
干脆把DSP的代码也加上吧,本例DSP操作的是一个DAC芯片,而该DAC芯片的寄存器是16位了,加上命令字节(1位读写+7位地址)一次需要发送三个字节。
- // DSP代码
-
- // Read the register of DAC chip
- Uint16 ReadDAC(Uint16 addr){
- Uint16 SpiTxBuff;
- volatile Uint16 SpiRxBuff_NOP;
- Uint16 SpiRxBuff_High;
- Uint16 SpiRxBuff_Low;
-
- SpiTxBuff = (addr << 8);
- SpiTxBuff = (SpiTxBuff | 0x8000);
-
- while(SpibRegs.SPISTS.bit.BUFFULL_FLAG==1);
- SpibRegs.SPITXBUF = SpiTxBuff;
- while(SpibRegs.SPISTS.bit.BUFFULL_FLAG==1);
- SpibRegs.SPITXBUF = 0;
- while(SpibRegs.SPISTS.bit.BUFFULL_FLAG==1);
- SpibRegs.SPITXBUF = 0;
-
- while(SpibRegs.SPISTS.bit.INT_FLAG==0);
- SpiRxBuff_NOP = SpibRegs.SPIRXBUF;
- while(SpibRegs.SPISTS.bit.INT_FLAG==0);
- SpiRxBuff_High = SpibRegs.SPIRXBUF;
- while(SpibRegs.SPISTS.bit.INT_FLAG==0);
- SpiRxBuff_Low = SpibRegs.SPIRXBUF;
-
- return ((SpiRxBuff_High << 8) | (SpiRxBuff_Low&0x00FF));
-
- }
-
- // Write data to the register of DAC chip
- void WriteDAC(Uint16 addr, Uint16 value){
-
- Uint16 SpiTxBuff;
- volatile Uint16 SpiRxBuff_NOP;
- Uint16 i = 0;
- Uint16 WaitFlag = 1;
-
- SpiTxBuff = (addr << 8);
- SpiTxBuff = (SpiTxBuff & 0x7FFF);
-
- while(SpibRegs.SPISTS.bit.BUFFULL_FLAG==1);
- SpibRegs.SPITXBUF = SpiTxBuff; // SPI配置了是一次发8位数据(1字节),但是实测是高8位有效。
- // (DSP的寄存器 存储器的最小数据单元都是16位)
- while(SpibRegs.SPISTS.bit.BUFFULL_FLAG==1);
- SpibRegs.SPITXBUF = (value & 0xFF00);
- while(SpibRegs.SPISTS.bit.BUFFULL_FLAG==1);
- SpibRegs.SPITXBUF = (value << 8);
-
-
- i = 0; WaitFlag = 1; // 加个WaitFlag主要为了防止通信发送异常,一直等下去。
- while(SpibRegs.SPISTS.bit.INT_FLAG==0 && WaitFlag == 1){
- i++; if(i>10000){ WaitFlag = 0; }
- }
- if(WaitFlag!=0) SpiRxBuff_NOP = SpibRegs.SPIRXBUF;
-
- i = 0; WaitFlag = 1;
- while(SpibRegs.SPISTS.bit.INT_FLAG==0 && WaitFlag == 1){
- i++; if(i>10000){ WaitFlag = 0; }
- }
- if(WaitFlag!=0) SpiRxBuff_NOP = SpibRegs.SPIRXBUF;
-
- i = 0; WaitFlag = 1;
- while(SpibRegs.SPISTS.bit.INT_FLAG==0 && WaitFlag == 1){
- i++; if(i>10000){ WaitFlag = 0; }
- }
- if(WaitFlag!=0) SpiRxBuff_NOP = SpibRegs.SPIRXBUF;
-
- }
还有一个需要注意的东西,就是片选信号线 SPI_CS。在最近接触的小吊舱项目中,让我彻底理解了片选到底是个什么概念。该项目就是一个主设备的SPI接口,连了两个从设备。所谓片选,就是你选择和哪个从设备通信的意思。 连接电路示意图如下所示。

当我们要与SPI Slave1通信时, CS1拉低,CS2拉高。 当我们要与SPI Slave2通信时,CS2拉低,CS1拉高。 另外,CS1,CS2引脚,都需配置成GPIO - 输出 引脚。写代码的时候,初始化把CS1,CS2引脚都拉高。 使用的时候,用哪个就拉低哪个,用完之后,再拉高。
加点自己的脑洞:
从上面的例子可以看出你有几个从设备,你就得有几个片选线。但如果你的从设备特别多怎么办呢, 那不是片选线把主设备的引脚都用完了。其实如果你要节约片选线,其实中间再加点逻辑器件就可以了。比如你16个设备,只需要4根片选线就可以了。 4根片选线就能表示2^4 = 16种选择了。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。