STM32F407HAL库-8.串口数据收发-DMA

串口数据收发(DMA)初始化步骤:

第一步:调用HAL_DMA_Init函数初始化DMA参数,函数传入结构体参数如下:

typedef struct __DMA_HandleTypeDef
{
  DMA_Stream_TypeDef   *Instance;  // DMA数据流地址

  DMA_InitTypeDef      Init;       // DMA通讯的参数

  HAL_LockTypeDef      Lock;       // DMA锁定对象

  __IO HAL_DMA_StateTypeDef  State; // DMA传输的状态

  void                 *Parent;    // 父对象的状态

  void   (* XferCpltCallback)( struct __DMA_HandleTypeDef * hdma); // DMA转换完成回调函数

  void   (* XferHalfCpltCallback)( struct __DMA_HandleTypeDef * hdma); // DMA转换完成一半回调函数

  void   (* XferM1CpltCallback)( struct __DMA_HandleTypeDef * hdma); // DMA内存1转换完成回调函数
  
  void   (* XferM1HalfCpltCallback)( struct __DMA_HandleTypeDef * hdma); // DMA内存1转换完成一半回调函数
  
  void   (* XferErrorCallback)( struct __DMA_HandleTypeDef * hdma); // DMA转换错误回调
  
  void   (* XferAbortCallback)( struct __DMA_HandleTypeDef * hdma); // DMA转换中止回调

  __IO uint32_t  ErrorCode; // DMA错误回调代码
  
  uint32_t        StreamBaseAddress; // DMA数据流地址

  uint32_t        StreamIndex; // DMA数据流索引
 
}DMA_HandleTypeDef;

第二步:初始化串口参数,将DMA初始化结构体参数传入串口结构体中,详情请参考下面代码。

第三步:调用数据收发函数。

  • DMA接收数据函数
HAL_StatusTypeDef HAL_UART_Receive_DMA(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size);

函数中主要实现以下功能:

  1. 初始化串口结构体参数(数据缓冲区地址、数据长度等)。
  2. 初始化DMA结构体参数(传输完成回调函数等)。
  3. 调用HAL_DMA_Start_IT函数,配置DMA源地址、目标地址以及使能中断。
  4. 使能串口DMA接收(UART->CR3,DMAR)。

函数如下:

HAL_StatusTypeDef HAL_UART_Receive_DMA(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size)
{  
  uint32_t *tmp;
  
  /* 检查Rx进程是否已经在进行中 */
  if(huart->RxState == HAL_UART_STATE_READY) 
  {
    if((pData == NULL ) || (Size == 0)) 
    {
      return HAL_ERROR;
    }
    
    /* 进程锁 */
    __HAL_LOCK(huart);
    
	/* 初始化接收数据缓冲区地址以及数据长度 */
    huart->pRxBuffPtr = pData;
    huart->RxXferSize = Size;
    
	/* 初始化DMA错误代码状态和Rx进程工作状态*/
    huart->ErrorCode = HAL_UART_ERROR_NONE;
    huart->RxState = HAL_UART_STATE_BUSY_RX;
        
    /* 设置串口DMA转换完成中断回调 */
    huart->hdmarx->XferCpltCallback = UART_DMAReceiveCplt;
    
    /*设置UART DMA半传输完全回调 */
    huart->hdmarx->XferHalfCpltCallback = UART_DMARxHalfCplt;
    
    /* 设置DMA错误回调 */
    huart->hdmarx->XferErrorCallback = UART_DMAError;
    
    /* 设置DMA中止回调 */
    huart->hdmarx->XferAbortCallback = NULL;

    /* 使能DMA  设置传输起始地址、目标地址 */
    tmp = (uint32_t*)&pData;
    HAL_DMA_Start_IT(huart->hdmarx, (uint32_t)&huart->Instance->DR, *(uint32_t*)tmp, Size);

    /* 在启用DMA Rx请求之前清除溢出标志:对于第二次传输可能是强制性的 */
    __HAL_UART_CLEAR_OREFLAG(huart);

    /* 进程解锁 */
    __HAL_UNLOCK(huart);

    /* 使能串口校验错误中断 */
    SET_BIT(huart->Instance->CR1, USART_CR1_PEIE);

    /* 使能串口错误中断:帧错误、噪声错误、溢出错误 */
    SET_BIT(huart->Instance->CR3, USART_CR3_EIE);
    
    /* 使能串口DMA接收  UART->CR3 Bit6 DMAR*/
    SET_BIT(huart->Instance->CR3, USART_CR3_DMAR);

    return HAL_OK;
  }
  else
  {
    return HAL_BUSY; 
  }
}
  • DMA发送数据函数
