一个严谨的STM32串口DMA发送&接收(1.5Mbps波特率)机制

2023-03-28  

1 前言
直接存储器访问(Direct Memory Access),简称 DMA。DMA 是 CPU 一个用于数据从一个地址空间到另一地址空间“搬运”(拷贝)的组件,数据拷贝过程不需 CPU 干预,数据拷贝结束则通知 CPU 处理。

因此,大量数据拷贝时,使用 DMA 可以释放 CPU 资源。DMA 数据拷贝过程,典型的有:
内存—>内存,内存间拷贝
外设—>内存,如 uart、spi、i2c 等总线接收数据过程
内存—>外设,如 uart、spi、i2c 等总线发送数据过程

2 串口有必要使用 DMA 吗
串口(uart)是一种低速的串行异步通信,适用于低速通信场景,通常使用的波特率小于或等于 115200bps。

对于小于或者等于 115200bps 波特率的,而且数据量不大的通信场景,一般没必要使用 DMA,或者说使用 DMA 并未能充分发挥出 DMA 的作用。

对于数量大,或者波特率提高时,必须使用 DMA 以释放 CPU 资源,因为高波特率可能带来这样的问题:
对于发送,使用循环发送,可能阻塞线程,需要消耗大量 CPU 资源“搬运”数据,浪费 CPU
对于发送,使用中断发送,不会阻塞线程,但需浪费大量中断资源,CPU 频繁响应中断;以 115200bps 波特率,1s 传输 11520 字节,大约 69us 需响应一次中断,如波特率再提高,将消耗更多 CPU 资源
对于接收,如仍采用传统的中断模式接收,同样会因为频繁中断导致消耗大量 CPU 资源
因此,高波特率场景下,串口非常有必要使用 DMA。

3 实现方式


整体设计图

4 STM32 串口使用 DMA
关于 STM32 串口使用 DMA,不乏一些开发板例程及网络上一些博主的使用教程。使用步骤、流程、配置基本大同小异,正确性也没什么毛病,但都是一些基本的 Demo 例子,作为学习过程没问题;实际项目使用缺乏严谨性,数据量大时可能导致数据异常。

测试平台:
STM32F030C8T6
UART1/UART2
DMA1 Channel2—Channel5
ST 标准库
主频 48MHz(外部 12MHz 晶振)


在这里插入图片描述

5 串口 DMA 接收
5.1 基本流程


串口接收流程图

5.2 相关配置
关键步骤
【1】初始化串口
【2】使能串口 DMA 接收模式,使能串口空闲中断
【3】配置 DMA 参数,使能 DMA 通道 buf 半满(传输一半数据)中断、buf 溢满(传输数据完成)中断

为什么需要使用 DMA 通道 buf 半满中断?
很多串口 DMA 模式接收的教程、例子,基本是使用了“空间中断”+“DMA 传输完成中断”来接收数据。

实质上这是存在风险的,当 DMA 传输数据完成,CPU 介入开始拷贝 DMA 通道 buf 数据,如果此时串口继续有数据进来,DMA 继续搬运数据到 buf,就有可能将数据覆盖,因为 DMA 数据搬运是不受 CPU 控制的,即使你关闭了 CPU 中断。

严谨的做法需要做双 buf,CPU 和 DMA 各自一块内存交替访问,即是"乒乓缓存” ,处理流程步骤应该是这样:
【1】第一步,DMA 先将数据搬运到 buf1,搬运完成通知 CPU 来拷贝 buf1 数据
【2】第二步,DMA 将数据搬运到 buf2,与 CPU 拷贝 buf1 数据不会冲突
【3】第三步,buf2 数据搬运完成,通知 CPU 来拷贝 buf2 数据
【4】执行完第三步,DMA 返回执行第一步,一直循环


双缓存 DMA 数据搬运过程

STM32F0 系列 DMA 不支持双缓存(以具体型号为准)机制,但提供了一个 buf"半满中断"。

即是数据搬运到 buf 大小的一半时,可以产生一个中断信号。基于这个机制,我们可以实现双缓存功能,只需将 buf 空间开辟大一点即可。

