DMA+SPI

DMA+SPI通信

回顾DMA的使用:

发送数据的流程:

标准库中如果要使用DMA发送数据,则需要做如下几件事

  1. 在数据发送缓冲区内放好要发送的数据,说明:此数据缓冲区的首地址必须要在DMA初始化的时候写入到DMA配置中去。

  2. 将数据缓冲区内要发送的数据字节数赋值给发送DMA通道,(SPI发送DMA和SPI接收DAM不是同一个DMA通道)

  3. 开启DMA,一旦开启,则DMA开始发送数据,说明一下:在KEIL调试好的时候,DMA和调试是不同步的,即不管主机状态,DMA总是发送数据。

  4. 等待发送完成标志位,即下面的中断服务函数中的第3点设置的标志位。或者根据自己的实际情况来定,是否要一直等待这个标志位,也可以通过状态机的方式来循环查询也可以。或者其他方式。

SPI的DMA接收:

接收数据的流程:

SPI的接收和发送DMA在初始化的时候就处于开启状态,只要用户给出开始指令,便可以同时开始接收和发送。在软件上无需做其他事情,只要在初始化配置的时候设置好配置就可以了。

判断数据数据接收完成:

​ 通常情况下判断接收完成是通过外设的空闲中断实现的,当SPI进行完一个周期的数据传输后,DMA可以根据中断类型判断读出SPI_DR中的数据。在Hal库中SPI的配套操作函数中指定了DMA接收数据的长度与SPI的十六位发送或者八位发送一致,所以也不需要额外设置发送或接收数据的长度