HAL_StatusTypeDef HAL_UART_Transmit_DMA(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size);

函数主要实现功能如下:

  1. 初始化串口结构体参数(数据缓冲区地址、数据长度等)。
  2. 初始化DMA结构体参数(传输完成回调函数等)。
  3. 调用HAL_DMA_Start_IT函数,配置DMA源地址、目标地址以及使能中断。
  4. 使能串口DMA发送(UART->CR3,DMAT)。

函数如下:

HAL_StatusTypeDef HAL_UART_Transmit_DMA(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size)
{
  uint32_t *tmp;
  
  /* 检查Tx进程是否已经在进行中 */
  if(huart->gState == HAL_UART_STATE_READY)
  {
    if((pData == NULL ) || (Size == 0))
    {
      return HAL_ERROR;
    }

    /* 进程锁 */
    __HAL_LOCK(huart);

	/* 配置发送缓冲区地址 数据个数*/
    huart->pTxBuffPtr = pData;
    huart->TxXferSize = Size;
    huart->TxXferCount = Size;

	/* 初始化DMA错误代码状态和Tx进程工作状态 */
    huart->ErrorCode = HAL_UART_ERROR_NONE;
    huart->gState = HAL_UART_STATE_BUSY_TX;

    /* 设置串口DMA发送数据完成中断回调 */
    huart->hdmatx->XferCpltCallback = UART_DMATransmitCplt;

    /* 设置串口DMA发送一半数据完成中断回调 */
    huart->hdmatx->XferHalfCpltCallback = UART_DMATxHalfCplt;

    /* 设置DMA错误回调函数 */
    huart->hdmatx->XferErrorCallback = UART_DMAError;

    /* 设置DMA中止回调函数 */
    huart->hdmatx->XferAbortCallback = NULL;

	/* 使能DMA  设置传输起始地址、目标地址 */
    tmp = (uint32_t*)&pData;
    HAL_DMA_Start_IT(huart->hdmatx, *(uint32_t*)tmp, (uint32_t)&huart->Instance->DR, Size);
    
    /* 清除发送完成标志位  SR  TC */
    __HAL_UART_CLEAR_FLAG(huart, UART_FLAG_TC);
    
    /* 进程解锁 */
    __HAL_UNLOCK(huart);
    
    /* 使能串口DMA发送  UART->CR3 Bit6 DMAT */
    SET_BIT(huart->Instance->CR3, USART_CR3_DMAT);
    
    return HAL_OK;
  }
  else
  {
    return HAL_BUSY;
  }
}
  • DMA开始函数

函数实现功能如下:

  1. 函数传入参数:DMA结构体变量、源地址、目的地址、数据长度。
  2. 函数中初始化DMA结构体参数:State状态、错误代码等。
  3. 调用DMA_SetConfig函数,配置目标地址、源地址、数据长度。
  4. 使能DMA。