【1】第一步,DMA 将数据搬运完成 buf 的前一半时,产生“半满中断”,CPU 来拷贝 buf 前半部分数据
【2】第二步,DMA 继续将数据搬运到 buf 的后半部分,与 CPU 拷贝 buf 前半部数据不会冲突
【3】第三步,buf 后半部分数据搬运完成,触发“溢满中断”,CPU 来拷贝 buf 后半部分数据
【4】执行完第三步,DMA 返回执行第一步,一直循环


使用半满中断 DMA 数据搬运过程

UART2 DMA 模式接收配置代码如下,与其他外设使用 DMA 的配置基本一致,留意关键配置:
串口接收,DMA 通道工作模式设为连续模式
使能 DMA 通道接收 buf 半满中断、溢满(传输完成)中断
启动 DMA 通道前清空相关状态标识,防止首次传输错乱数据
左右滑动查看全部代码>>>
void bsp_uart2_dmarx_config(uint8_t *mem_addr, uint32_t mem_size)
{
DMA_InitTypeDef DMA_InitStructure;

DMA_DeInit(DMA1_Channel5);
DMA_Cmd(DMA1_Channel5, DISABLE);
DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&(USART2->RDR);/* UART2 接收数据地址 */
DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)mem_addr; /* 接收 buf */
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC; /* 传输方向:外设 ->内存 */
DMA_InitStructure.DMA_BufferSize = mem_size; /* 接收 buf 大小 */
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;
DMA_InitStructure.DMA_Mode = DMA_Mode_Circular; /* 连续模式 */
DMA_InitStructure.DMA_Priority = DMA_Priority_VeryHigh;
DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;
DMA_Init(DMA1_Channel5, &DMA_InitStructure);
DMA_ITConfig(DMA1_Channel5, DMA_IT_TC|DMA_IT_HT|DMA_IT_TE, ENABLE);/* 使能 DMA 半满、溢满、错误中断 */
DMA_ClearFlag(DMA1_IT_TC5); /* 清除相关状态标识 */
DMA_ClearFlag(DMA1_IT_HT5);
DMA_Cmd(DMA1_Channel5, ENABLE);
}
DMA 错误中断“DMA_IT_TE”,一般用于前期调试使用,用于检查 DMA 出现错误的次数,发布软件可以不使能该中断。

5.3 接收处理
基于上述描述机制,DMA 方式接收串口数据,有三种中断场景需要 CPU 去将 buf 数据拷贝到 fifo 中,分别是:
DMA 通道 buf 溢满(传输完成)场景
DMA 通道 buf 半满场景
串口空闲中断场景
前两者场景,前面文章已经描述。串口空闲中断指的是,数据传输完成后,串口监测到一段时间内没有数据进来,则触发产生的中断信号。

5.3 .1 接收数据大小
数据传输过程是随机的,数据大小也是不定的,存在几类情况:
数据刚好是 DMA 接收 buf 的整数倍,这是理想的状态
数据量小于 DMA 接收 buf 或者小于接收 buf 的一半,此时会触发串口空闲中断
因此,我们需根据“DMA 通道 buf 大小”、“DMA 通道 buf 剩余空间大小”、“上一次接收的总数据大小”来计算当前接收的数据大小。

/* 获取 DMA 通道接收 buf 剩余空间大小 */
uint16_t DMA_GetCurrDataCounter(DMA_Channel_TypeDef* DMAy_Channelx);
DMA 通道 buf 溢满场景计算
接收数据大小 = DMA 通道 buf 大小 - 上一次接收的总数据大小
DMA 通道 buf 溢满中断处理函数:
左右滑动查看全部代码>>>
void uart_dmarx_done_isr(uint8_t uart_id)
{
uint16_t recv_size;

recv_size = s_uart_dev[uart_id].dmarx_buf_size - s_uart_dev[uart_id].last_dmarx_size;
fifo_write(&s_uart_dev[uart_id].rx_fifo,
(const uint8_t *)&(s_uart_dev[uart_id].dmarx_buf[s_uart_dev[uart_id].last_dmarx_size]), recv_size);
s_uart_dev[uart_id].last_dmarx_size = 0;
}
DMA 通道 buf 半满场景计算
接收数据大小 = DMA 通道接收总数据大小 - 上一次接收的总数据大小
DMA 通道接收总数据大小 = DMA 通道 buf 大小 - DMA 通道 buf 剩余空间大小
DMA 通道 buf 半满中断处理函数:

3年嵌入式物联网学习资源整理分享:C语言、Linux开发、数据结构;软件开发,STM32单片机、ARM硬件开发、物联网通信开发、综合项目开发教程资料;笔试面试真题。点击下方插件免费领取↓↓↓

加微信领取资料s.pdb2.com/l/CMIsoKcnATFIF4M


void uart_dmarx_half_done_isr(uint8_t uart_id)
{
uint16_t recv_total_size;
uint16_t recv_size;

if(uart_id == 0)
{
recv_total_size = s_uart_dev[uart_id].dmarx_buf_size - bsp_uart1_get_dmarx_buf_remain_size();
}
else if (uart_id == 1)
{
recv_total_size = s_uart_dev[uart_id].dmarx_buf_size - bsp_uart2_get_dmarx_buf_remain_size();
}
recv_size = recv_total_size - s_uart_dev[uart_id].last_dmarx_size;

fifo_write(&s_uart_dev[uart_id].rx_fifo,
(const uint8_t *)&(s_uart_dev[uart_id].dmarx_buf[s_uart_dev[uart_id].last_dmarx_size]), recv_size);
s_uart_dev[uart_id].last_dmarx_size = recv_total_size;/* 记录接收总数据大小 */
}
串口空闲中断场景计算
串口空闲中断场景的接收数据计算与“DMA 通道 buf 半满场景”计算方式是一样的。
串口空闲中断处理函数:
左右滑动查看全部代码>>>
void uart_dmarx_idle_isr(uint8_t uart_id)
{
uint16_t recv_total_size;
uint16_t recv_size;

if(uart_id == 0)
{
recv_total_size = s_uart_dev[uart_id].dmarx_buf_size - bsp_uart1_get_dmarx_buf_remain_size();
}
else if (uart_id == 1)
{
recv_total_size = s_uart_dev[uart_id].dmarx_buf_size - bsp_uart2_get_dmarx_buf_remain_size();
}
recv_size = recv_total_size - s_uart_dev[uart_id].last_dmarx_size;
s_UartTxRxCount[uart_id*2+1] += recv_size;
fifo_write(&s_uart_dev[uart_id].rx_fifo,
(const uint8_t *)&(s_uart_dev[uart_id].dmarx_buf[s_uart_dev[uart_id].last_dmarx_size]), recv_size);
s_uart_dev[uart_id].last_dmarx_size = recv_total_size;
}
注:串口空闲中断处理函数,除了将数据拷贝到串口接收 fifo 中,还可以增加特殊处理,如作为串口数据传输完成标识、不定长度数据处理等等。

5.3.2 接收数据偏移地址
将有效数据拷贝到 fifo 中,除了需知道有效数据大小外,还需知道数据存储于 DMA 接收 buf 的偏移地址。

有效数据偏移地址只需记录上一次接收的总大小即,可,在 DMA 通道 buf 全满中断处理函数将该值清零,因为下一次数据将从 buf 的开头存储。

在 DMA 通道 buf 溢满中断处理函数中将数据偏移地址清零:
void uart_dmarx_done_isr(uint8_t uart_id)
{
/* todo */
s_uart_dev[uart_id].last_dmarx_size = 0;
}

5.4 应用读取串口数据方法
经过前面的处理步骤,已将串口数据拷贝至接收 fifo,应用程序任务只需从 fifo 获取数据进行处理。前提是,处理效率必须大于 DAM 接收搬运数据的效率,否则导致数据丢失或者被覆盖处理。

6 串口 DMA 发送
5.1 基本流程


串口发送流程图

5.2 相关配置
关键步骤
【1】初始化串口
【2】使能串口 DMA 发送模式
【3】配置 DMA 发送通道,这一步无需在初始化设置,有数据需要发送时才配置使能 DMA 发送通道

