串口空闲中断配合DMA收发不定长数据

注意下面这段我是引用的 雨飞工作室的,基本没改动,因为觉得将得很好,这里给出链接

https://mp.weixin.qq.com/s/Zp3NjLbj981W8JX-064Few

解释为何用DMA

​ 串口(UART)是一种低速的串行异步通信,适用于低速通信场景,通常使用的波特率小于或等于115200bps。对于小于或者等于115200bps波特率的,而且数据量不大的通信场景,一般没必要使用DMA,或者说使用DMA并未能充分发挥出DMA的作用。对于数量大,或者波特率提高时,必须使用DMA以释放CPU资源,因为高波特率可能带来CPU资源过度浪费的问题。

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

串口接收完数据是要处理的,那么处理的步骤是怎么样呢?

  • 暂时关闭串口接收DMA通道,有两个原因:1.防止后面又有数据接收到,产生干扰,因为此时的数据还未处理。2.DMA需要重新配置。
  • 清串口空闲中断标志位。
  • 从DMA寄存器中获取接收到的数据字节数(可有可无)。
  • 重新设置DMA下次要接收的数据字节数。注意,数据传输数量范围为0至65535。这个寄存器只能在通道不工作(DMA_CCRx的EN=0)时写入。通道开启后该寄存器变为只读,指示剩余的待传输字节数目。寄存器内容在每次DMA传输后递减。数据传输结束后,寄存器的内容或者变为0;或者当该通道配置为自动重加载模式时,寄存器的内容将被自动重新加载为之前配置时的数值。当寄存器的内容为0时,无论通道是否开启,都不会发生任何数据传输。
  • 给出信号量,发送接收到新数据标志,供前台程序查询。
  • 开启DMA通道,等待下一次的数据接收,注意,对DMA的相关寄存器配置写入,如重置DMA接收数据长度,必须要在关闭DMA的条件进行,否则操作无效。

注意事项

STM32的IDLE的中断在串口无数据接收的情况下,是不会一直产生的,产生的条件是这样的,当清除IDLE标志位后,必须有接收到第一个数据后,才开始触发,一断接收的数据断流,没有接收到数据,即产生IDLE中断。如果中断发送数据帧的速率很快,MCU来不及处理此次接收到的数据,中断又发来数据的话,这里不能开启,否则数据会被覆盖。有两种方式解决:(1)在重新开启接收DMA通道之前,将Rx_Buf缓冲区里面的数据复制到另外一个数组中,然后再开启DMA,然后马上处理复制出来的数据。(2)建立双缓冲,重新配置DMA_MemoryBaseAddr的缓冲区地址,那么下次接收到的数据就会保存到新的缓冲区中,不至于被覆盖。

本次实现的是双缓冲,这里参考的是b站超子物联网,思路非常清晰

代码区

usart.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
#ifndef __USART_H
#define __USART_H

#include "stdint.h"
#include "stdarg.h"
#include "stdio.h"
#include "string.h"

#define U0_TX_SIZE 2048
#define U0_RX_SIZE 2048
#define U0_RX_MAX 256
#define NUM 10

extern uint8_t U0_RxBuff[U0_RX_SIZE];
// extern U0_TxBuff[U0_TX_SIZE];

typedef struct
{
uint8_t *start;
uint8_t *end;
}UCB_URxBuffptr;

typedef struct{

uint16_t URxCounter;//计算指向rxbuff哪儿个位置了
UCB_URxBuffptr URxDataPtr[NUM];//定义了10个结构体指针
UCB_URxBuffptr *URxDataIn;//结构体指针
UCB_URxBuffptr *URxDataOut;
UCB_URxBuffptr *URxDataEnd;
}UCB_CB;
extern UCB_CB U0_CB;

void Usart0_Init(uint32_t baund);
void DMA_Init(void);
void U0Rx_PtrInit(void);
void u0_printf(char *format,...);
#endif

usart.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
#include "usart.h"
#include "gd32f10x.h"
uint8_t U0_RxBuff[U0_RX_SIZE];
uint8_t U0_TxBuff[U0_TX_SIZE];
UCB_CB U0_CB;

void Usart0_Init(uint32_t baund)
{
/*1.开启时钟*/
rcu_periph_clock_enable(RCU_GPIOA);
rcu_periph_clock_enable(RCU_USART0);

/*2. 设置输入输出口*/
gpio_init(GPIOA,GPIO_MODE_AF_PP,GPIO_OSPEED_50MHZ,GPIO_PIN_9);
gpio_init(GPIOA,GPIO_MODE_IN_FLOATING,GPIO_OSPEED_50MHZ,GPIO_PIN_10);

/*3.恢复为默认值*/
usart_deinit(USART0);
/*4.配置串口,这里注意要关闭硬件控制流,不然实验得不到想要的结果 这里一般配置6个*/
usart_baudrate_set(USART0,baund);
usart_parity_config(USART0,USART_PM_NONE);
usart_word_length_set(USART0,USART_WL_8BIT);
usart_stop_bit_set(USART0,USART_STB_1BIT);
usart_hardware_flow_rts_config(USART0, USART_RTS_DISABLE);
usart_hardware_flow_cts_config(USART0, USART_CTS_DISABLE);
usart_transmit_config(USART0,USART_TRANSMIT_ENABLE);
usart_receive_config(USART0,USART_RECEIVE_ENABLE);

usart_dma_receive_config(USART0,USART_RECEIVE_DMA_ENABLE);//开启串口DMA接收
nvic_priority_group_set(NVIC_PRIGROUP_PRE2_SUB2);
nvic_irq_enable(USART0_IRQn,0,0);//分配优先级
usart_interrupt_enable(USART0,USART_INT_IDLE);//开启串口空闲中断

DMA_Init();
U0Rx_PtrInit();
usart_enable(USART0);
}