HAL_StatusTypeDef HAL_DMA_Start(DMA_HandleTypeDef *hdma, uint32_t SrcAddress, uint32_t DstAddress, uint32_t DataLength)
{
  HAL_StatusTypeDef status = HAL_OK;
  
  /* 检查参数 */
  assert_param(IS_DMA_BUFFER_SIZE(DataLength));

  /* 进程锁 */
  __HAL_LOCK(hdma);

  if(HAL_DMA_STATE_READY == hdma->State)
  {
    /* 改变DMA外设状态 */
    hdma->State = HAL_DMA_STATE_BUSY;
    
    /* 初始化错误代码 */
    hdma->ErrorCode = HAL_DMA_ERROR_NONE;
    
    /* 配置DMA源地址  目标地址 数据长度*/
    DMA_SetConfig(hdma, SrcAddress, DstAddress, DataLength);

    /* 使能DMA */
    __HAL_DMA_ENABLE(hdma);
  }
  else
  {
    /* 进程解锁 */
    __HAL_UNLOCK(hdma);
    
    /* 返回错误状态 */
    status = HAL_BUSY;
  } 
  return status; 
}
  • DMA中止函数
HAL_StatusTypeDef HAL_DMA_Abort(DMA_HandleTypeDef *hdma);

函数主要功能实现:

  1. 中止DMA传输,失能所有转换中断。
  2. 在禁用DMA流之后,添加检查等待直到DMA流被有效禁用,如果在数据传输过程中禁用了Stream,则将传输当前数据,并且只有在完成此数据的传输后才能有效禁用Stream。
  3. 函数最后释放DMA状态。
HAL_StatusTypeDef HAL_DMA_Abort(DMA_HandleTypeDef *hdma)
{
  /*校验DMA地址和数据流*/
  DMA_Base_Registers *regs = (DMA_Base_Registers *)hdma->StreamBaseAddress;
  
  uint32_t tickstart = HAL_GetTick();
  
  if(hdma->State != HAL_DMA_STATE_BUSY)
  {
    hdma->ErrorCode = HAL_DMA_ERROR_NO_XFER;
    
    /* 设置进程锁 */
    __HAL_UNLOCK(hdma);
    
    return HAL_ERROR;
  }
  else
  {
    /* 失能所有转换中断 */
    hdma->Instance->CR  &= ~(DMA_IT_TC | DMA_IT_TE | DMA_IT_DME);
    hdma->Instance->FCR &= ~(DMA_IT_FE);
    
    if((hdma->XferHalfCpltCallback != NULL) || (hdma->XferM1HalfCpltCallback != NULL))
    {
      hdma->Instance->CR  &= ~(DMA_IT_HT);
    }
    
    /* 失能DMA */
    __HAL_DMA_DISABLE(hdma);
    
    /* 检查DMA是否是有效的使能 */
    while((hdma->Instance->CR & DMA_SxCR_EN) != RESET)
    {
      /* 检查时间是否溢出 */
      if((HAL_GetTick() - tickstart ) > HAL_TIMEOUT_DMA_ABORT)
      {
        /* 更新错误代码 */
        hdma->ErrorCode = HAL_DMA_ERROR_TIMEOUT;
        
        /* 进程解锁 */
        __HAL_UNLOCK(hdma);
        
        /* 改变DMA状态 */
        hdma->State = HAL_DMA_STATE_TIMEOUT;
        
        return HAL_TIMEOUT;
      }
    }
    
    /* 清除寄存器中所有正确偏移量的中断标志 */
    regs->IFCR = 0x3FU << hdma->StreamIndex;
    
    /* 进程解锁 */
    __HAL_UNLOCK(hdma);
    
    /* 改变DMA状态*/
    hdma->State = HAL_DMA_STATE_READY;
  }
  return HAL_OK;
}
  • DMA使用到的宏定义:DMA使能
#define __HAL_DMA_ENABLE(__HANDLE__)      ((__HANDLE__)->Instance->CR |=  DMA_SxCR_EN)
  • DMA失能
#define __HAL_DMA_DISABLE(__HANDLE__)     ((__HANDLE__)->Instance->CR &=  ~DMA_SxCR_EN)
  • 获取标志位
