STM32CubeMx使用STM32F4系列芯片实现串口DMA接收

本期目标

  • 知晓DMA在系统框架下如何与串口配合

  • 熟悉STM32CubeMx的相关配置及其意义

  • 学习使用DMA将串口接受到的数据存入SRAM中

本文会通过底层寄存器原理配合代码讲解 DMA 串口接收全流程,小白也能看懂!

DMA与串口配合原理(不想了解可跳到STM32CubeMx配置)

串口接受数据流程

当通信的设备i传来数据时 , RX引脚会接受到数据 , 然后数据被寄存器传输到Data register 寄存器,等待CPU或者DMA将数据取走

CPU会在这里一直查询 或者是 一个中断 去DR寄存器里将数据取走 ,但是这样是非常占用CPU时间的

如果使用DMA的话 , CPU就不用去管DR寄存器里面的值 , 只要串口传来了数据 , DMA就会把DR寄存器里面的值一个字节一个字节的取走

那么DMA如何知道DR寄存器里有没有值?

DMA_Request

串口在产生数据以后 , 会发送一个DMA_Request的请求

接收到这个请求后DMA就会寻址找到DR寄存器, 然后搬走数据

DMAR寄存器

当这个位被软件置位的时候

当串口来数据了,就会发送DMA request

DM就会去DR寄存器将数据给拿走

如果UART挂载在了APB2总线上

那么DMA就可以不走里面直接从外面拿到数据再存入SRAM里

这样CPU就可以不被搬运数据的活给"困扰" , 解放更多的生产力去干别的事情, 比如说访问其他外设 , 也可以访问SRAM

STM32CubeMx配置

步骤一

设置时钟

步骤二

配置 串口

使能中断

设置Request

这样USART接受到数据的时侯, 就会向DMA发送请求 , DMA就会过来取走数据

因为操纵的是一位寄存器, 所以不需要勾上

但是数组地址需要递增所以Memroy需要勾上

​​​​​​​ ​​​​​​​ ​​​​​​​ ​​​​​​​ ​​​​​​​ ​​​​​ ​​​​​​​

Fifo的作用是缓存数据,假如DMA在访问Memroy的时候 , CPU正在占用Memory , 导致DMA的数据无法正常搬运而堵塞, 就可能导致数据丢失 , 而Fifo的作用就是将这一部分被堵塞的数据缓存下来,等到CPU释放了Memory , 再把数据取出来正常搬运

具体可以参考这篇文章

认识芯片架构图和总线矩阵以及FIFO的作用-CSDN博客

步骤三

点击生成代码

Keil部分

cpp 复制代码
/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
static void MX_GPIO_Init(void);
static void MX_DMA_Init(void);
static void MX_USART1_UART_Init(void);
/* USER CODE BEGIN PFP */

此处完成了初始化

cpp 复制代码
static void MX_DMA_Init(void)
{

  /* DMA controller clock enable */
  __HAL_RCC_DMA2_CLK_ENABLE();

  /* DMA interrupt init */
  /* DMA2_Stream2_IRQn interrupt configuration */
  HAL_NVIC_SetPriority(DMA2_Stream2_IRQn, 0, 0);
  HAL_NVIC_EnableIRQ(DMA2_Stream2_IRQn);

}

此处完成了DMA的中断

打开Functions

想要实现DMA串口接受 ,可以在这里找到对应的函数

cpp 复制代码
HAL_StatusTypeDef HAL_UART_Receive_DMA(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size)

将其复制

cpp 复制代码
uint8_t receive_data[20] = {0x00} ;
cpp 复制代码
  HAL_StatusTypeDef ret = 0 ;
  HAL_UART_Receive_DMA(&huart1, receive_data, 20);

此时就已经可以开始接受了

调试

添加receive_data到观察窗口观察数据

也可以将其复制到Memory窗口观察

现在打开串口助手

编辑信息准备发送

接受到了数据

但是这里可以看到在反复发送后数据不会继续递增接着存储数据

这就是前面提到的设置了

因为CubeMx设置的是Normal模式

所以到了我们设置的20个字节存满后

地址不会继续递增

那么如果想实现一直循环接受的目的, 就要再次对Cubemx进行设置

也可以将函数搬到while循环里

这样会一直重启DMA的接收

就可以达到一直接收的效果

但是今天的目标不仅经是知道API接口怎么使用,还要知晓其中原理

函数解析

点击函数按下F12跳转函数定义位置

