stm32 hal库 SPI使用(二)硬件SPI的HAL库函数调用

使用硬件SPI1,开启DMA,软件NSS。

1.使用硬件spi后,spi.c文件里会自动生成SPI_HandleTypeDef hspi1句柄,并且在main.c中自动使用MX_SPI1_Init()函数对hsp1句柄赋值和SPI初始化

cs 复制代码
void MX_SPI1_Init(void)
{
  hspi1.Instance = SPI1;//spi1
  hspi1.Init.Mode = SPI_MODE_MASTER;//主机模式
  hspi1.Init.Direction = SPI_DIRECTION_2LINES;//双线双向
  hspi1.Init.DataSize = SPI_DATASIZE_8BIT;//8位数据传输
  hspi1.Init.CLKPolarity = SPI_POLARITY_HIGH;//时钟极性
  hspi1.Init.CLKPhase = SPI_PHASE_1EDGE;//始终相位
  hspi1.Init.NSS = SPI_NSS_HARD_OUTPUT;//硬件NSS(输出)
  hspi1.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_8;//prescaler
  hspi1.Init.FirstBit = SPI_FIRSTBIT_MSB;//MSB
  hspi1.Init.TIMode = SPI_TIMODE_DISABLE;//使用摩托罗拉帧格式
  hspi1.Init.CRCCalculation = SPI_CRCCALCULATION_ENABLE;//开启CRC校验
  hspi1.Init.CRCPolynomial = 10;//CRC多项式
  if (HAL_SPI_Init(&hspi1) != HAL_OK)//调用HAL_StatusTypeDef HAL_SPI_Init(SPI_HandleTypeDef \*hspi)函数进行SPI初始化,在这个函数里主要调用了HAL_SPI_MspInit这个虚函数(虽然是虚函数但是内容已经在spi.c里写好了^^)
  {
    Error_Handler();
  }
}

Info

补充解释一下 hspi1.Init.Direction = SPI_DIRECTION_2LINES;//双线双向 的意思。

双线指的是MOSI和MISO两条线 一般默认是双线双向传输

也可以通过软件改成双线单向或者单线。

双线单向就是同一时间两条线都向同一个设备发数据 所以传输速率能加快一倍^^

单线就是,单线(禁用一条线),虽然速率慢但是硬件上能省下一个管脚。

这部分在上一篇原理部分寄存器位那里具体解释过如何设置(通过控制寄存器标志位)。

2.在正式发送和接受之前先要进行NSS的接收和配置。

3.先介绍一下主要用到的hal库函数:

cs 复制代码
HAL_StatusTypeDef HAL_SPI_Transmit(SPI_HandleTypeDef *hspi, const uint8_t *pData, uint16_t Size, uint32_t Timeout);//阻塞式发送
HAL_StatusTypeDef HAL_SPI_Receive(SPI_HandleTypeDef *hspi, uint8_t *pData, uint16_t Size, uint32_t Timeout);//阻塞式接收
HAL_StatusTypeDef HAL_SPI_TransmitReceive(SPI_HandleTypeDef *hspi, const uint8_t *pTxData, uint8_t *pRxData, uint16_t Size, uint32_t Timeout);//阻塞式发送+接收(因为spi的发送和接收是同时进行的^^)


HAL_StatusTypeDef HAL_SPI_Transmit_IT(SPI_HandleTypeDef *hspi, const uint8_t *pData, uint16_t Size);
HAL_StatusTypeDef HAL_SPI_Receive_IT(SPI_HandleTypeDef *hspi, uint8_t *pData, uint16_t Size);
HAL_StatusTypeDef HAL_SPI_TransmitReceive_IT(SPI_HandleTypeDef *hspi, const uint8_t *pTxData, uint8_t *pRxData,uint16_t Size);//中断式发送和接收


HAL_StatusTypeDef HAL_SPI_Transmit_DMA(SPI_HandleTypeDef *hspi, const uint8_t *pData, uint16_t Size);
HAL_StatusTypeDef HAL_SPI_Receive_DMA(SPI_HandleTypeDef *hspi, uint8_t *pData, uint16_t Size);
HAL_StatusTypeDef HAL_SPI_TransmitReceive_DMA(SPI_HandleTypeDef *hspi, const uint8_t *pTxData, uint8_t *pRxData,uint16_t Size);//SPI的dma发送接收