#define __HAL_DMA_GET_FLAG(__HANDLE__, __FLAG__)\
(((uint32_t)((__HANDLE__)->Instance) > (uint32_t)DMA2_Stream3)? (DMA2->HISR & (__FLAG__)) :\
 ((uint32_t)((__HANDLE__)->Instance) > (uint32_t)DMA1_Stream7)? (DMA2->LISR & (__FLAG__)) :\
 ((uint32_t)((__HANDLE__)->Instance) > (uint32_t)DMA1_Stream3)? (DMA1->HISR & (__FLAG__)) : (DMA1->LISR & (__FLAG__)))

串口DMA数据收发程序如下:

DMA_HandleTypeDef UART1RxDMA_Handler; // 串口接收DMA句柄
DMA_HandleTypeDef UART1TxDMA_Handler; // 串口发送DMA句柄
UART_HandleTypeDef UART1_Handler; // UART 句柄

// 串口DMA初始化
void UART_DMA_Init(void)
{
	__HAL_RCC_DMA2_CLK_ENABLE(); // 使能DMA2时钟
	
	// 接收DMA配置
	UART1RxDMA_Handler.Instance = DMA2_Stream5; // 数据流选择
	UART1RxDMA_Handler.Init.Channel = DMA_CHANNEL_4; // 通道选择
	UART1RxDMA_Handler.Init.Direction = DMA_PERIPH_TO_MEMORY; // 外设到存储器
	UART1RxDMA_Handler.Init.FIFOMode = DMA_FIFOMODE_DISABLE; // FIFO不使能
	UART1RxDMA_Handler.Init.FIFOThreshold = DMA_FIFO_THRESHOLD_FULL; // FIFO阈值
	UART1RxDMA_Handler.Init.MemBurst = DMA_MBURST_SINGLE; // 内存突发传输配置
	UART1RxDMA_Handler.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE; // 存储器数据长度
	UART1RxDMA_Handler.Init.MemInc = DMA_MINC_ENABLE; // 内存寄存器地址是否自增
	UART1RxDMA_Handler.Init.Mode = DMA_CIRCULAR; // 循环模式
	UART1RxDMA_Handler.Init.PeriphBurst = DMA_PBURST_SINGLE; // 外设突发传输配置
	UART1RxDMA_Handler.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE; // 外设数据长度
	UART1RxDMA_Handler.Init.PeriphInc = DMA_PINC_DISABLE; // 外设地址非自增
	UART1RxDMA_Handler.Init.Priority = DMA_PRIORITY_MEDIUM; // 中等优先级
	HAL_DMA_Init(&UART1RxDMA_Handler);
		
	// 发送DMA配置
	UART1TxDMA_Handler.Instance = DMA2_Stream7; // 数据流选择
	UART1TxDMA_Handler.Init.Channel = DMA_CHANNEL_4; // 通道选择
	UART1TxDMA_Handler.Init.Direction = DMA_MEMORY_TO_PERIPH; // 外设到存储器
	UART1TxDMA_Handler.Init.FIFOMode = DMA_FIFOMODE_DISABLE; // FIFO不使能
	UART1TxDMA_Handler.Init.FIFOThreshold = DMA_FIFO_THRESHOLD_FULL; // FIFO阈值
	UART1TxDMA_Handler.Init.MemBurst = DMA_MBURST_SINGLE; // 内存突发传输配置
	UART1TxDMA_Handler.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE; // 存储器数据长度
	UART1TxDMA_Handler.Init.MemInc = DMA_MINC_ENABLE; // 内存寄存器地址是否自增
	UART1TxDMA_Handler.Init.Mode = DMA_NORMAL; // 循环模式
	UART1TxDMA_Handler.Init.PeriphBurst = DMA_PBURST_SINGLE; // 外设突发传输配置
	UART1TxDMA_Handler.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE; // 外设数据长度
	UART1TxDMA_Handler.Init.PeriphInc = DMA_PINC_DISABLE; // 外设地址非自增
	UART1TxDMA_Handler.Init.Priority = DMA_PRIORITY_MEDIUM; // 中等优先级
	HAL_DMA_Init(&UART1TxDMA_Handler);	
}

