[其他ST产品] STM32F103 SPI(踩坑日记)

[复制链接]
 楼主| 怎么总是重复啊 发表于 2023-1-26 14:16 | 显示全部楼层 |阅读模式
前言
第1部分针对的spi的基础知识
第2、3部分是使用中遇到的坑和自己的理解。也欢迎大佬对文章中错误内容指出、更正。
可以有选择的阅读。

1.SPI 协议
SPI是串行外设接口(Serial Peripheral Interface)的缩写,是一种高速的,全双工,同步的通信总线,并且在芯片的管脚上只占用四根线。分别是以下4根

MISO– Master Input Slave Output,主设备数据输入,从设备数据输出;
MOSI– Master Output Slave Input,主设备数据输出,从设备数据输入;
SCLK – Serial Clock,时钟信号,由主设备产生;
CS – Chip Select,从设备使能信号,由主设备控制。

评论

———————————————— 版权声明:本文为CSDN博主「柒妖71」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。 原文链接:https://blog.csdn.net/qq_39854159/article/details/122260159  发表于 2023-1-26 14:17
 楼主| 怎么总是重复啊 发表于 2023-1-26 14:18 | 显示全部楼层
SPI的4种模式
spi的4种模式是通过CPOL和CPHA设置0和1来决定的。排列组合一下一共4种:
CPOL: SPI空闲时的时钟信号电平(1:高电平, 0:低电平)
CPHA: SPI在时钟第几个边沿采样(1:第二个边沿开始, 0:第一个边沿开始)
3815463d21b327c72e.png
1488763d21b3aa376b.png
sfd123 发表于 2023-1-26 15:15 | 显示全部楼层
请问:哪里容易入坑???
 楼主| 怎么总是重复啊 发表于 2023-1-26 15:31 | 显示全部楼层
