这篇文章介绍了串口+DMA的发送过程:记群内因串口DMA发送而引发的讨论_哈士奇去买菜的博客-CSDN博客
本文将介绍基于串口+DMA循环模式+循环队列的接收过程。其最大的优点是每次接收完成后不需要禁止MDA通道然后重新配置接收长度。并且还支持接收不定长数据。
我已经写过IDLE中断的解释,请参考:GD32F130之USART串口通信_哈士奇去买菜的博客-CSDN博客_gd32f130串口
这里需要补充强调的是,当本机串口检测到IDLE中断时,只是说明对方已经结束了一串连续的串口帧发送过程,而不一定说明对方已经完成了一包应用层数据的发送。首先串口通信本身没有规定串口帧之间的间隔时间,可以没有间隔,或者可以有任意长间隔。其次,对方在发送一包应用层数据的时候,可能在发送过程中被中断打断,导致发送产生间隔,从而会让本机产生IDLE中断。
在DMA接收方式下,IDLE中断用于告诉应用程序,对方已经完整发送了一包数据,或者,对方只发送了一包数据的一部分。因此,在IDLE中断发生时,应该检查收到数据的完整性,只有收到了完整的数据,才去分析处理这个数据。最好的做法是在应用层设计一个串口通信协议,使用帧头和帧尾以及校验来协助检查数据的完整性和合法性。
我们使用IDLE中断而不是RBNE中断,是因为在接收一包应用层数据时,发生IDLE中断的次数远低于RXNE中断,这样接收起来就更高效,在最好的情况下,接收一包数据只会发生一次IDLE中断。而且在对方彻底完成一包数据的发送后,一定会产生IDLE中断。最重要的原因是,我们要接收不定长数据,则不能使用DMA的传输完成中断。
也可以使用接收超时中断替换IDLE中断。如果你的串口支持接收超时中断(RT)则更好,因为接收超时中断可以设置接收超时时间阈值,而IDLE的超时时间固定为一个串口帧的时间,RT比IDLE更加灵活。
我们也不需要使用DMA的接收完成中断。整个接收过程只需要使用IDLE中断(或者接收超时中断)。
关于DMA循环模式,我也写过说明:
GD32F130之DMA_哈士奇去买菜的博客-CSDN博客
使用循环模式的好处是,当指定长度的串口数据通过DMA接收完成后,DMA硬件自动重新设置传输长度,同时开启下一个接收过程,当接收缓冲区满了后,会自动从接收缓冲区数组的开始存储。接收过程不会停止,因为DMA通道总是处于使能状态。否则,我们需要在每次传输完成后,通过代码禁止DMA通道,配置下一次的传输长度,最后使能通道,而在这个过程,需要CPU的介入,最严重的问题是,可能错过串口数据的接收导致丢数据。
循环队列就是将数组的首位在逻辑上连接起来,臆造成环形形态。
如下图所示,假设DMA接收缓冲区定义为字节数组DMA_buf,长度为6,用DMA_RX_BUF_SIZE表示。把接收缓冲区当做一个循环队列来使用:定义2个循环队列头指针和尾指针数组索引变量front和rear,用于指示当前接收的串口数据在DMA接收缓冲数组中的起始和结束位置。
图(a):现在接收到了3个字符(A,B,C)
图(b):现在接收到了2个字符(h,g)
图(c):现在接收到了3个字符(T,N,Z)
代码实现:GD32F303的USART0+keil
#include "gd32f30x.h"
#include
#include
#include
void RCU_config(void)
{
rcu_periph_clock_enable(RCU_GPIOA);
rcu_periph_clock_enable(RCU_GPIOB);
rcu_periph_clock_enable(RCU_USART0);
rcu_periph_clock_enable(RCU_DMA0);
}
void NVIC_config(void)
{
nvic_priority_group_set(NVIC_PRIGROUP_PRE4_SUB0);
nvic_irq_enable(USART0_IRQn,1,0);
}
void GPIO_config(void)
{
//PA9 USART0_Tx
gpio_init(GPIOA, GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_9);
//PA10 USART0_Rx
gpio_init(GPIOA, GPIO_MODE_IPU, GPIO_OSPEED_50MHZ, GPIO_PIN_10);
}
#define USART0_DMA_TXBUF_SIZE 100
#define USART0_DMA_RXBUF_SIZE 100
uint8_t USART0_DMA_txbuf[USART0_DMA_TXBUF_SIZE];
uint8_t USART0_DMA_rxbuf[USART0_DMA_RXBUF_SIZE];
void USART0_config(void)
{
usart_deinit(USART0);
usart_baudrate_set(USART0, 115200U);
//--接收超时设置
usart_receiver_timeout_threshold_config(USART0,100);
usart_interrupt_enable(USART0,USART_INT_RT);
usart_receiver_timeout_enable(USART0);
//=====配置USART使用的DMA通道======
//USART0_TX:DMA0_CH3
dma_parameter_struct dma_init_struct; //DMA初始化结构体
dma_deinit(DMA0, DMA_CH3); //复位DMA通道为默认配置
dma_init_struct.direction = DMA_MEMORY_TO_PERIPHERAL; //传输方向
dma_init_struct.memory_addr = (uint32_t)USART0_DMA_txbuf; //存储器基地址
dma_init_struct.memory_inc = DMA_MEMORY_INCREASE_ENABLE; //增量式存储器地址生成模式
dma_init_struct.memory_width = DMA_MEMORY_WIDTH_8BIT; //存储器单个数据宽度
dma_init_struct.number = 0; //传输长度
dma_init_struct.periph_addr = (uint32_t)&(USART_DATA(USART0)); //外设基地址(寄存器地址)
dma_init_struct.periph_inc = DMA_PERIPH_INCREASE_DISABLE; //增量式外设地址生成模式
dma_init_struct.periph_width = DMA_PERIPHERAL_WIDTH_8BIT; //外设单个数据宽度
dma_init_struct.priority = DMA_PRIORITY_ULTRA_HIGH; //通道的优先级
dma_init(DMA0, DMA_CH3, &dma_init_struct); //进行配置
dma_circulation_disable(DMA0, DMA_CH3); //配置循环传输模式(单次模式和循环模式)
//dma_interrupt_enable(DMA0,DMA_CH3,DMA_INT_FTF); //DMA中断使能
//dma_channel_enable(DMA0, DMA_CH3); //使能DMA通道
//USART0_RX:DMA0_CH4
dma_deinit(DMA0, DMA_CH4); //复位DMA通道为默认配置
dma_init_struct.direction = DMA_PERIPHERAL_TO_MEMORY; //传输方向
dma_init_struct.memory_addr = (uint32_t)USART0_DMA_rxbuf; //存储器基地址
dma_init_struct.memory_inc = DMA_MEMORY_INCREASE_ENABLE; //增量式存储器地址生成模式
dma_init_struct.memory_width = DMA_MEMORY_WIDTH_8BIT; //存储器单个数据宽度
dma_init_struct.number = USART0_DMA_RXBUF_SIZE; //传输长度
dma_init_struct.periph_addr = (uint32_t)&(USART_DATA(USART0)); //外设基地址(寄存器地址)
dma_init_struct.periph_inc = DMA_PERIPH_INCREASE_DISABLE; //增量式外设地址生成模式
dma_init_struct.periph_width = DMA_PERIPHERAL_WIDTH_8BIT; //外设单个数据宽度
dma_init_struct.priority = DMA_PRIORITY_ULTRA_HIGH; //通道的优先级
dma_init(DMA0, DMA_CH4, &dma_init_struct); //进行配置
dma_circulation_enable(DMA0, DMA_CH4); //配置循环传输模式(单次模式和循环模式)
//dma_interrupt_enable(DMA0,DMA_CH4,DMA_INT_FTF); //DMA中断使能
dma_channel_enable(DMA0, DMA_CH4); //使能DMA通道
//=====使能串口的DMA发送和接收=====
usart_dma_transmit_config(USART0, USART_DENT_ENABLE); //使能串口的DMA发送
usart_dma_receive_config(USART0,USART_DENR_ENABLE); //使能串口的DMA接收
usart_receive_config(USART0, USART_RECEIVE_ENABLE);
usart_transmit_config(USART0, USART_TRANSMIT_ENABLE);
usart_enable(USART0);
}
void USART0_printf(const char*format , ...)
{
int tx_len;
va_list args;
//如果dma传输通道是使能的,且传输没完成
while((DMA_CHCTL(DMA0,DMA_CH3)&0x01) && (!dma_flag_get(DMA0,DMA_CH3,DMA_FLAG_FTF))); //等待DMA传输完成
dma_flag_clear(DMA0,DMA_CH3,DMA_FLAG_FTF); //清除传输完成标志
va_start(args,format);
tx_len = vsnprintf((char*)USART0_DMA_txbuf,USART0_DMA_TXBUF_SIZE,format,args);
va_end(args);
if(tx_len>0)
{
dma_channel_disable(DMA0,DMA_CH3); //关闭DMA通道,这样才能设置传输长度
dma_transfer_number_config(DMA0,DMA_CH3,tx_len); //设置本次DMA传输长度
dma_channel_enable(DMA0,DMA_CH3); //使能USART0_TX使用的DMA通道,开始DMA传输
}
}
volatile uint32_t USART0_get_msg=0;
volatile uint32_t USART0_rear=0;
volatile uint32_t USART0_front=0;
int main(void)
{
RCU_config();
NVIC_config();
GPIO_config();
USART0_config();
while(1)
{
if(USART0_get_msg)
{
USART0_get_msg=0;
uint32_t len = (USART0_DMA_RXBUF_SIZE - USART0_front + USART0_rear)%USART0_DMA_RXBUF_SIZE;//本次消息长度
USART0_printf("\nlen=%u:",len);
//打印出收到的字符
for(uint32_t i=0;i