打印
[其他ST产品]

STM32F103 SPI(踩坑日记)

[复制链接]
700|17
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
前言
第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,从设备使能信号,由主设备控制。

使用特权

评论回复
沙发
怎么总是重复啊|  楼主 | 2023-1-26 14:18 | 只看该作者
SPI的4种模式
spi的4种模式是通过CPOL和CPHA设置0和1来决定的。排列组合一下一共4种:
CPOL: SPI空闲时的时钟信号电平(1:高电平, 0:低电平)
CPHA: SPI在时钟第几个边沿采样(1:第二个边沿开始, 0:第一个边沿开始)

使用特权

评论回复
板凳
sfd123| | 2023-1-26 15:15 | 只看该作者
请问:哪里容易入坑???

使用特权

评论回复
地板
怎么总是重复啊|  楼主 | 2023-1-26 15:31 | 只看该作者
那么在STM32中体现是在这个结构体中(有省略):
typedef struct
{
......
  uint16_t SPI_CPOL;                /*!< Specifies the serial clock steady state.
                                        This parameter can be a value of [url=home.php?mod=space&uid=144993]@ref[/url] SPI_Clock_Polarity */
  uint16_t SPI_CPHA;                /*!< Specifies the clock active edge for the bit capture.
......
}SPI_InitTypeDef;

使用特权

评论回复
5
怎么总是重复啊|  楼主 | 2023-1-26 15:32 | 只看该作者
这边需要根据从站的datasheet来配置,比如下面的时序图:

SCLK默认为高电平,从sck的第二个边沿采集数据位,所以:
CPOL=1;
CPHA=1;

使用特权

评论回复
6
怎么总是重复啊|  楼主 | 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库提供了两种解决办法:

使用特权

评论回复
7
怎么总是重复啊|  楼主 | 2023-1-26 15:45 | 只看该作者
标准库的发送函数
/**
  * [url=home.php?mod=space&uid=247401]@brief[/url]  Transmits a Data through the SPIx/I2Sx peripheral.
  * @param  SPIx: where x can be
  *   - 1, 2 or 3 in SPI mode
  *   - 2 or 3 in I2S mode
  * @param  Data : Data to be transmitted.
  * @retval None
  */
void SPI_I2S_SendData(SPI_TypeDef* SPIx, uint16_t Data)
{
  /* Check the parameters */
  assert_param(IS_SPI_ALL_PERIPH(SPIx));
  
  /* Write in the DR register the data to be sent */
  SPIx->DR = Data;
}

使用特权

评论回复
8
怎么总是重复啊|  楼主 | 2023-1-26 15:46 | 只看该作者
assert_param这个断言可以不管,其实就是把数据写到DR寄存器,什么判断都没有。所以一般我们在使用的时候超时跳出和检测发送是否成功,如下:
while (SPI_I2S_GetFlagStatus(SPI2, SPI_I2S_FLAG_TXE) == RESET) //检查指定的SPI标志位设置与否:发送缓存空标志位
                {
                retry++;
                if(retry>200)return 0;//重试200次
                }                       

使用特权

评论回复
9
怎么总是重复啊|  楼主 | 2023-1-26 15:51 | 只看该作者
但是执行retry++和SPI_I2S_GetFlagStatus(SPI2, SPI_I2S_FLAG_TXE) == RESET需要占用CPU时间,这会导致SPI在发送时出现非连续传输详见3.节。

使用特权

评论回复
10
怎么总是重复啊|  楼主 | 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;
        }
      }
    }

}

使用特权

评论回复
11
怎么总是重复啊|  楼主 | 2023-1-26 15:53 | 只看该作者
核心部分:
if (__HAL_SPI_GET_FLAG(hspi, SPI_FLAG_TXE))
   *((__IO uint8_t *)&hspi->Instance->DR) = (*hspi->pTxBuffPtr);

使用特权

评论回复
12
怎么总是重复啊|  楼主 | 2023-1-26 16:03 | 只看该作者
也是检查发送完成标志位和把数据写到DR寄存器中。但是HAL库写的比较好的是支持写数据长度了。一般我们不会只写8位,一般一条指令都有32位或者更长,HAL库的封装我只需要提供一个首地址,把Size设置成对应的长度就可以了。

使用特权

评论回复
13
怎么总是重复啊|  楼主 | 2023-1-26 16:04 | 只看该作者
这里有个小坑
数据在单片机中存储是按小端格式存储的,但是往往数据发送是按大端格式发送的。
比如想发送01 02 03 04
但是我们很容易存成:

uint32_t data =0x01020304
1
然后把*pData指向data的地址,那么通过SPI发出去的数据会变成 04 03 02 01所以大端小端转换需要软件去做转换。

使用特权

评论回复
14
怎么总是重复啊|  楼主 | 2023-1-26 16:06 | 只看该作者
SPI的连续传输和非连续传输

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


图3.1 SPI非连续传输


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

从这段描述来说,只要你数据写的够快,他就可以连续传输。这显然是不靠谱的,SPI速度越快软件写入的速度会跟不上。那么怎么充分发挥ST硬件SPI的性能呢? 开启DMA模式。

使用特权

评论回复
15
怎么总是重复啊|  楼主 | 2023-1-26 16:11 | 只看该作者
SPI+DMA传输的坑
根据自己SPI通道选择DMA配置,使用STM32Cube进行配置
这是我写的一个发送函数,但是抓波形的时候发现发送的数据不对,但是不使用DMA发送就正常了。后来研究了很久才发现问题。

使用特权

评论回复
16
怎么总是重复啊|  楼主 | 2023-1-26 16:14 | 只看该作者
int sgm5349_RegWrite(sgm5349Device_t *device, uint8_t channel, uint16_t data, uint8_t update){
    HAL_StatusTypeDef status = HAL_OK;
    uint32_t regVal = 0;   
    /* Input Validation Check */
    if(device == NULL)    return -1;
    if(isChannelValid(channel) != 0)    return -1;
    /* 32bit register value generation with command = 0 or 2 or 3*/
    regVal = (channel << 20) | (data << 4);
    if(update == 1)        regVal |= (3 << 24);
    else if(update == 2)   regVal |= (2 << 24);
        regVal = LittleEndian2BigEndian(regVal);
        HAL_GPIO_WritePin(GPIOA,GPIO_PIN_3,0);
        HAL_SPI_Transmit_DMA(device->hspi, (uint8_t *)&regVal, 4);
        HAL_GPIO_WritePin(GPIOA,GPIO_PIN_3,1);
       
    if(status != HAL_OK)    return (int)status;
    return 0;
}

使用特权

评论回复
17
怎么总是重复啊|  楼主 | 2023-1-26 16:21 | 只看该作者
DMA发送代码如下所示(部分省略):
HAL_StatusTypeDef HAL_SPI_Transmit_DMA(SPI_HandleTypeDef *hspi, uint8_t *pData, uint16_t Size)
{
......
  /* 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;
/* Enable the Tx DMA Stream/Channel */
  if (HAL_OK != HAL_DMA_Start_IT(hspi->hdmatx, (uint32_t)hspi->pTxBuffPtr, (uint32_t)&hspi->Instance->DR,
                                 hspi->TxXferCount))
  {
    /* Update SPI error code */
    SET_BIT(hspi->ErrorCode, HAL_SPI_ERROR_DMA);
    errorcode = HAL_ERROR;

    hspi->State = HAL_SPI_STATE_READY;
    goto error;
  }
......
}

使用特权

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

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

24

主题

197

帖子

1

粉丝