HAL_StatusTypeDef HAL_SPI_TransmitReceive_DMA(SPI_HandleTypeDef *hspi, uint8_t *pTxData, uint8_t *pRxData,
                                              uint16_t Size)
{
  uint32_t             tmp_mode;
  HAL_SPI_StateTypeDef tmp_state;
  HAL_StatusTypeDef errorcode = HAL_OK;

  /* Check rx & tx dma handles */
  assert_param(IS_SPI_DMA_HANDLE(hspi->hdmarx));
  assert_param(IS_SPI_DMA_HANDLE(hspi->hdmatx));

  /* Check Direction parameter */
  assert_param(IS_SPI_DIRECTION_2LINES(hspi->Init.Direction));

  /* Process locked */
  __HAL_LOCK(hspi);

  /* Init temporary variables */
  tmp_state           = hspi->State;
  tmp_mode            = hspi->Init.Mode;

  if (!((tmp_state == HAL_SPI_STATE_READY) ||
        ((tmp_mode == SPI_MODE_MASTER) && (hspi->Init.Direction == SPI_DIRECTION_2LINES) && (tmp_state == HAL_SPI_STATE_BUSY_RX))))
  {
    errorcode = HAL_BUSY;
    goto error;
  }

  if ((pTxData == NULL) || (pRxData == NULL) || (Size == 0U))
  {
    errorcode = HAL_ERROR;
    goto error;
  }

  /* Don't overwrite in case of HAL_SPI_STATE_BUSY_RX */
  if (hspi->State != HAL_SPI_STATE_BUSY_RX)
  {
    hspi->State = HAL_SPI_STATE_BUSY_TX_RX;
  }

  /* Set the transaction information */
  hspi->ErrorCode   = HAL_SPI_ERROR_NONE;
  hspi->pTxBuffPtr  = (uint8_t *)pTxData;
  hspi->TxXferSize  = Size;
  hspi->TxXferCount = Size;
  hspi->pRxBuffPtr  = (uint8_t *)pRxData;
  hspi->RxXferSize  = Size;
  hspi->RxXferCount = Size;

  /* Init field not used in handle to zero */
  hspi->RxISR       = NULL;
  hspi->TxISR       = NULL;

#if (USE_SPI_CRC != 0U)
  /* Reset CRC Calculation */
  if (hspi->Init.CRCCalculation == SPI_CRCCALCULATION_ENABLE)
  {
    SPI_RESET_CRC(hspi);
  }
#endif /* USE_SPI_CRC */

  /* Check if we are in Rx only or in Rx/Tx Mode and configure the DMA transfer complete callback */
  if (hspi->State == HAL_SPI_STATE_BUSY_RX)
  {
    /* Set the SPI Rx DMA Half transfer complete callback */
    hspi->hdmarx->XferHalfCpltCallback = SPI_DMAHalfReceiveCplt;
    hspi->hdmarx->XferCpltCallback     = SPI_DMAReceiveCplt;
  }
  else
  {
    /* Set the SPI Tx/Rx DMA Half transfer complete callback */
    hspi->hdmarx->XferHalfCpltCallback = SPI_DMAHalfTransmitReceiveCplt;
    hspi->hdmarx->XferCpltCallback     = SPI_DMATransmitReceiveCplt;
  }

  /* Set the DMA error callback */
  hspi->hdmarx->XferErrorCallback = SPI_DMAError;

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

  /* Enable the Rx DMA Stream/Channel  */
  if (HAL_OK != HAL_DMA_Start_IT(hspi->hdmarx, (uint32_t)&hspi->Instance->DR, (uint32_t)hspi->pRxBuffPtr,
                                 hspi->RxXferCount))
  {
    /* Update SPI error code */
    SET_BIT(hspi->ErrorCode, HAL_SPI_ERROR_DMA);
    errorcode = HAL_ERROR;

    hspi->State = HAL_SPI_STATE_READY;
    goto error;
  }

  /* Enable Rx DMA Request */
  SET_BIT(hspi->Instance->CR2, SPI_CR2_RXDMAEN);

  /* Set the SPI Tx DMA transfer complete callback as NULL because the communication closing
  is performed in DMA reception complete callback  */
  hspi->hdmatx->XferHalfCpltCallback = NULL;
  hspi->hdmatx->XferCpltCallback     = NULL;
  hspi->hdmatx->XferErrorCallback    = NULL;
  hspi->hdmatx->XferAbortCallback    = NULL;

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

  /* Check if the SPI is already enabled */
  if ((hspi->Instance->CR1 & SPI_CR1_SPE) != SPI_CR1_SPE)
  {
    /* Enable SPI peripheral */
    __HAL_SPI_ENABLE(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);

error :
  /* Process Unlocked */
  __HAL_UNLOCK(hspi);
  return errorcode;
}
  • 在HAL的操作中使用了hspi全局句柄指向了hdmarxhdmatx两个分支句柄,进入对DMA的操作。在确认参数无误后将所需数据读出。
     

 

SPI+DMA:

由于SPI的协议特性,主机需要同时进行接收和发送数据,所以配置DMA的时候,需要同时配置SPI的接收和发送DMA

SPI主机模式与从机模式的区别:

主从模式的区别在于谁主动提供时钟信号,因为时钟信号是通信的载体。通过设置时钟极性和时钟相位实现与不同外设的同步通信。

 

SPI的主要中断类型:
中断事件事件标志使能控制位
发送缓存区为空TXETXEIE
接收缓存区非空RXNERXNEIE
主模式故障MODFERRIE
溢出错误OVRERRIE
CRC错误CRCERRERRIE
TI帧格式错误FREERRIE

 

 

SPI的寄存器:

注:外设寄存器可支持半字或字的访问,每个SPI寄存器为16位。

● 字:32 位数据/指令。
● 半字:16 位数据/指令。
● 字节:8 位数据。
● 双字:64 位数据

SPI_CR: 分为两个共32位的控制寄存器。

负责设置传输模式、数据帧格式、波特率、CRC计算、时钟极性、相位等基础属性。

在这里插入图片描述

SPI_DR:是一个十六位的可读写寄存器。

负责发送或接收的数据为 8 位或 16 位。具体取决于数据帧格式选择位( SPI_CR1 寄存器中的 DFF )。必须在使能 SPI 前进行此项选择,以确保操作正确。对于 8 位数据帧,缓冲区为 8 位,只有寄存器的 LSB (SPI_DR[7:0]) 用于发送 / 接收。在接收模式下,寄存器的 MSB (SPI_DR[15:8]) 强制为 0 。对于 16 位数据帧,缓冲区为 16 位,整个寄存器 SPI_DR[15:0] 均用于发送 / 接收
在这里插入图片描述

SPI_SR:是一个十六位只读寄存器,高七位保留。

负责显示SPI的外设状态、错误标识和中断标志位。在这里插入图片描述

HAL_StatusTypeDef HAL_SPI_Transmit_DMA(SPI_HandleTypeDef *hspi, 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, uint8_t *pTxData, uint8_t *pRxData,
                                              uint16_t Size);

Cube中配置SPI+DMA的发送和接收两个通道,使用SPI的全双工模式,设置DMA为循环模式,使DMA接收处于循环接受状态,否则DMA只能接收一次。

生成的代码中,DMA的初始化被放在了HAL_SPI_MspInit()中,被HAL_SPI_Init()调用,不需要我们进行别的初始化操作,标志位的清除在中断服务函数中进行。

我们只需要在回调函数中调用如下函数,将自己声明的用来存放接收和发送数据数组(uint8_t)作为参数传入函数。然后就可以依照发送周期操作数据数组,实现简单的SPI通讯传输。

  • 4
    点赞
  • 53
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值