如果要用中断式发送/接收需要配套加上中断接收回调

cs 复制代码
void HAL_SPI_TxRxCpltCallback(SPI_HandleTypeDef *hspi) {
    if (hspi == &hspi1) {
        // 处理接收到的数据
    }
}

阻塞式中断式和DMA任选一种使用即可,因为开了DMA所以用DMA 的接收和发送^^

先看下hal库里提供的发送函数:

cs 复制代码
/**
  * @brief  Transmit an amount of data in non-blocking mode with DMA.
  * @param  hspi pointer to a SPI_HandleTypeDef structure that contains
  *               the configuration information for SPI module.
  * @param  pData pointer to data buffer
  * @param  Size amount of data to be sent
  * @retval HAL status
  */
HAL_StatusTypeDef HAL_SPI_Transmit_DMA(SPI_HandleTypeDef *hspi, const uint8_t *pData, uint16_t Size)
{

  /* Check tx dma handle */
  assert_param(IS_SPI_DMA_HANDLE(hspi->hdmatx));//dma发送句柄验证

  /* Check Direction parameter */
  assert_param(IS_SPI_DIRECTION_2LINES_OR_1LINE(hspi->Init.Direction));//spi方向验证------根据设置这里是双线双向

  if (hspi->State != HAL_SPI_STATE_READY)
  {
    return HAL_BUSY;
  }

  if ((pData == NULL) || (Size == 0U))
  {
    return HAL_ERROR;
  }

  /* Process Locked */
  __HAL_LOCK(hspi);//加锁  防止多线程或中断环境下的资源竞争

  /* Set the transaction information */
  hspi->State       = HAL_SPI_STATE_BUSY_TX;//更新spi状态并且存数据
  hspi->ErrorCode   = HAL_SPI_ERROR_NONE;
  hspi->pTxBuffPtr  = (const uint8_t *)pData;
  hspi->TxXferSize  = Size;
  hspi->TxXferCount = Size;

  /* Init field not used in handle to zero */
  //接收相关字段(pRxBuffPtr, RxXferSize)置零,因本函数仅处理发送。
  hspi->pRxBuffPtr  = (uint8_t *)NULL;
  hspi->TxISR       = NULL;
  hspi->RxISR       = NULL;
  hspi->RxXferSize  = 0U;
  hspi->RxXferCount = 0U;

  /* Configure communication direction : 1Line */
  //如果是单线
  if (hspi->Init.Direction == SPI_DIRECTION_1LINE)
  {
    /* Disable SPI Peripheral before set 1Line direction (BIDIOE bit) */
    __HAL_SPI_DISABLE(hspi);
    SPI_1LINE_TX(hspi);
  }

#if (USE_SPI_CRC != 0U)
  /* Reset CRC Calculation */
  //如果启用CRC
  if (hspi->Init.CRCCalculation == SPI_CRCCALCULATION_ENABLE)
  {
    SPI_RESET_CRC(hspi);//复位CRC单元
  }
#endif /* USE_SPI_CRC */

  /* Set the SPI TxDMA Half transfer complete callback */
  //半传输完成回调
  hspi->hdmatx->XferHalfCpltCallback = SPI_DMAHalfTransmitCplt;

  /* Set the SPI TxDMA transfer complete callback */
  //传输完成回调
  hspi->hdmatx->XferCpltCallback = SPI_DMATransmitCplt;

  /* Set the DMA error callback */
  //错误回调
  hspi->hdmatx->XferErrorCallback = SPI_DMAError;

  /* Set the DMA AbortCpltCallback */
  hspi->hdmatx->XferAbortCallback = NULL;

  /* Enable the Tx DMA Stream/Channel *///使能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 */
    //若DMA启动失败,设置错误码HAL_SPI_ERROR_DMA,解锁资源并返回 HAL_ERROR。
    SET_BIT(hspi->ErrorCode, HAL_SPI_ERROR_DMA);
    /* Process Unlocked */
    __HAL_UNLOCK(hspi);
    return HAL_ERROR;
  }

  /* Check if the SPI is already enabled */
  //若SPI未启用(SPI_CR1_SPE`位未设置),调用 __HAL_SPI_ENABLE 启动SPI外设。
  if ((hspi->Instance->CR1 & SPI_CR1_SPE) != SPI_CR1_SPE)
  {
    /* Enable SPI peripheral */
    __HAL_SPI_ENABLE(hspi);
  }

  /* Process Unlocked */
  __HAL_UNLOCK(hspi);

  /* Enable the SPI Error Interrupt Bit */
  __HAL_SPI_ENABLE_IT(hspi, (SPI_IT_ERR));

  /* Enable Tx DMA Request */
  SET_BIT(hspi->Instance->CR2, SPI_CR2_TXDMAEN);

  return HAL_OK;
}

