STM32F103 SPI(踩坑日记) 您所在的位置:网站首页 spi标准模式 STM32F103 SPI(踩坑日记)

STM32F103 SPI(踩坑日记)

2024-02-20 17:11| 来源: 网络整理| 查看: 265

SPI学习 前言1.SPI 协议1.1SPI的4种模式 2. STM32F103 硬件SPI2.1 标准库的发送函数2.2 HAL库发送函数2.2.1 这里有个小坑 3. SPI的连续传输和非连续传输4.SPI+DMA传输的坑

前言

第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,从设备使能信号,由主设备控制。 1.1SPI的4种模式

spi的4种模式是通过CPOL和CPHA设置0和1来决定的。排列组合一下一共4种: CPOL: SPI空闲时的时钟信号电平(1:高电平, 0:低电平) CPHA: SPI在时钟第几个边沿采样(1:第二个边沿开始, 0:第一个边沿开始) 在这里插入图片描述 在这里插入图片描述 那么在STM32中体现是在这个结构体中(有省略):

typedef struct { ...... uint16_t SPI_CPOL; /*!< Specifies the serial clock steady state. This parameter can be a value of @ref SPI_Clock_Polarity */ uint16_t SPI_CPHA; /*!< Specifies the clock active edge for the bit capture. ...... }SPI_InitTypeDef;

这边需要根据从站的datasheet来配置,比如下面的时序图: 在这里插入图片描述 SCLK默认为高电平,从sck的第二个边沿采集数据位,所以: CPOL=1; CPHA=1;

2. STM32F103 硬件SPI

STMf103的SPI->DR寄存器是个16位的,从参考手册上来看F1的芯片支持8位和16位的数据发送,通过SPI->CR1的DFF标志位来决定传输的数据是8位还是16位。这个也是需要参考从站的datasheet来决定的。 主模式波特率预分频系数(最大为fPCLK/2) ,所以SPI可以最高分到一个36MHz的频率。关于SPI的发送函数,标准库函数版本和HAL库提供了两种解决办法:

2.1 标准库的发送函数 /** * @brief 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; }

assert_param这个断言可以不管,其实就是把数据写到DR寄存器,什么判断都没有。所以一般我们在使用的时候超时跳出和检测发送是否成功,如下:

while (SPI_I2S_GetFlagStatus(SPI2, SPI_I2S_FLAG_TXE) == RESET) //检查指定的SPI标志位设置与否:发送缓存空标志位 { retry++; if(retry>200)return 0;//重试200次 }

但是执行retry++和SPI_I2S_GetFlagStatus(SPI2, SPI_I2S_FLAG_TXE) == RESET需要占用CPU时间,这会导致SPI在发送时出现非连续传输详见3.节。

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; } } } }

核心部分:

if (__HAL_SPI_GET_FLAG(hspi, SPI_FLAG_TXE)) *((__IO uint8_t *)&hspi->Instance->DR) = (*hspi->pTxBuffPtr);

也是检查发送完成标志位和把数据写到DR寄存器中。但是HAL库写的比较好的是支持写数据长度了。一般我们不会只写8位,一般一条指令都有32位或者更长,HAL库的封装我只需要提供一个首地址,把Size设置成对应的长度就可以了。点击跳转

2.2.1 这里有个小坑

数据在单片机中存储是按小端格式存储的,但是往往数据发送是按大端格式发送的。 比如想发送01 02 03 04 但是我们很容易存成:

uint32_t data =0x01020304

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

3. 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模式。

4.SPI+DMA传输的坑

根据自己SPI通道选择DMA配置,使用STM32Cube进行配置 这是我写的一个发送函数,但是抓波形的时候发现发送的数据不对,但是不使用DMA发送就正常了。后来研究了很久才发现问题。

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 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; } ...... }

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



【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有