DMA+SPI通信
DMA+SPI
回顾DMA的使用:
发送数据的流程:
标准库中如果要使用DMA发送数据,则需要做如下几件事
-
在数据发送缓冲区内放好要发送的数据,说明:此数据缓冲区的首地址必须要在DMA初始化的时候写入到DMA配置中去。
-
将数据缓冲区内要发送的数据字节数赋值给发送DMA通道,(SPI发送DMA和SPI接收DAM不是同一个DMA通道)
-
开启DMA,一旦开启,则DMA开始发送数据,说明一下:在KEIL调试好的时候,DMA和调试是不同步的,即不管主机状态,DMA总是发送数据。
-
等待发送完成标志位,即下面的中断服务函数中的第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
全局句柄指向了hdmarx
和hdmatx
两个分支句柄,进入对DMA的操作。在确认参数无误后将所需数据读出。
SPI+DMA:
由于SPI的协议特性,主机需要同时进行接收和发送数据,所以配置DMA的时候,需要同时配置SPI的接收和发送DMA。
SPI主机模式与从机模式的区别:
主从模式的区别在于谁主动提供时钟信号,因为时钟信号是通信的载体。通过设置时钟极性和时钟相位实现与不同外设的同步通信。
SPI的主要中断类型:
中断事件 | 事件标志 | 使能控制位 |
---|---|---|
发送缓存区为空 | TXE | TXEIE |
接收缓存区非空 | RXNE | RXNEIE |
主模式故障 | MODF | ERRIE |
溢出错误 | OVR | ERRIE |
CRC错误 | CRCERR | ERRIE |
TI帧格式错误 | FRE | ERRIE |
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通讯传输。