// 初始化串口函数
void UART_Init(void)
{
	UART1_Handler.Instance = USART1; // 串口1
	UART1_Handler.Init.BaudRate = 115200; // 波特率
	UART1_Handler.Init.HwFlowCtl = UART_HWCONTROL_NONE; // 无硬件流控
	UART1_Handler.Init.Mode = UART_MODE_TX_RX; // 收发模式
	UART1_Handler.Init.Parity = UART_PARITY_NONE; // 无奇偶校验
	UART1_Handler.Init.StopBits = UART_STOPBITS_1; // 一个停止位
	UART1_Handler.Init.WordLength = UART_WORDLENGTH_8B; // 字长为8位格式
	UART1_Handler.hdmarx = &UART1RxDMA_Handler; // 传入接收DMA句柄
	UART1_Handler.hdmatx = &UART1TxDMA_Handler; // 传入发送DMA句柄
	HAL_UART_Init(&UART1_Handler); // 初始化串口
}

// 初始化低级硬件
void HAL_UART_MspInit(UART_HandleTypeDef *huart)
{
	if(huart->Instance == USART1)
	{
		GPIO_InitTypeDef GPIO_Initure;
		
		__HAL_RCC_GPIOA_CLK_ENABLE(); // 使能GPIO引脚
		__HAL_RCC_USART1_CLK_ENABLE(); // 使能串口
		
		GPIO_Initure.Mode = GPIO_MODE_AF_PP; // 复用推挽模式
		GPIO_Initure.Pin = GPIO_PIN_9|GPIO_PIN_10; // PA9、PA10
		GPIO_Initure.Pull = GPIO_PULLUP;  //上拉
		GPIO_Initure.Speed = GPIO_SPEED_FAST; //高速
		GPIO_Initure.Alternate = GPIO_AF7_USART1; // 复用为 USART1
		HAL_GPIO_Init(GPIOA, &GPIO_Initure); // 初始化 PA9、PA10
	}
}

// 串口DMA接收数据
void UART_DMA_Receive(uint8_t *pData, uint16_t Size)
{
	HAL_UART_Receive_DMA(&UART1_Handler, pData, Size);	
}

// 串口DMA发送数据
void UART_DMA_Transmit(uint8_t *pData, uint16_t Size)
{
	HAL_UART_Transmit_DMA(&UART1_Handler, pData, Size);
}

uint8_t Rx_buffer[RXBUFFERSIZE]; // 串口接收数据缓冲区

// 获取状态标志位
void Get_Flag(void)
{
	if(__HAL_DMA_GET_FLAG(&UART1RxDMA_Handler, DMA_FLAG_TCIF1_5)) // 如果数据接收完成
	{
		__HAL_DMA_ENABLE(&UART1TxDMA_Handler);
		__HAL_DMA_CLEAR_FLAG(&UART1RxDMA_Handler,DMA_FLAG_TCIF1_5); // 清除状态标志位
	}
	if(__HAL_DMA_GET_FLAG(&UART1TxDMA_Handler, DMA_FLAG_TCIF3_7)) // 如果数据发送完成
	{
		__HAL_DMA_DISABLE(&UART1TxDMA_Handler);
		__HAL_DMA_CLEAR_FLAG(&UART1TxDMA_Handler,DMA_FLAG_TCIF3_7); // 清除状态标志位
	}
}

主函数程序如下:

extern uint8_t Rx_buffer[RXBUFFERSIZE];

int main(void)
{
	CLOCLK_Init(); // 配置系统时钟为168M
	UART_DMA_Init(); // 串口DMA初始化
	UART_Init(); // 串口初始化
	
	UART_DMA_Receive(Rx_buffer, RXBUFFERSIZE); // 使用DMA接收数据
	UART_DMA_Transmit(Rx_buffer, RXBUFFERSIZE);	// 使用DMA发送数据
	
	while(1)
	{
		Get_Flag(); // 获取状态标志位,控制DMA
	}
}

 

你可能感兴趣的:(STM32F4外设开发)