cpp 复制代码
HAL_StatusTypeDef HAL_UART_Receive_DMA(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size)
{
  /* Check that a Rx process is not already ongoing */
  if (huart->RxState == HAL_UART_STATE_READY)
  {
    if ((pData == NULL) || (Size == 0U))
    {
      return HAL_ERROR;
    }

    /* Process Locked */
    __HAL_LOCK(huart);

    /* Set Reception type to Standard reception */
    huart->ReceptionType = HAL_UART_RECEPTION_STANDARD;

    return (UART_Start_Receive_DMA(huart, pData, Size));
  }
  else
  {
    return HAL_BUSY;
  }
}

上一篇文章讲过DMA不论跟谁配合 , 结束后都会扔出三个回调出来

  • 半满 回调

  • 全满 回调

  • DMA erro 回调

​​​​​​​

相关概念不清楚可以参考下面文章

(新手向详解)通过STM32CubeMx使用STM32F411的DMA发送数据翻转GPIO-CSDN博客

通过STM32CubeMx使用STM32F4系列芯片的DMA中断发送数据翻转GPIO-CSDN博客

你需要向DMA注册回调,让系统知道你具体调用的是哪个回调

但是现在运行这个函数好像什么都办好了

cpp 复制代码
HAL_UART_Receive_DMA(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size)

本质在下面这行代码

cpp 复制代码
   return (UART_Start_Receive_DMA(huart, pData, Size));
cpp 复制代码
 uint32_t *tmp;

  huart->pRxBuffPtr = pData;
  huart->RxXferSize = Size;

  huart->ErrorCode = HAL_UART_ERROR_NONE;
  huart->RxState = HAL_UART_STATE_BUSY_RX;

  /* Set the UART DMA transfer complete callback */
  huart->hdmarx->XferCpltCallback = UART_DMAReceiveCplt;

  /* Set the UART DMA Half transfer complete callback */
  huart->hdmarx->XferHalfCpltCallback = UART_DMARxHalfCplt;

  /* Set the DMA error callback */
  huart->hdmarx->XferErrorCallback = UART_DMAError;

  /* Set the DMA abort callback */
  huart->hdmarx->XferAbortCallback = NULL;

  /* Enable the DMA stream */
  tmp = (uint32_t *)&pData;
  HAL_DMA_Start_IT(huart->hdmarx, (uint32_t)&huart->Instance->DR, *(uint32_t *)tmp, Size);

  /* Clear the Overrun flag just before enabling the DMA Rx request: can be mandatory for the second transfer */
  __HAL_UART_CLEAR_OREFLAG(huart);

  /* Process Unlocked */
  __HAL_UNLOCK(huart);

这个函数就在里面进行了前面所需要的操做, 已经封装好了,无需你亲自动手

cpp 复制代码
  /* Set the UART DMA transfer complete callback */
  huart->hdmarx->XferCpltCallback = UART_DMAReceiveCplt;

这一行代码就是将这个回调函数挂载到了DMA的全满回调里

点开DMA的类定义不难看到挂载在其中的回调函数 , XferCpltCallback就在其中 , 在传输完成之后进行回调

但是 UART_DMAReceiveCplt是串口的接收完成函数吗?

其实不是

点开Functions

这个函数才是

但是为什么说把UART_DMAReceiveCplt挂载在XferCpltCallback上 就等于调用了这个函数

打开

UART_DMAReceiveCplt函数

往下翻可以看到其最终会调用这个函数

DMAR

这个就是前面提到的DMAR(忘记了或者没了解的可以点击左边目录跳转对应DMAR寄存器的部分)

相关推荐
切糕师学AI2 小时前
ARM 架构中的 PRIMASK、FAULTMAST、BASEPRI 寄存器
arm开发·架构·嵌入式·寄存器
minglie12 小时前
Wokwi@通过RFC2217 TCP 服务器连接到模拟微控制器串口
mcu
m0_553210042 小时前
stm32读取rtc年份错误问题,需要指定星期几
stm32·单片机
Jerry丶Li2 小时前
NXP--S32K移植FreeRTOS
嵌入式硬件·rtos·nxp·s32k
南棱笑笑生2 小时前
20251211给飞凌OK3588-C开发板适配Rockchip原厂的Buildroot【linux-6.1】系统时适配adb【type-C0】
linux·c语言·adb·rockchip
q27551300423 小时前
PL27A1对拷线搭配 PTCB818A 设计资料 高速跨系统互传+键鼠共享一缆搞定
经验分享·单片机·嵌入式硬件·硬件架构·信号处理
Joshua-a3 小时前
SPI芯片选择(CS)引脚的深层作用:为什么必须直连MCU的GPIO?
单片机·嵌入式硬件
chenchen000000003 小时前
全志新一代“普惠”工业芯方案:HZ-T153_MiniEVM开发板评测
驱动开发·嵌入式硬件
say_fall3 小时前
C语言编程实战:每日一题:用栈实现队列
c语言·开发语言