UART2 DMA 模式发送配置代码如下,与其他外设使用 DMA 的配置基本一致,留意关键配置:
串口发送是,DMA 通道工作模式设为单次模式(正常模式),每次需要发送数据时重新配置 DMA
使能 DMA 通道传输完成中断,利用该中断信息处理一些必要的任务,如清空发送状态、启动下一次传输
启动 DMA 通道前清空相关状态标识,防止首次传输错乱数据
左右滑动查看全部代码>>>
void bsp_uart2_dmatx_config(uint8_t *mem_addr, uint32_t mem_size)
{
DMA_InitTypeDef DMA_InitStructure;

DMA_DeInit(DMA1_Channel4);
DMA_Cmd(DMA1_Channel4, DISABLE);
DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&(USART2->TDR);/* UART2 发送数据地址 */
DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)mem_addr; /* 发送数据 buf */
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST; /* 传输方向:内存 ->外设 */
DMA_InitStructure.DMA_BufferSize = mem_size; /* 发送数据 buf 大小 */
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;
DMA_InitStructure.DMA_Mode = DMA_Mode_Normal; /* 单次模式 */
DMA_InitStructure.DMA_Priority = DMA_Priority_High;
DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;
DMA_Init(DMA1_Channel4, &DMA_InitStructure);
DMA_ITConfig(DMA1_Channel4, DMA_IT_TC|DMA_IT_TE, ENABLE); /* 使能传输完成中断、错误中断 */
DMA_ClearFlag(DMA1_IT_TC4); /* 清除发送完成标识 */
DMA_Cmd(DMA1_Channel4, ENABLE); /* 启动 DMA 发送 */
}

5.3 发送处理
串口待发送数据存于发送 fifo 中,发送处理函数需要做的的任务就是循环查询发送 fifo 是否存在数据,如存在则将该数据拷贝到 DMA 发送 buf 中,然后启动 DMA 传输。

前提是需要等待上一次 DMA 传输完毕,即是 DMA 接收到 DMA 传输完成中断信号"DMA_IT_TC"。

串口发送处理函数:
左右滑动查看全部代码>>>
void uart_poll_dma_tx(uint8_t uart_id)
{
uint16_t size = 0;

if (0x01 == s_uart_dev[uart_id].status)
{
return;
}
size = fifo_read(&s_uart_dev[uart_id].tx_fifo, s_uart_dev[uart_id].dmatx_buf,
s_uart_dev[uart_id].dmatx_buf_size);
if (size != 0)
{
s_UartTxRxCount[uart_id*2+0] += size;
if (uart_id == 0)
{
s_uart_dev[uart_id].status = 0x01; /* DMA 发送状态 */
bsp_uart1_dmatx_config(s_uart_dev[uart_id].dmatx_buf, size);
}
else if (uart_id == 1)
{
s_uart_dev[uart_id].status = 0x01; /* DMA 发送状态,必须在使能 DMA 传输前置位,否则有可能 DMA 已经传输并进入中断 */
bsp_uart2_dmatx_config(s_uart_dev[uart_id].dmatx_buf, size);
}
}
}
注意发送状态标识,必须先置为“发送状态”,然后启动 DMA 传输。如果步骤反过来,在传输数据量少时,DMA 传输时间短,“DMA_IT_TC”中断可能比“发送状态标识置位”先执行,导致程序误判 DMA 一直处理发送状态(发送标识无法被清除)。

注:关于 DMA 发送数据启动函数,有些博客文章描述只需改变 DMA 发送 buf 的大小即可;经过测试发现,该方法在发送数据量较小时可行,数据量大后,导致发送失败,而且不会触发 DMA 发送完成中断。因此,可靠办法是:每次启动 DMA 发送,重新配置 DMA 通道所有参数。该步骤只是配置寄存器过程,实质上不会占用很多 CPU 执行时间。