void DMA_Init(void)
{
rcu_periph_clock_enable(RCU_DMA0);

dma_deinit(DMA0,DMA_CH4);
dma_parameter_struct dma_config_t;
/*这里配置9个*/
dma_config_t.direction = DMA_PERIPHERAL_TO_MEMORY;
dma_config_t.memory_addr = (uint32_t)U0_RxBuff;
dma_config_t.memory_inc = DMA_MEMORY_INCREASE_ENABLE ;
dma_config_t.number = U0_RX_MAX + 1;
dma_config_t.memory_width = DMA_MEMORY_WIDTH_8BIT;
dma_config_t.periph_addr = USART0 + 4 ;
dma_config_t.periph_inc = DMA_PERIPH_INCREASE_DISABLE;
dma_config_t.periph_width = DMA_PERIPHERAL_WIDTH_8BIT;
dma_config_t.priority = DMA_PRIORITY_HIGH;
dma_struct_para_init(&dma_config_t);
dma_circulation_disable(DMA0,DMA_CH4);

dma_init(DMA0,DMA_CH4,&dma_config_t);
dma_channel_enable(DMA0,DMA_CH4);
}

void U0Rx_PtrInit(void)
{
U0_CB.URxCounter = 0 ;
U0_CB.URxDataIn = &U0_CB.URxDataPtr[0];
U0_CB.URxDataOut = &U0_CB.URxDataPtr[0];
U0_CB.URxDataEnd = &U0_CB.URxDataPtr[NUM-1];
U0_CB.URxDataIn->start = U0_RxBuff;
}

void u0_printf(char *format,...)
{
uint16_t i;
va_list listdata;
va_start(listdata,format);
vsprintf((char *)U0_TxBuff,format,listdata);
va_end(listdata);
for(i = 0; i < strlen((const char *)U0_TxBuff);i++)
{
while(usart_flag_get(USART0,USART_FLAG_TBE) != 1);
usart_data_transmit(USART0,U0_TxBuff[i]);
}
while (usart_flag_get(USART0,USART_FLAG_TC) != 1);
}

串口0中断函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
void USART0_IRQHandler(void)
{
/*清空标志位*/
if(usart_flag_get(USART0,USART_INT_FLAG_IDLE) == 1)
{
usart_flag_get(USART0,USART_FLAG_IDLEF);
usart_data_receive(USART0);
}
/*读数据*/
U0_CB.URxCounter += (U0_RX_MAX + 1) - dma_transfer_number_get(DMA0,DMA_CH4);//得到接收长度
U0_CB.URxDataIn->end = &U0_RxBuff[U0_CB.URxCounter - 1];
U0_CB.URxDataIn++;
if( U0_CB.URxDataIn == U0_CB.URxDataEnd)
{
U0_CB.URxDataIn = &U0_CB.URxDataPtr[0];
}
if(U0_RX_SIZE - U0_CB.URxCounter >= 256)
{
U0_CB.URxDataIn->start = U0_RxBuff[U0_CB.URxCounter];
}else
{
U0_CB.URxDataIn->start = U0_RxBuff;
U0_CB.URxCounter = 0;
}
dma_channel_disable(DMA0,DMA_CH4);
dma_transfer_number_config(DMA0,DMA_CH4,U0_RX_MAX + 1);
dma_memory_address_config(DMA0,DMA_CH4,(uint32_t)U0_CB.URxDataIn->start);
dma_channel_enable(DMA0,DMA_CH4);
}

main.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#include "gd32f10x.h"
#include "usart.h"

int main(void)
{
Usart0_Init(115200);
uint8_t i;

while (1)
{
if(U0_CB.URxDataOut != U0_CB.URxDataIn)
{
u0_printf("本次接受了%d字节数据\r\n",U0_CB.URxDataOut->end - U0_CB.URxDataOut->start + 1);
for( i = 0 ; i < U0_CB.URxDataOut->end - U0_CB.URxDataOut->start + 1 ;i++ )
{
u0_printf("%c",U0_CB.URxDataOut->start[i]);
}
u0_printf("\r\n\r\n");
U0_CB.URxDataOut++;
if( U0_CB.URxDataOut == U0_CB.URxDataEnd)
{
U0_CB.URxDataOut = &U0_CB.URxDataPtr[0];
}

}
}
}