STM32 HAL库 USART DMA驱动
前言
本文是在使用 STM32L4 的串口 DMA 功能时,使用 HAL 库出现的一些问题,通过以下方式解决了 HAL 库中存在 DMA 发送和接收的一些问题。
STM32L4 的 DMA 简介
DMA Mapping



DMA 相关配置及使用
以下根据 STM32L43xxx 系列进行 USART2 + DMA 的开发。
串口配置
sg_USART2_HandleStruct.Instance = USART2;
sg_USART2_HandleStruct.Init.BaudRate = bound;
sg_USART2_HandleStruct.Init.WordLength = UART_WORDLENGTH_8B; /* 字长为8位数据格式 */
sg_USART2_HandleStruct.Init.StopBits = UART_STOPBITS_1; /* 一个停止位 */
sg_USART2_HandleStruct.Init.Parity = UART_PARITY_NONE; /* 无奇偶校验位 */
sg_USART2_HandleStruct.Init.Mode = UART_MODE_TX_RX; /* 收发模式 */
sg_USART2_HandleStruct.Init.HwFlowCtl = UART_UART_HWCONTROL_NONE_RTS_CTS;
sg_USART2_HandleStruct.Init.OverSampling = UART_OVERSAMPLING_16;
sg_USART2_HandleStruct.Init.OneBitSampling = UART_ONE_BIT_SAMPLE_DISABLE;
sg_USART2_HandleStruct.AdvancedInit.AdvFeatureInit = UART_ADVFEATURE_NO_INIT;
HAL_UART_Init(&sg_USART2_HandleStruct) ;
I/O 配置
/* USART2 clock enable */
__HAL_RCC_USART2_CLK_ENABLE();
__HAL_RCC_GPIOA_CLK_ENABLE();
/**USART2 GPIO Configuration
PA2 ------> USART2_TX
PA3 ------> USART2_RX
*/
GPIO_InitStruct.Pin = GPIO_PIN_2 | GPIO_PIN_3;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Pull = GPIO_PULLUP;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
GPIO_InitStruct.Alternate = GPIO_AF7_USART2;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
配置并使用 TX + DMA
DMA 配置
根据 DMA MAP 表可知,USART2 TX 可使用 DMA1 通道 7 (1-7),通道请求为 2 (0-7),方向为存储器到外设,并且设置字节长度。
/* Configure DMA Tx parameters */
sg_USART2_TxDMAHandleStruct.Instance = DMA1_Channel7;
sg_USART2_TxDMAHandleStruct.Init.Request = DMA_REQUEST_2;
sg_USART2_TxDMAHandleStruct.Init.Direction = DMA_MEMORY_TO_PERIPH;
sg_USART2_TxDMAHandleStruct.Init.PeriphInc = DMA_PINC_DISABLE;
sg_USART2_TxDMAHandleStruct.Init.MemInc = DMA_MINC_ENABLE;
sg_USART2_TxDMAHandleStruct.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE;
sg_USART2_TxDMAHandleStruct.Init.MemDataAlignment = DMA_PDATAALIGN_BYTE;
sg_USART2_TxDMAHandleStruct.Init.Priority = DMA_PRIORITY_HIGH;
sg_USART2_TxDMAHandleStruct.Init.Mode = DMA_NORMAL;
/* Associate the DMA handle */
__HAL_LINKDMA(uartHandle, hdmatx, sg_USART2_TxDMAHandleStruct);
/* Stop any ongoing transfer and reset the state*/
HAL_DMA_DeInit(&sg_USART2_TxDMAHandleStruct);
/* Configure the DMA Channel */
HAL_DMA_Init(&sg_USART2_TxDMAHandleStruct);
DMA TX 使用(禁止中断)
定义一个发送函数(该函数没有使用发送完成中断处理,在下次进入该函数时检测 DMA 相关标志并清除,因此,须确保每次调用该函数的间隔时间能完成上次的数据传输),传输完成必须关闭串口 DMA ,否则不能启动下一次 DMA 传输。
void USART2_UART_Transmit(uint8_t *pData, uint16_t len)
{
if (__HAL_DMA_GET_FLAG(&sg_USART2_HandleStruct, DMA_FLAG_TC7))
{
__HAL_DMA_CLEAR_FLAG(&sg_USART2_HandleStruct, DMA_FLAG_TC7); /* 清除DMA1_Steam7传输完成标志 */
HAL_UART_DMAStop(&sg_USART2_HandleStruct); /* 传输完成以后关闭串口DMA */
}
HAL_UART_Transmit_DMA(&sg_USART2_HandleStruct, pData, len);
}
DMA TX 使用(使能中断)
暂无
配置并使用 RX + DMA
DMA 配置
根据 DMA MAP 表可知,USART2 RX 可使用 DMA1 通道 6 (1-7),通道请求为 2 (0-7),方向为外设到存储器,并且设置字节长度。
/* Configure DMA Rx parameters */
sg_USART2_RxDMAHandleStruct.Instance = DMA1_Channel6;
sg_USART2_RxDMAHandleStruct.Init.Request = DMA_REQUEST_2;
sg_USART2_RxDMAHandleStruct.Init.Direction = DMA_PERIPH_TO_MEMORY;
sg_USART2_RxDMAHandleStruct.Init.PeriphInc = DMA_PINC_DISABLE;
sg_USART2_RxDMAHandleStruct.Init.MemInc = DMA_MINC_ENABLE;
sg_USART2_RxDMAHandleStruct.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE;
sg_USART2_RxDMAHandleStruct.Init.MemDataAlignment = DMA_PDATAALIGN_BYTE;
sg_USART2_RxDMAHandleStruct.Init.Priority = DMA_PRIORITY_HIGH;
sg_USART2_RxDMAHandleStruct.Init.Mode = DMA_NORMAL;
/* Associate the DMA handle */
__HAL_LINKDMA(uartHandle, hdmarx, sg_USART2_RxDMAHandleStruct);
/* Stop any ongoing transfer and reset the state*/
HAL_DMA_DeInit(&sg_USART2_RxDMAHandleStruct);
/* Configure the DMA Channel */
HAL_DMA_Init(&sg_USART2_RxDMAHandleStruct);
接收中断配置
使用 RX DMA,通常采用 DMA + RX + UART_IT_IDLE(空闲中断)用来接收不定长的数据内容。
DMA RX 使用
定义接收中断函数,做相关处理。
void USART2_IRQHandler(void)
{
uint32_t tmp;
if ((__HAL_UART_GET_IT(&sg_USART2_HandleStruct, UART_IT_IDLE) != RESET))
{
/* 清除相关标志 */
__HAL_UART_CLEAR_IDLEFLAG(&sg_USART2_HandleStruct);
tmp = sg_USART2_HandleStruct.Instance->ISR;/* 通过读取该寄存器来清除 */
tmp = sg_USART2_HandleStruct.Instance->RDR;/* 通过读取该寄存器来清除 */
HAL_UART_DMAStop(&sg_USART2_HandleStruct);
/* 获取实际接收长度 */
tmp = __HAL_DMA_GET_COUNTER(&sg_USART2_RxDMAHandleStruct);
sg_USART2_RxLen = USART_MAX_RX_BUF_SIZE - tmp;
g_Usart2RecvFinish = 1;
/* 重新启动接收 */
HAL_UART_Receive_DMA(&sg_USART2_HandleStruct, sg_USART2_RxBuffer, USART_MAX_RX_BUF_SIZE);
}
HAL_UART_IRQHandler(&sg_USART2_HandleStruct);
}
注意事项
1、串口接收中断中若通过函数 HAL_UART_Receive 读取串口数据,会出现没有正常读取数据,导致不停地进入接收中断,造成程序无法正常运行。
void USART2_IRQHandler(void)
{
uint32_t tmp;
if ((__HAL_UART_GET_IT(&sg_USART2_HandleStruct, UART_IT_RXNE) != RESET))
{
将函数
HAL_UART_Receive(&sg_USART2_HandleStruct, sg_USART2_RxBuffer[sg_USART2_RxLen++], 1, 10);
修改为
sg_USART2_RxBuffer[sg_USART2_RxLen++] = USART2->RDR;
}
HAL_UART_IRQHandler(&sg_USART2_HandleStruct);
}
2、在同时使用 DMA RX 和 DAM TX 的时候,在其中一个完成后会关闭串口 DMA,导致另一个受其影响,导致发送或接收异常。
原因:
由于使用了函数 HAL_UART_DMAStop(&sg_USART2_HandleStruct) 用来关闭 UASRT DMA 将TX 和 RX 均给关闭了,导致影响了另一 DMA 正常传输。
解决方案:
基于函数 HAL_UART_DMAStop(UART_HandleTypeDef *huart) 为原型,定义一个函数 MY_HAL_UART_DMAStop(UART_HandleTypeDef *huart, uint8_t obj),
用来可单独关闭其中一个 DMA 的传输,函数如下,其中函数 My_UART_EndTxTransfer 和 My_UART_EndRxTransfer 也是分别基于函数 HAL_UART_DMAStop(UART_HandleTypeDef *huart) 内部中调用的 UART_EndTxTransfer 和 UART_EndRxTransfer 为原型,除了函数名称,没有其他任何改动(在不改动 HAL 库的情况下可这样自定义三个函数,其中 USART_TX_DMA 是自定义的一个宏)。
/**
* @brief End ongoing Tx transfer on UART peripheral (following error detection or Transmit completion).
* @note 函数原型: UART_EndTxTransfer(UART_HandleTypeDef *huart), 函数功能完全一样
* @param huart UART handle.
* @retval None
*/
static void My_UART_EndTxTransfer(UART_HandleTypeDef *huart)
{
#if defined(USART_CR1_FIFOEN)
/* Disable TXEIE, TCIE, TXFT interrupts */
CLEAR_BIT(huart->Instance->CR1, (USART_CR1_TXEIE_TXFNFIE | USART_CR1_TCIE));
CLEAR_BIT(huart->Instance->CR3, (USART_CR3_TXFTIE));
#else
/* Disable TXEIE and TCIE interrupts */
CLEAR_BIT(huart->Instance->CR1, (USART_CR1_TXEIE | USART_CR1_TCIE));
#endif /* USART_CR1_FIFOEN */
/* At end of Tx process, restore huart->gState to Ready */
huart->gState = HAL_UART_STATE_READY;
}
/**
* @brief End ongoing Rx transfer on UART peripheral (following error detection or Reception completion).
* @note 函数原型: UART_EndRxTransfer(UART_HandleTypeDef *huart), 函数功能完全一样
* @param huart UART handle.
* @retval None
*/
static void My_UART_EndRxTransfer(UART_HandleTypeDef *huart)
{
/* Disable RXNE, PE and ERR (Frame error, noise error, overrun error) interrupts */
#if defined(USART_CR1_FIFOEN)
CLEAR_BIT(huart->Instance->CR1, (USART_CR1_RXNEIE_RXFNEIE | USART_CR1_PEIE));
CLEAR_BIT(huart->Instance->CR3, (USART_CR3_EIE | USART_CR3_RXFTIE));
#else
CLEAR_BIT(huart->Instance->CR1, (USART_CR1_RXNEIE | USART_CR1_PEIE));
CLEAR_BIT(huart->Instance->CR3, USART_CR3_EIE);
#endif /* USART_CR1_FIFOEN */
/* At end of Rx process, restore huart->RxState to Ready */
huart->RxState = HAL_UART_STATE_READY;
/* Reset RxIsr function pointer */
huart->RxISR = NULL;
}
/**
* @brief Stop the DMA Transfer.
* @note 函数原型: HAL_UART_DMAStop(UART_HandleTypeDef *huart), 可单独关闭 TX/RX 其中一个DMA
* @param huart UART handle.
* @param obj USART_TX_DMA or USART_RX_DMA.
* @retval HAL status
*/
HAL_StatusTypeDef MY_HAL_UART_DMAStop(UART_HandleTypeDef *huart, uint8_t obj)
{
const HAL_UART_StateTypeDef gstate = huart->gState;
const HAL_UART_StateTypeDef rxstate = huart->RxState;
if (obj == USART_TX_DMA)
{
/* Stop UART DMA Tx request if ongoing */
if ((HAL_IS_BIT_SET(huart->Instance->CR3, USART_CR3_DMAT)) &&
(gstate == HAL_UART_STATE_BUSY_TX))
{
CLEAR_BIT(huart->Instance->CR3, USART_CR3_DMAT);
/* Abort the UART DMA Tx channel */
if (huart->hdmatx != NULL)
{
if (HAL_DMA_Abort(huart->hdmatx) != HAL_OK)
{
if (HAL_DMA_GetError(huart->hdmatx) == HAL_DMA_ERROR_TIMEOUT)
{
/* Set error code to DMA */
huart->ErrorCode = HAL_UART_ERROR_DMA;
return HAL_TIMEOUT;
}
}
}
My_UART_EndTxTransfer(huart);
}
}
else
{
/* Stop UART DMA Rx request if ongoing */
if ((HAL_IS_BIT_SET(huart->Instance->CR3, USART_CR3_DMAR)) &&
(rxstate == HAL_UART_STATE_BUSY_RX))
{
CLEAR_BIT(huart->Instance->CR3, USART_CR3_DMAR);
/* Abort the UART DMA Rx channel */
if (huart->hdmarx != NULL)
{
if (HAL_DMA_Abort(huart->hdmarx) != HAL_OK)
{
if (HAL_DMA_GetError(huart->hdmarx) == HAL_DMA_ERROR_TIMEOUT)
{
/* Set error code to DMA */
huart->ErrorCode = HAL_UART_ERROR_DMA;
return HAL_TIMEOUT;
}
}
}
My_UART_EndRxTransfer(huart);
}
}
return HAL_OK;
}
STM32 HAL库 USART DMA驱动的更多相关文章
- STM32 HAL库利用DMA实现串口不定长度接收方法
参考:https://blog.csdn.net/u014470361/article/details/79206352 我这里使用的芯片是 F1 系列的,主要是利用 DMA 数据传输方式实现的,在配 ...
- WS2812B彩灯详细讲解篇(STM32 PWM+DMA控制 STM32 HAL库编程 循环延时控制多种控制方式)
一.效果展示 观看演示效果:https://www.bilibili.com/video/BV1JT4y1P72Q 二. 基础认识 (一) 小理论 WS2812B是一种智能控制LED光源,将控制电路 ...
- 【春节歌曲回味 | STM32小音乐盒 】PWM+定时器驱动无源蜂鸣器(STM32 HAL库)
l STM32通过PWM与定时器方式控制无源蜂鸣器鸣响 l STM32小音乐盒,歌曲进度条图形显示与百分比显示,歌曲切换 l 编程使用STM32 HAL库 l IIC OLED界面编程,动画实 ...
- 【有趣的全彩LED | 编程】用STM32 HAL库让WS2812B为你所动
一.效果展示 观看演示效果:https://www.bilibili.com/video/BV1dv411Y7x3 使用STM32 HAL库编程 PWM+DMA控制输出,CubeMX生成初始工程 实现 ...
- stm32 HAL库笔记(零)
最近在设计四旋翼飞行器,用stm32f407,有三种开发方式可以选择:一.寄存器开发.二:库函数开发.三:HAL库开发,考虑了一下,选择了HAL库,原因如下: 1. 寄存器开发相对较慢,寄存器很多,配 ...
- STM32 HAL库详解 及 手动移植
源: STM32 HAL库详解 及 手动移植
- 【书籍连载】《STM32 HAL 库开发实战指南—基于F7》-第一章
从今天起,每天开始连载一章<STM32 HAL 库开发实战指南—基于F7>.欢迎各位阅读.点评.学习. 第1章 如何使用本书 1.1 本书的参考资料 本书参考资料为:<STM32 ...
- 【情人节选帽子】TCS34725颜色传感器和Python图形界面编程(STM32 HAL库)
截图 描述: l STM32 HAL库编程 l 使用模拟IIC通信,方便程序移植 l Python界面编写,蘑菇头的帽子是什么颜色 l STM32 HAL库串口通信 l Python界面使用 ...
- STM32 HAL 库实现乒乓缓存加空闲中断的串口 DMA 收发机制,轻松跑上 2M 波特率
前言 直接储存器访问(Direct Memory Access,DMA),允许一些设备独立地访问数据,而不需要经过 CPU 介入处理.因此在访问大量数据时,使用 DMA 可以节约可观的 CPU 处理时 ...
- STM32 HAL库学习 (2) USART实验
使用STM32F407 串口:PA9.PA10(利用CH340G驱动) 一. stm32f4xx_hal_uart.c 函数说明 HAL_UART_Init 函数 要使用一个外设首先要对它进行初始化, ...
随机推荐
- vue+iviews 动态表格(table组件)
iviews官网上关于table的使用方法是固定表头的使用方法,如何生成动态的table网上找了好多也没有特别合适的,综合几位博主的文章经过尝试终于实现了,分享出来供大家参考 一.先看官网上的样例 官 ...
- python常见面试题讲解(四)字符串分隔
题目描述 •连续输入字符串,请按长度为8拆分每个字符串后输出到新的字符串数组:•长度不是8整数倍的字符串请在后面补数字0,空字符串不处理. 输入描述: 连续输入字符串(输入2次,每个字符串长度小于10 ...
- Spring boot 运行服务jar外配置配置文件方式总结
本文为博主原创,转载请注明出处: 由于需要在本地编译打包,在服务器上验证某些功能,需要频繁修改配置,本地打包时,会将配置文件也打包的jar 包内部,这种方式下,若修改配置则需要本地修改重新上传服务器一 ...
- 文心一言 VS 讯飞星火 VS chatgpt (183)-- 算法导论13.4 7题
七.用go语言,假设用 RB-INSERT 将一个结点 x 插入一棵红黑树,紧接着又用 RB-DELETE 将它从树中删除.结果的红黑树与初始的红黑树是否一样?证明你的答案. 文心一言: 首先,我们需 ...
- 星索称重/生产管理软件 联机版V1.0
星索称重/生产管理软件 联机版V1.0 一.特点 1.支持多用户.多组织管理,灵活控制用户权限. 2.支持地磅秤.智能电子秤.轨道秤等多款称重设备. 3.支持三联单/热敏纸等多种打印模板. 二.系 ...
- [转帖]什么是内存屏障? Why Memory Barriers ?
要了解如何使用memory barrier,最好的方法是明白它为什么存在.CPU硬件设计为了提高指令的执行速度,增设了两个缓冲区(store buffer, invalidate queue).这个两 ...
- [转帖]TiDB 环境与系统配置检查
https://docs-archive.pingcap.com/zh/tidb/v6.0/check-before-deployment 本文介绍部署 TiDB 前的环境检查操作,以下各项操作按优先 ...
- [转帖]Jmeter连接InfluxDB2.0.4
Jmeter连接InfluxDB2.0.4 问题描述:在用Jmeter+InfluxDB构建监控时,因为docker构建的InfluxDB的版本是2.0.4,按照网上的教程进行后端监听器的填写,但是一 ...
- [转帖]kubernetes Flannel网络剖析
https://plantegg.github.io/2022/01/19/kubernetes_Flannel%E7%BD%91%E7%BB%9C%E5%89%96%E6%9E%90/ cni(Co ...
- openssh 修改版本号显示
#背景介绍:G端项目经常收到相关漏洞但有时升级最新版本(8.8p)还是会有相关漏洞(CVE-2020-15778),只能禁用相关命令或修改版本号 #漏洞名称OpenSSH 命令注入漏洞(CVE-202 ...