DMA 传输完成中断处理函数:
void uart_dmatx_done_isr(uint8_t uart_id)
{
s_uart_dev[uart_id].status = 0; /* 清空 DMA 发送状态标识 */
}
上述串口发送处理函数可以在几种情况调用:
主线程任务调用,前提是线程不能被其他任务阻塞,否则导致 fifo 溢出
void thread(void)
{
uart_poll_dma_tx(DEV_UART1);
uart_poll_dma_tx(DEV_UART2);
}
定时器中断中调用
void TIMx_IRQHandler(void)
{
uart_poll_dma_tx(DEV_UART1);
uart_poll_dma_tx(DEV_UART2);
}
DMA 通道传输完成中断中调用
void DMA1_Channel4_5_IRQHandler(void)
{
if(DMA_GetITStatus(DMA1_IT_TC4))
{
UartDmaSendDoneIsr(UART_2);
DMA_ClearFlag(DMA1_FLAG_TC4);
uart_poll_dma_tx(DEV_UART2);
}
}
每次拷贝多少数据量到 DMA 发送 buf:
关于这个问题,与具体应用场景有关,遵循的原则就是:只要发送 fifo 的数据量大于等于 DMA 发送 buf 的大小,就应该填满 DMA 发送 buf,然后启动 DMA 传输,这样才能充分发挥会 DMA 性能。

因此,需兼顾每次 DMA 传输的效率和串口数据流实时性,参考着几类实现:
周期查询发送 fifo 数据,启动 DMA 传输,充分利用 DMA 发送效率,但可能降低串口数据流实时性
实时查询发送 fifo 数据,加上超时处理,理想的方法
在 DMA 传输完成中断中处理,保证实时连续数据流

6 串口设备
6.1 数据结构
/* 串口设备数据结构 */
typedef struct
{
uint8_t status; /* 发送状态 */
_fifo_t tx_fifo; /* 发送 fifo */
_fifo_t rx_fifo; /* 接收 fifo */
uint8_t *dmarx_buf; /* dma 接收缓存 */
uint16_t dmarx_buf_size;/* dma 接收缓存大小*/
uint8_t *dmatx_buf; /* dma 发送缓存 */
uint16_t dmatx_buf_size;/* dma 发送缓存大小 */
uint16_t last_dmarx_size;/* dma 上一次接收数据大小 */
}uart_device_t;
6.2 对外接口
左右滑动查看全部代码>>>
/* 串口注册初始化函数 */
void uart_device_init(uint8_t uart_id)
{
if (uart_id == 1)
{
/* 配置串口 2 收发 fifo */
fifo_register(&s_uart_dev[uart_id].tx_fifo, &s_uart2_tx_buf[0],
sizeof(s_uart2_tx_buf), fifo_lock, fifo_unlock);
fifo_register(&s_uart_dev[uart_id].rx_fifo, &s_uart2_rx_buf[0],
sizeof(s_uart2_rx_buf), fifo_lock, fifo_unlock);

/* 配置串口 2 DMA 收发 buf */
s_uart_dev[uart_id].dmarx_buf = &s_uart2_dmarx_buf[0];
s_uart_dev[uart_id].dmarx_buf_size = sizeof(s_uart2_dmarx_buf);
s_uart_dev[uart_id].dmatx_buf = &s_uart2_dmatx_buf[0];
s_uart_dev[uart_id].dmatx_buf_size = sizeof(s_uart2_dmatx_buf);
bsp_uart2_dmarx_config(s_uart_dev[uart_id].dmarx_buf,
sizeof(s_uart2_dmarx_buf));
s_uart_dev[uart_id].status = 0;
}
}
/* 串口发送函数 */
uint16_t uart_write(uint8_t uart_id, const uint8_t *buf, uint16_t size)
{
return fifo_write(&s_uart_dev[uart_id].tx_fifo, buf, size);
}
/* 串口读取函数 */
uint16_t uart_read(uint8_t uart_id, uint8_t *buf, uint16_t size)
{
return fifo_read(&s_uart_dev[uart_id].rx_fifo, buf, size);
}

7 相关文章
依赖的 fifo 参考该文章:
通用环形缓冲区模块:
acuity.blog.csdn.net/ar

8 完整源码
代码仓库:
github.com/Prry/stm32f0
串口&DMA 底层配置:
左右滑动查看全部代码>>>
#include
#include
#include
#include "stm32f0xx.h"
#include "bsp_uart.h"
/**
* @brief
* @param
* @retval
*/
static void bsp_uart1_gpio_init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
#if 0
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOB, ENABLE);

GPIO_PinAFConfig(GPIOB, GPIO_PinSource6, GPIO_AF_0);
GPIO_PinAFConfig(GPIOB, GPIO_PinSource7, GPIO_AF_0);

GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_Level_3;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;
GPIO_Init(GPIOB, &GPIO_InitStructure);
#else
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOA, ENABLE);

GPIO_PinAFConfig(GPIOB, GPIO_PinSource9, GPIO_AF_1);
GPIO_PinAFConfig(GPIOB, GPIO_PinSource10, GPIO_AF_1);

GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9 | GPIO_Pin_10;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_Level_3;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;
GPIO_Init(GPIOA, &GPIO_InitStructure);
#endif
}
/**
* @brief
* @param
* @retval
*/
static void bsp_uart2_gpio_init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;

RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOB, ENABLE);

GPIO_PinAFConfig(GPIOA, GPIO_PinSource2, GPIO_AF_1);
GPIO_PinAFConfig(GPIOA, GPIO_PinSource3, GPIO_AF_1);

GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2 | GPIO_Pin_3;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_10MHz;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;
GPIO_Init(GPIOA, &GPIO_InitStructure);
}
/**
* @brief
* @param
* @retval
*/
void bsp_uart1_init(void)
{
USART_InitTypeDef USART_InitStructure;
NVIC_InitTypeDef NVIC_InitStructure;

bsp_uart1_gpio_init();

/* 使能串口和 DMA 时钟 */
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);

USART_InitStructure.USART_BaudRate = 57600;
USART_InitStructure.USART_WordLength = USART_WordLength_8b;
USART_InitStructure.USART_StopBits = USART_StopBits_1;
USART_InitStructure.USART_Parity = USART_Parity_No;
USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
USART_Init(USART1, &USART_InitStructure);

USART_ITConfig(USART1, USART_IT_IDLE, ENABLE); /* 使能空闲中断 */
USART_OverrunDetectionConfig(USART1, USART_OVRDetection_Disable);

USART_Cmd(USART1, ENABLE);
USART_DMACmd(USART1, USART_DMAReq_Rx|USART_DMAReq_Tx, ENABLE); /* 使能 DMA 收发 */
/* 串口中断 */
NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPriority = 2;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
/* DMA 中断 */
NVIC_InitStructure.NVIC_IRQChannel = DMA1_Channel2_3_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
}
/**
* @brief
* @param
* @retval
*/
void bsp_uart2_init(void)
{
USART_InitTypeDef USART_InitStructure;
NVIC_InitTypeDef NVIC_InitStructure;

bsp_uart2_gpio_init();

/* 使能串口和 DMA 时钟 */
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);
RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2, ENABLE);
USART_InitStructure.USART_BaudRate = 57600;
USART_InitStructure.USART_WordLength = USART_WordLength_8b;
USART_InitStructure.USART_StopBits = USART_StopBits_1;
USART_InitStructure.USART_Parity = USART_Parity_No;
USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
USART_Init(USART2, &USART_InitStructure);

USART_ITConfig(USART2, USART_IT_IDLE, ENABLE); /* 使能空闲中断 */
USART_OverrunDetectionConfig(USART2, USART_OVRDetection_Disable);