那么在STM32中体现是在这个结构体中(有省略):
  1. typedef struct
  2. {
  3. ......
  4.   uint16_t SPI_CPOL;                /*!< Specifies the serial clock steady state.
  5.                                         This parameter can be a value of [url=home.php?mod=space&uid=144993]@ref[/url] SPI_Clock_Polarity */
  6.   uint16_t SPI_CPHA;                /*!< Specifies the clock active edge for the bit capture.
  7. ......
  8. }SPI_InitTypeDef;
 楼主| 怎么总是重复啊 发表于 2023-1-26 15:32 | 显示全部楼层
这边需要根据从站的datasheet来配置,比如下面的时序图:
9217363d22c7b6723a.png
SCLK默认为高电平,从sck的第二个边沿采集数据位,所以:
CPOL=1;
CPHA=1;
 楼主| 怎么总是重复啊 发表于 2023-1-26 15:43 | 显示全部楼层
STM32F103 硬件SPI
STMf103的SPI->DR寄存器是个16位的,从参考手册上来看F1的芯片支持8位和16位的数据发送,通过SPI->CR1的DFF标志位来决定传输的数据是8位还是16位。这个也是需要参考从站的datasheet来决定的。 主模式波特率预分频系数(最大为fPCLK/2) ,所以SPI可以最高分到一个36MHz的频率。关于SPI的发送函数,标准库函数版本和HAL库提供了两种解决办法:
 楼主| 怎么总是重复啊 发表于 2023-1-26 15:45 | 显示全部楼层
标准库的发送函数
  1. /**
  2.   * [url=home.php?mod=space&uid=247401]@brief[/url]  Transmits a Data through the SPIx/I2Sx peripheral.
  3.   * @param  SPIx: where x can be
  4.   *   - 1, 2 or 3 in SPI mode
  5.   *   - 2 or 3 in I2S mode
  6.   * @param  Data : Data to be transmitted.
  7.   * @retval None
  8.   */
  9. void SPI_I2S_SendData(SPI_TypeDef* SPIx, uint16_t Data)
  10. {
  11.   /* Check the parameters */
  12.   assert_param(IS_SPI_ALL_PERIPH(SPIx));
  13.   
  14.   /* Write in the DR register the data to be sent */
  15.   SPIx->DR = Data;
  16. }
 楼主| 怎么总是重复啊 发表于 2023-1-26 15:46 | 显示全部楼层
assert_param这个断言可以不管,其实就是把数据写到DR寄存器,什么判断都没有。所以一般我们在使用的时候超时跳出和检测发送是否成功,如下:
  1. while (SPI_I2S_GetFlagStatus(SPI2, SPI_I2S_FLAG_TXE) == RESET) //检查指定的SPI标志位设置与否:发送缓存空标志位
  2.                 {
  3.                 retry++;
  4.                 if(retry>200)return 0;//重试200次
  5.                 }                       
 楼主| 怎么总是重复啊 发表于 2023-1-26 15:51 | 显示全部楼层
但是执行retry++和SPI_I2S_GetFlagStatus(SPI2, SPI_I2S_FLAG_TXE) == RESET需要占用CPU时间,这会导致SPI在发送时出现非连续传输详见3.节。
 楼主| 怎么总是重复啊 发表于 2023-1-26 15:52 | 显示全部楼层
2.2 HAL库发送函数
HAL库的发送函数比较长,我这边做了删减只看8位的发送的过程。
HAL_StatusTypeDef HAL_SPI_Transmit(SPI_HandleTypeDef *hspi, uint8_t *pData, uint16_t Size, uint32_t Timeout)
{
        .......
          /* Set the transaction information */
        hspi->State       = HAL_SPI_STATE_BUSY_TX;
          hspi->ErrorCode   = HAL_SPI_ERROR_NONE;
        hspi->pTxBuffPtr  = (uint8_t *)pData;
          hspi->TxXferSize  = Size;
          hspi->TxXferCount = Size;
          .......
    while (hspi->TxXferCount > 0U)
    {
      /* Wait until TXE flag is set to send data */
      if (__HAL_SPI_GET_FLAG(hspi, SPI_FLAG_TXE))
      {
        *((__IO uint8_t *)&hspi->Instance->DR) = (*hspi->pTxBuffPtr);
        hspi->pTxBuffPtr += sizeof(uint8_t);
        hspi->TxXferCount--;
      }
      else
      {
        /* Timeout management */
        if ((((HAL_GetTick() - tickstart) >=  Timeout) && (Timeout != HAL_MAX_DELAY)) || (Timeout == 0U))
        {
          errorcode = HAL_TIMEOUT;
          goto error;
        }
      }
    }

}
 楼主| 怎么总是重复啊 发表于 2023-1-26 15:53 | 显示全部楼层
核心部分:
  1. if (__HAL_SPI_GET_FLAG(hspi, SPI_FLAG_TXE))
  2.    *((__IO uint8_t *)&hspi->Instance->DR) = (*hspi->pTxBuffPtr);
 楼主| 怎么总是重复啊 发表于 2023-1-26 16:03 | 显示全部楼层
也是检查发送完成标志位和把数据写到DR寄存器中。但是HAL库写的比较好的是支持写数据长度了。一般我们不会只写8位,一般一条指令都有32位或者更长,HAL库的封装我只需要提供一个首地址,把Size设置成对应的长度就可以了。
 楼主| 怎么总是重复啊 发表于 2023-1-26 16:04 | 显示全部楼层
这里有个小坑
数据在单片机中存储是按小端格式存储的,但是往往数据发送是按大端格式发送的。
比如想发送01 02 03 04
但是我们很容易存成:

uint32_t data =0x01020304
1
然后把*pData指向data的地址,那么通过SPI发出去的数据会变成 04 03 02 01所以大端小端转换需要软件去做转换。
 楼主| 怎么总是重复啊 发表于 2023-1-26 16:06 | 显示全部楼层
SPI的连续传输和非连续传输

前提条件,这里设置的是8位的spi传输,先看下传输32位数据时时序图的区别:
1734163d234647c93b.png

图3.1 SPI非连续传输

4479663d2346b3f746.png
图3.2 SPI连续传输
结果是如果是非连续传输的时候,每8位之间sck信号会断开,产生一个1us左右的延时,这个延时大小可能不一样。我这边SPI速度是2.25MHz,那么我使用连续传输32位数据会快20%。 翻阅《STM32中文参考手册》
当在主模式下发送数据时,如果软件足够快,能够在检测到每次TXE的上升沿(或TXE中断),并立即在正在进行的传输结束之前写入SPI_DR寄存器,则能够实现连续的通信;此时,在每个数据项的传输之间的SPI时钟保持连续,同时BSY位不会被清除。如果软件不够快,则会导致不连续的通信;这时,在每个数据传输之间会被清除

从这段描述来说,只要你数据写的够快,他就可以连续传输。这显然是不靠谱的,SPI速度越快软件写入的速度会跟不上。那么怎么充分发挥ST硬件SPI的性能呢? 开启DMA模式。
 楼主| 怎么总是重复啊 发表于 2023-1-26 16:11 | 显示全部楼层
SPI+DMA传输的坑
根据自己SPI通道选择DMA配置,使用STM32Cube进行配置
这是我写的一个发送函数,但是抓波形的时候发现发送的数据不对,但是不使用DMA发送就正常了。后来研究了很久才发现问题。
 楼主| 怎么总是重复啊 发表于 2023-1-26 16:14 | 显示全部楼层
  1. int sgm5349_RegWrite(sgm5349Device_t *device, uint8_t channel, uint16_t data, uint8_t update){
  2.     HAL_StatusTypeDef status = HAL_OK;
  3.     uint32_t regVal = 0;   
  4.     /* Input Validation Check */
  5.     if(device == NULL)    return -1;
  6.     if(isChannelValid(channel) != 0)    return -1;
  7.     /* 32bit register value generation with command = 0 or 2 or 3*/
  8.     regVal = (channel << 20) | (data << 4);
  9.     if(update == 1)        regVal |= (3 << 24);
  10.     else if(update == 2)   regVal |= (2 << 24);
  11.         regVal = LittleEndian2BigEndian(regVal);
  12.         HAL_GPIO_WritePin(GPIOA,GPIO_PIN_3,0);
  13.         HAL_SPI_Transmit_DMA(device->hspi, (uint8_t *)&regVal, 4);
  14.         HAL_GPIO_WritePin(GPIOA,GPIO_PIN_3,1);
  15.        
  16.     if(status != HAL_OK)    return (int)status;
  17.     return 0;
  18. }
 楼主| 怎么总是重复啊 发表于 2023-1-26 16:21 | 显示全部楼层
DMA发送代码如下所示(部分省略):
  1. HAL_StatusTypeDef HAL_SPI_Transmit_DMA(SPI_HandleTypeDef *hspi, uint8_t *pData, uint16_t Size)
  2. {
  3. ......
  4.   /* Set the transaction information */
  5.   hspi->State       = HAL_SPI_STATE_BUSY_TX;
  6.   hspi->ErrorCode   = HAL_SPI_ERROR_NONE;
  7.   hspi->pTxBuffPtr  = (uint8_t *)pData;
  8.   hspi->TxXferSize  = Size;
  9.   hspi->TxXferCount = Size;
  10. /* Enable the Tx DMA Stream/Channel */
  11.   if (HAL_OK != HAL_DMA_Start_IT(hspi->hdmatx, (uint32_t)hspi->pTxBuffPtr, (uint32_t)&hspi->Instance->DR,
  12.                                  hspi->TxXferCount))
  13.   {
  14.     /* Update SPI error code */
  15.     SET_BIT(hspi->ErrorCode, HAL_SPI_ERROR_DMA);
  16.     errorcode = HAL_ERROR;

  17.     hspi->State = HAL_SPI_STATE_READY;
  18.     goto error;
  19.   }
  20. ......
  21. }

 楼主| 怎么总是重复啊 发表于 2023-1-26 16:23 | 显示全部楼层
问题出在DMA发送函数中,(uint32_t)hspi->pTxBuffPtr强制转换成了数据的地址。但是我们传入HAL_SPI_Transmit_DMA的可是一个局部变量regVal。当跳出sgm5349_RegWrite函数时局部变量就会释放regVal,那么这个局部变量的地址就没意义了。所以做了个简单测试加了个延时,就能正常发送数据。当然实际并不能这么做,可以通过加给局部变量+static修饰,也可以让程序通过查询发送完成标志位死等来解决这个问题。
最后获得了一个完整的连续的SPI数据发送
您需要登录后才可以回帖 登录 | 注册

本版积分规则

29

主题

262

帖子

1

粉丝
快速回复 在线客服 返回列表 返回顶部