用这个的话需要补上:

cs 复制代码
void HAL_SPI_TxCpltCallback(SPI_HandleTypeDef *hspi) {
    if (hspi == &hspi1) {
        // 发送完成处理
    }
}

接收差不多同理。

虽然有把接收和发送塞在一起的函数但还是建议发送和接收拆开,一个是方便看懂现在在干嘛,一个是有些复杂外设需要更灵活的配置。

但是既然只是个样例所以就把用接收发送一起的即可:

(这个函数要自己写)

cs 复制代码
void SPI1_TransmitReceive_DMA_With_CS(uint8_t *txBuf, uint8_t *rxBuf, uint16_t len)
{
    // Step 1: NSS 拉低
    HAL_GPIO_WritePin(OLED_CS_GPIO_Port, OLED_CS_Pin, GPIO_PIN_RESET);

    // Step 2: SSI = 0 表示选中从机
    hspi1.Instance->CR1 &= ~SPI_CR1_SSI;

    // Step 3: 启动全双工 DMA
    HAL_SPI_TransmitReceive_DMA(&hspi1, txBuf, rxBuf, len);
}

4.进行BSY==0的判断 通过(即确认总线空闲)之后才能升高NSS(其实这一步一般来说没什么必要 可加可不加的)

5.在(自己写的)DMA传输完成回调中恢复NSS和SSI:

cs 复制代码
void HAL_SPI_TxRxCpltCallback(SPI_HandleTypeDef *hspi)//这个函数是DMA传输完成 只能保证DMA数据在数据寄存器和内存中搬运完毕  不能保证总线上的数据传输完
{
    if (hspi->Instance == SPI1)

    {

        // Step 1: 等待 SPI 硬件传输真正完成(BSY 清零)
        while (__HAL_SPI_GET_FLAG(hspi, SPI_FLAG_BSY)) {}

        // Step 2: NSS 拉高,表示通信结束
        HAL_GPIO_WritePin(OLED_CS_GPIO_Port, OLED_CS_Pin, GPIO_PIN_SET);

        // Step 3: 设置 SSI = 1,表示 SPI 进入空闲状态
        hspi->Instance->CR1 |= SPI_CR1_SSI;
	}
}

下一节:具体例程之SPI读取FLASH

相关推荐
憧憬一下4 小时前
stm32之TIM定时中断详解
stm32·单片机·嵌入式·定时器
学习噢学个屁6 小时前
基于51单片机的红外人体感应报警器
c语言·单片机·嵌入式硬件·51单片机
技术干货贩卖机8 小时前
0基础 | STM32 | STM32F103C8T6开发板 | 项目开发
stm32·单片机·嵌入式硬件·源代码·项目开发·0基础
Leon_George9 小时前
GPIO引脚的上拉下拉以及转换速度到底怎么选
单片机·嵌入式硬件·引脚配置·上拉下拉·引脚速度
2401_888859719 小时前
STM32 USART串口
stm32·嵌入式硬件
zhugedz10 小时前
开关电源原理
单片机·嵌入式硬件
河湾边的一亩三分地10 小时前
STM32 PulseSensor心跳传感器驱动代码
stm32·单片机·嵌入式硬件
学生小羊11 小时前
[C++] 小游戏 决战苍穹
c++·stm32·单片机
mftang11 小时前
zephyr OS架构下构建Nordic MCU boot
单片机·嵌入式硬件