USART_Cmd(USART2, ENABLE);
USART_DMACmd(USART2, USART_DMAReq_Rx|USART_DMAReq_Tx, ENABLE); /* 使能 DMA 收发 */
/* 串口中断 */
NVIC_InitStructure.NVIC_IRQChannel = USART2_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPriority = 2;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
/* DMA 中断 */
NVIC_InitStructure.NVIC_IRQChannel = DMA1_Channel4_5_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
}
void bsp_uart1_dmatx_config(uint8_t *mem_addr, uint32_t mem_size)
{
DMA_InitTypeDef DMA_InitStructure;

DMA_DeInit(DMA1_Channel2);
DMA_Cmd(DMA1_Channel2, DISABLE);
DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&(USART1->TDR);
DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)mem_addr;
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST; /* 传输方向:内存 ->外设 */
DMA_InitStructure.DMA_BufferSize = mem_size;
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;
DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;
DMA_InitStructure.DMA_Priority = DMA_Priority_High;
DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;
DMA_Init(DMA1_Channel2, &DMA_InitStructure);
DMA_ITConfig(DMA1_Channel2, DMA_IT_TC|DMA_IT_TE, ENABLE);
DMA_ClearFlag(DMA1_IT_TC2); /* 清除发送完成标识 */
DMA_Cmd(DMA1_Channel2, ENABLE);
}
void bsp_uart1_dmarx_config(uint8_t *mem_addr, uint32_t mem_size)
{
DMA_InitTypeDef DMA_InitStructure;

DMA_DeInit(DMA1_Channel3);
DMA_Cmd(DMA1_Channel3, DISABLE);
DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&(USART1->RDR);
DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)mem_addr;
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC; /* 传输方向:外设 ->内存 */
DMA_InitStructure.DMA_BufferSize = mem_size;
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;
DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;
DMA_InitStructure.DMA_Priority = DMA_Priority_VeryHigh;
DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;
DMA_Init(DMA1_Channel3, &DMA_InitStructure);
DMA_ITConfig(DMA1_Channel3, DMA_IT_TC|DMA_IT_HT|DMA_IT_TE, ENABLE);/* 使能 DMA 半满、全满、错误中断 */
DMA_ClearFlag(DMA1_IT_TC3);
DMA_ClearFlag(DMA1_IT_HT3);
DMA_Cmd(DMA1_Channel3, ENABLE);
}
uint16_t bsp_uart1_get_dmarx_buf_remain_size(void)
{
return DMA_GetCurrDataCounter(DMA1_Channel3); /* 获取 DMA 接收 buf 剩余空间 */
}
void bsp_uart2_dmatx_config(uint8_t *mem_addr, uint32_t mem_size)
{
DMA_InitTypeDef DMA_InitStructure;

DMA_DeInit(DMA1_Channel4);
DMA_Cmd(DMA1_Channel4, DISABLE);
DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&(USART2->TDR);
DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)mem_addr;
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST; /* 传输方向:内存 ->外设 */
DMA_InitStructure.DMA_BufferSize = mem_size;
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;
DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;
DMA_InitStructure.DMA_Priority = DMA_Priority_High;
DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;
DMA_Init(DMA1_Channel4, &DMA_InitStructure);
DMA_ITConfig(DMA1_Channel4, DMA_IT_TC|DMA_IT_TE, ENABLE);
DMA_ClearFlag(DMA1_IT_TC4); /* 清除发送完成标识 */
DMA_Cmd(DMA1_Channel4, ENABLE);
}
void bsp_uart2_dmarx_config(uint8_t *mem_addr, uint32_t mem_size)
{
DMA_InitTypeDef DMA_InitStructure;

DMA_DeInit(DMA1_Channel5);
DMA_Cmd(DMA1_Channel5, DISABLE);
DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&(USART2->RDR);
DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)mem_addr;
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC; /* 传输方向:外设 ->内存 */
DMA_InitStructure.DMA_BufferSize = mem_size;
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;
DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;
DMA_InitStructure.DMA_Priority = DMA_Priority_VeryHigh;
DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;
DMA_Init(DMA1_Channel5, &DMA_InitStructure);
DMA_ITConfig(DMA1_Channel5, DMA_IT_TC|DMA_IT_HT|DMA_IT_TE, ENABLE);/* 使能 DMA 半满、全满、错误中断 */
DMA_ClearFlag(DMA1_IT_TC5);
DMA_ClearFlag(DMA1_IT_HT5);
DMA_Cmd(DMA1_Channel5, ENABLE);
}
uint16_t bsp_uart2_get_dmarx_buf_remain_size(void)
{
return DMA_GetCurrDataCounter(DMA1_Channel5); /* 获取 DMA 接收 buf 剩余空间 */
}

压力测试:
1.5Mbps 波特率,串口助手每毫秒发送 1k 字节数据,stm32f0 DMA 接收数据,再通过 DMA 发送回串口助手,毫无压力。

1.5Mbps 波特率,可传输大文件测试,将接收数据保存为文件,与源文件比较。

串口高波特率测试需要 USB 转 TLL 工具及串口助手都支持才可行,推荐 CP2102、FT232 芯片的 USB 转 TTL 工具。

文章来源于:电子工程世界    原文链接
本站所有转载文章系出于传递更多信息之目的,且明确注明来源,不希望被转载的媒体或个人可与我们联系,我们将立即进行删除处理。