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

相关推荐
cjy_Somnr16 小时前
keil5报错显示stm32的SWDIO未连接不能烧录
stm32·单片机·嵌入式硬件
Lay_鑫辰17 小时前
西门子诊断-状态和错误位(“轴”工艺对象 V1...3)
服务器·网络·单片机·嵌入式硬件·自动化
无垠的广袤19 小时前
【工业树莓派 CM0 NANO 单板计算机】本地部署 EMQX
linux·python·嵌入式硬件·物联网·树莓派·emqx·工业物联网
雲烟21 小时前
嵌入式设备EMC安规检测参考
网络·单片机·嵌入式硬件
泽虞21 小时前
《STM32单片机开发》p7
笔记·stm32·单片机·嵌入式硬件
田甲1 天前
【STM32】 数码管驱动
stm32·单片机·嵌入式硬件
up向上up1 天前
基于51单片机垃圾箱自动分类加料机快递物流分拣器系统设计
单片机·嵌入式硬件·51单片机
纳祥科技1 天前
Switch快充方案,内置GaN,集成了多个独立芯片
单片机
单片机日志1 天前
【单片机毕业设计】【mcugc-mcu826】基于单片机的智能风扇系统设计
stm32·单片机·嵌入式硬件·毕业设计·智能家居·课程设计·电子信息
松涛和鸣1 天前
从零开始理解 C 语言函数指针与回调机制
linux·c语言·开发语言·嵌入式硬件·排序算法