STM32/GD32学习指南-踩坑之(五)串口收发数据的三种方式:UART接收中断、UART+DMA空闲中断、UART+DMA超时中断,接收不定长数据,纯干货,有史以来最详细的讲解,附源码

6 篇文章 0 订阅
2 篇文章 0 订阅

一、 串口收发功能介绍

        常用的串口收发数据的方式一共两种,一种是不使用DMA,直接串口中断收发数据,另外一种是通过串口+DMA收发数据。

1. 串口接收数据

        对于使用DMA的串口数据收发,一般常用的还可以分为串口接收超时中断和串口空闲中断,所以细分的话,常用的主要有以下三种方式的串口数据接收使用方法:

(1)直接串口中断接收数据:

        也就是串口data寄存器非空即触发中断,中断标志为:USART_INT_RBNE;该种方法每接收一个字节的数据就会触发一次串口接收中断,当串口接收数据量小或是系统性能要求不高的情况下可以使用,如果串口接收数据量很大,频繁进入串口接收中断,会影响系统处理的性能,或者造成串口接收丢数的问题。

(2)串口空闲中断+DMA接收不定长数据:

        该种方式使用DMA(直接内存存取器)接收串口数据,中断标志为:USART_INT_IDLE;该种方式在串口RX处于空闲状态时,会触发中断,也就是一帧数据接收完成后,串口RX不再收到数据,即可触发空闲中断,此时可以将接收到的数据提取出来,由于使用DMA接收,所以可以接收不定长数据,并且数据接收过程中不需要CPU的参与,只是在数据接收完的时候,会触发一次空闲中断才涉及到CPU参与,可以大大的减少CPU的负荷,并且对于所有支持DMA的串口都可以使用串口空闲中断+DMA的方式接收数据。

(3)串口超时中断+DMA接收不定长数据(实测比空闲中断好用):

        超时中断的方式,中断标志为:USART_INT_RT;与上面的空闲中断其实非常类似,只不过空闲中断是硬件自带的,触发空闲中断的间隔也是固定的(1.5个字节,具体换算成时间的话还要涉及到时钟以及波特率的配置等),空闲间隔的判断不可以修改;而超时中断的超时时间是可以自己配置的,比如配置成10个字节的超时时间,那么收到一帧数据后的10个字节的时间内没有再收到新的数据,就会触发超时中断,此时可以通过DMA将接收的不定长数据取出。

        注意:GD32的芯片(我用的GD32 F470),只有USART支持超时中断,UART不支持超时中断,因为UART没有对应的超时RT寄存器,但是UART支持空闲中断。

2. 串口发送数据:

(1)直接串口发送数据:

        直接串口发送数据,只需要将数据写入对应串口的DATA寄存器即可,缺点的话是每次只能发送一个字节,发送不定长数据的话需要自己写个循环,比较占用CPU;优点的是操作比较简单,不容易出错

(2)串口+DMA发送数据:

        通过DMA的方式发送数据,需要将发送数据的buffer与DMA绑定,效率更高,可以降低CPU的占用。

二、 串口接收详细介绍(代码包含三种接收方法)

以下代码都是经过验证的,实际项目中在使用,稳定性良好,供大家参考

 1. Uart_hdl.h文件定义每个串口对应的GPIO以及DMA通道参还有函数声明,从上往下依次是:

(1)是否使能DMA接收和发送的枚举

(2)每个串口对应的GPIO引脚、时钟等

(3)每个串口对应的数据地址、DMA通道、DMA时钟的参数

(4)存储接收数据的环形buffer结构体

(5)DMA数据收发状态参数结构体(包含环形buffer结构体存储数据)

(6)一些串口配置和收发的函数声明

/*!
    \file    Uart_hdl.h
    \brief   firmware functions to manage Uart
    \version 2024-01-10, V1.0.0
		\author  tbj
*/

#include "systick.h"
#include "gd32f4xx.h"
#include <stdio.h>
#include <string.h>

#ifndef UART_HAL_H
#define UART_HAL_H

#ifdef __cplusplus
 extern "C" {
#endif
 
 typedef enum 
{ 
		DMA_TX_EB,
		DMA_TX_NEB,
} UART_DMA_TX_STU;

 typedef enum 
{ 
		DMA_RX_EB,
		DMA_RX_NEB	
} UART_DMA_RX_STU;

//串口引脚配置
#define COMn                             8U

//usart0
#define COM0                        USART0
#define COM0_CLK                    RCU_USART0

#define COM0_TX_PIN                 GPIO_PIN_9
#define COM0_RX_PIN                 GPIO_PIN_10

#define COM0_TX_GPIO_PORT           GPIOA
#define COM0_RX_GPIO_PORT           GPIOA
#define COM0_TX_GPIO_CLK            RCU_GPIOA
#define COM0_RX_GPIO_CLK            RCU_GPIOA
#define COM0_AF                     GPIO_AF_7

//usart1
#define COM1                        USART1
#define COM1_CLK                    RCU_USART1

#define COM1_TX_PIN                 GPIO_PIN_5
#define COM1_RX_PIN                 GPIO_PIN_6

#define COM1_TX_GPIO_PORT           GPIOD
#define COM1_RX_GPIO_PORT           GPIOD
#define COM1_TX_GPIO_CLK            RCU_GPIOD
#define COM1_RX_GPIO_CLK            RCU_GPIOD
#define COM1_AF                     GPIO_AF_7

//usart2
#define COM2                        USART2
#define COM2_CLK                    RCU_USART2

#define COM2_TX_PIN                 GPIO_PIN_8
#define COM2_RX_PIN                 GPIO_PIN_9

#define COM2_TX_GPIO_PORT           GPIOD
#define COM2_RX_GPIO_PORT           GPIOD
#define COM2_TX_GPIO_CLK            RCU_GPIOD
#define COM2_RX_GPIO_CLK            RCU_GPIOD
#define COM2_AF                     GPIO_AF_7

//uart3
#define COM3                        UART3
#define COM3_CLK                    RCU_UART3

#define COM3_TX_PIN                 GPIO_PIN_0
#define COM3_RX_PIN                 GPIO_PIN_1

#define COM3_TX_GPIO_PORT           GPIOA
#define COM3_RX_GPIO_PORT           GPIOA
#define COM3_TX_GPIO_CLK            RCU_GPIOA
#define COM3_RX_GPIO_CLK            RCU_GPIOA
#define COM3_AF                     GPIO_AF_8

//uart4
#define COM4                        UART4
#define COM4_CLK                    RCU_UART4

#define COM4_TX_PIN                 GPIO_PIN_12
#define COM4_RX_PIN                 GPIO_PIN_2

#define COM4_TX_GPIO_PORT           GPIOC
#define COM4_RX_GPIO_PORT           GPIOD
#define COM4_TX_GPIO_CLK            RCU_GPIOC
#define COM4_RX_GPIO_CLK            RCU_GPIOD
#define COM4_AF                     GPIO_AF_8

//usart5
#define COM5                        USART5
#define COM5_CLK                    RCU_USART5

#define COM5_TX_PIN                 GPIO_PIN_6
#define COM5_RX_PIN                 GPIO_PIN_7

#define COM5_TX_GPIO_PORT           GPIOC
#define COM5_RX_GPIO_PORT           GPIOC
#define COM5_TX_GPIO_CLK            RCU_GPIOC
#define COM5_RX_GPIO_CLK            RCU_GPIOC
#define COM5_AF                     GPIO_AF_8

//uart6
#define COM6                        UART6
#define COM6_CLK                    RCU_UART6

#define COM6_TX_PIN                 GPIO_PIN_8
#define COM6_RX_PIN                 GPIO_PIN_7

#define COM6_TX_GPIO_PORT           GPIOE
#define COM6_RX_GPIO_PORT           GPIOE
#define COM6_TX_GPIO_CLK            RCU_GPIOE
#define COM6_RX_GPIO_CLK            RCU_GPIOE
#define COM6_AF                     GPIO_AF_8

//uart7
#define COM7                        UART7
#define COM7_CLK                    RCU_UART7

#define COM7_TX_PIN                 GPIO_PIN_1
#define COM7_RX_PIN                 GPIO_PIN_0

#define COM7_TX_GPIO_PORT           GPIOE
#define COM7_RX_GPIO_PORT           GPIOE
#define COM7_TX_GPIO_CLK            RCU_GPIOE
#define COM7_RX_GPIO_CLK            RCU_GPIOE
#define COM7_AF                     GPIO_AF_8
//****************************************************//
//UART DMA相关
//USART0:
#define USART0_DATA_ADDR    			((uint32_t)0x40011004)
#define USART0_DMA_ADDR					DMA1
#define USART0_DMA_RCU					RCU_DMA1
#define USART0_DMA_TX_CHANNEL			DMA_CH7
#define USART0_DMA_RX_CHANNEL			DMA_CH5//DMA_CH2
#define USART0_DMA_SUBPERI				DMA_SUBPERI4
#define USART0_DMA_MEMORY				DMA_MEMORY_1
//USART1:
#define USART1_DATA_ADDR    			((uint32_t)0x40004404)
#define USART1_DMA_ADDR					DMA0
#define USART1_DMA_RCU					RCU_DMA0
#define USART1_DMA_TX_CHANNEL			DMA_CH6
#define USART1_DMA_RX_CHANNEL			DMA_CH5
#define USART1_DMA_SUBPERI				DMA_SUBPERI4
#define USART1_DMA_MEMORY				DMA_MEMORY_0
//USART2:-与串口6的DMA通道冲突,串口2和串口6只能选其中一个使用DMA
#define USART2_DATA_ADDR    			((uint32_t)0x40004804)
#define USART2_DMA_ADDR					DMA0
#define USART2_DMA_RCU					RCU_DMA0
#define USART2_DMA_TX_CHANNEL			DMA_CH3
#define USART2_DMA_RX_CHANNEL			DMA_CH1
#define USART2_DMA_SUBPERI				DMA_SUBPERI4
#define USART2_DMA_MEMORY				DMA_MEMORY_0
//UART3:
#define UART3_DATA_ADDR    				((uint32_t)0x40004C04)
#define UART3_DMA_ADDR					DMA0
#define UART3_DMA_RCU					RCU_DMA0
#define UART3_DMA_TX_CHANNEL			DMA_CH4
#define UART3_DMA_RX_CHANNEL			DMA_CH2
#define UART3_DMA_SUBPERI				DMA_SUBPERI4
#define UART3_DMA_MEMORY				DMA_MEMORY_0
//UART4:
#define UART4_DATA_ADDR    				((uint32_t)0x40005004)
#define UART4_DMA_ADDR					DMA0
#define UART4_DMA_RCU					RCU_DMA0
#define UART4_DMA_TX_CHANNEL			DMA_CH7
#define UART4_DMA_RX_CHANNEL			DMA_CH0
#define UART4_DMA_SUBPERI				DMA_SUBPERI4
#define UART4_DMA_MEMORY				DMA_MEMORY_0
//USART5:
#define USART5_DATA_ADDR    			((uint32_t)0x40011404)
#define USART5_DMA_ADDR					DMA1
#define USART5_DMA_RCU					RCU_DMA1
#define USART5_DMA_TX_CHANNEL			DMA_CH6
#define USART5_DMA_RX_CHANNEL			DMA_CH1
#define USART5_DMA_SUBPERI				DMA_SUBPERI5
#define USART5_DMA_MEMORY				DMA_MEMORY_1
//UART6:-与串口2的DMA通道冲突,串口2和串口6只能选其中一个使用DMA
#define UART6_DATA_ADDR   				((uint32_t)0x40007804)
#define UART6_DMA_ADDR					DMA0
#define UART6_DMA_RCU					RCU_DMA0
#define UART6_DMA_TX_CHANNEL			DMA_CH1
#define UART6_DMA_RX_CHANNEL			DMA_CH3
#define UART6_DMA_SUBPERI				DMA_SUBPERI5
#define UART6_DMA_MEMORY				DMA_MEMORY_0
//UART7:-与串口4的DMA通道冲突,串口4和串口7只能选其中一个使用DMA
#define UART7_DATA_ADDR    				((uint32_t)0x40007C04)
#define UART7_DMA_ADDR					DMA0
#define UART7_DMA_RCU					RCU_DMA0
#define UART7_DMA_TX_CHANNEL			DMA_CH0
#define UART7_DMA_RX_CHANNEL			DMA_CH6
#define UART7_DMA_SUBPERI				DMA_SUBPERI5
#define UART7_DMA_MEMORY				DMA_MEMORY_0
//****************************************************//

#define  	RINGBUFF_LEN        5120
//环形buffer结构体
typedef struct
{
	uint32_t Head;
	uint32_t Tail;
	uint32_t Length;
	uint8_t Ring_Buff[RINGBUFF_LEN];
} RingBuff_t;

#define 	BUFFER_SIZE   			5120
//串口DMA收发状态和数据长度
typedef struct{
	uint8_t 		uart_dma_rx_state;	//串口是否使用DMA接收
	uint8_t 		uart_dma_tx_state;	//串口是否使用DMA发送
	uint16_t 		uart_dma_rx_len;	//串口使用DMA接收时,超时中断后接收的数据长度
	RingBuff_t 	    uart_rx_buffer;		//串口接收数据缓存区,是一个环形buffer,使用DMA接收时,按正常缓存区使用,不适用DMA接收时,按照环形buffer功能使用
	uint8_t 		uart_tx_buffer[BUFFER_SIZE];//串口使用DMA发送时的发送缓存区
}uart_state;

//↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓普通串口收发↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓//
//串口初始化
void uart_init(uint8_t uart_id, uint32_t baudval, uint8_t nvic_irq_pre_priority, uint8_t nvic_irq_sub_priority, 
								UART_DMA_TX_STU dma_tx_stu, UART_DMA_RX_STU dma_rx_stu);
//串口发送数据-每次发送一字节
void uart_send_byte(uint8_t uart_id, uint8_t ch);
//串口发送数据-每次buf
void uart_send_buf(uint8_t uart_id, uint8_t *ch, uint16_t len);

//↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓DMA串口收发↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓//
//初始化串口DMA tx配置
void usart_dma_tx_init(uint8_t uart_id);
//初始化串口DMA rx配置
void usart_dma_rx_init(uint8_t uart_id);
//DMA串口发送数据
void uart_dma_send(uint8_t uart_id, uint8_t *tx_buffer, uint32_t len);
//获取DMA接收数据的长度
unsigned int get_uart_dma_recv_data_size(uint8_t uart_id);
//初始化串口相关状态、DMA、buffer等
void init_uart_state(uint8_t uart_id);
//重新配置DMA rx,为了重新接收数据
void uart_dma_rx_refcg(uint8_t uart_id);

#ifdef __cplusplus
}
#endif

#endif /* UART_HAL_H */

2. Uart_hdl.c文件主要对Uart_hdl.h文件中声明的函数进行实现,主要包括串口初始化、DMA初始化、串口发送等功能函数,具体如下:

(1)首先定义每个串口状态结构体的对象usart0_state-usart7_state(包含存储数据的buffer在里面),同时定义一个指针数组(uart_dma_state_ptr)指向对应的串口状态对象,方便后续对其操作,然后定义了每个串口是否使用DMA接收数据的状态数组uart_rx_dma_en_stu;

(2)定义串口相应配置参数的若干个数组,主要包括串口号、串口对应GPIO号以及Bank时钟、串口中断号、复用情况等等;

(3)定义串口对应DMA通道以及时钟、地址等参数的若干个数组,这些数组都是为了后续方便统一操作配置多个串口使用;

(4)初始化串口相关参数的方法:void uart_init(uint8_t uart_id, uint32_t baudval, uint8_t nvic_irq_pre_priority, uint8_t nvic_irq_sub_priority, 
                                UART_DMA_TX_STU dma_tx_stu, UART_DMA_RX_STU dma_rx_stu)

通过传入串口号,对不同的串口进行初始化配置,里面包含了GPIO复用串口的相关配置、串口波特率、中断使能、优先级、串口收发使能等配置

同时判断当前初始化的串口是否使用DMA收发,如果使用DMA收发,则同时进行DMA收发的配置操作,其中判断的0,1,2,5串口为USART,可使用串口超时中断和空闲中断,其余的为UART,只能使用空闲中断;

(5)串口单字节数据发送方法:uart_send_byte(uint8_t uart_id, uint8_t ch),函数中的定时器timer1主要是用来防止串口发送数据过程中发生卡死的现象,无法跳出while循环,等待2s后如果未发送完,触发定时器中断,手动将发送完成标志位置位;

(6)串口不定长数据发送方法:void uart_send_buf(uint8_t uart_id, uint8_t *ch, uint16_t len)

(7)printf打印输出重映射方法:int fputc(int ch, FILE *f),可将printf函数打印信息通过串口发出;

(8)串口DMA发送配置方法:void usart_dma_tx_init(uint8_t uart_id),主要配置DMA数据发送方向、发送存储器(发送数据存储的buffer,即之前定义的环形buffer)、DMA使能等;

(9)串口DMA接收配置方法:void usart_dma_rx_init(uint8_t uart_id),主要配置DMA数据接收方向、接收存储器(接收数据存储的buffer,即之前定义的环形buffer)、DMA使能等;

(10)串口DMA数据发送方法:void uart_dma_send(uint8_t uart_id, uint8_t *tx_buffer, uint32_t len),主要将发送数据buffer绑定至DMA,并使能DMA发送,然后等待发送完成,这里面的timer1与(5)中的timer1功能一致;

(11)获取DMA接收数据长度的方法:unsigned int get_uart_dma_recv_data_size(uint8_t uart_id),返回当前串口DMA接收数据的长度

(12)串口DMA接收Rx重配置方法:void uart_dma_rx_refcg(uint8_t uart_id),每当接收完一包数据并且提取出来后,需要重新进入接收状态,这时候就需要重置DMA的一些配置

(13)初始化串口状态对象方法:void init_uart_state(uint8_t uart_id),在初始化串口配置前,先将串口串口、接收、发送buffer等对象置零初始化;

/*!
    \file    Uart_hdl.c
    \brief   firmware functions to manage Uart
    \version 2024-01-10, V1.0.0
		\author  tbj
*/

#include "Uart_hdl.h"

//存储当前发送数据的串口基地址
uint32_t curr_send_uart_addr = 0;
//串口状态对象,包括DMA收发使用情况,串口收发缓存区等
uart_state usart0_state, usart1_state, usart2_state, uart3_state, uart4_state, usart5_state, uart6_state, uart7_state;
//串口状态对象指针数组(为了方便其他函数调用建立的)
uart_state *uart_dma_state_ptr[COMn] = {&usart0_state, &usart1_state, &usart2_state, &uart3_state, &uart4_state, &usart5_state, &uart6_state, &uart7_state};
//串口是否使用DMA通道收数据,用于串口中断函数判断使用
UART_DMA_RX_STU uart_rx_dma_en_stu [COMn] = {DMA_RX_NEB, DMA_RX_NEB, DMA_RX_NEB, DMA_RX_NEB, DMA_RX_NEB, DMA_RX_NEB, DMA_RX_NEB, DMA_RX_NEB};

串口相关参数列表
//串口编号
static uint32_t COM_NUM[COMn] = {USART0, USART1, USART2, UART3, UART4, USART5, UART6, UART7};
//串口中断号
static uint32_t COM_IRQn[COMn] = {USART0_IRQn, USART1_IRQn, USART2_IRQn, UART3_IRQn, UART4_IRQn, USART5_IRQn, UART6_IRQn, UART7_IRQn};
//GPIO复用功能,GPIO_AF_7和GPIO_AF_8将GPIO复用为串口
static uint32_t COM_AF[COMn] = {COM0_AF, COM1_AF, COM2_AF, COM3_AF, COM4_AF, COM5_AF, COM6_AF, COM7_AF};
//串口TX对应的GPIO号
static uint32_t COM_TX_PIN[COMn] = {COM0_TX_PIN, COM1_TX_PIN, COM2_TX_PIN, COM3_TX_PIN, COM4_TX_PIN, COM5_TX_PIN, COM6_TX_PIN, COM7_TX_PIN};
//串口RX对应的GPIO号
static uint32_t COM_RX_PIN[COMn] = {COM0_RX_PIN, COM1_RX_PIN, COM2_RX_PIN, COM3_RX_PIN, COM4_RX_PIN, COM5_RX_PIN, COM6_RX_PIN, COM7_RX_PIN};
//串口TX对应的GPIO端口分组
static uint32_t COM_TX_GPIO_PORT[COMn] = {COM0_TX_GPIO_PORT, COM1_TX_GPIO_PORT, COM2_TX_GPIO_PORT, COM3_TX_GPIO_PORT, COM4_TX_GPIO_PORT, COM5_TX_GPIO_PORT, COM6_TX_GPIO_PORT, COM7_TX_GPIO_PORT};
//串口RX对应的GPIO端口分组
static uint32_t COM_RX_GPIO_PORT[COMn] = {COM0_RX_GPIO_PORT, COM1_RX_GPIO_PORT, COM2_RX_GPIO_PORT, COM3_RX_GPIO_PORT, COM4_RX_GPIO_PORT, COM5_RX_GPIO_PORT, COM6_RX_GPIO_PORT, COM7_RX_GPIO_PORT};
//串口对应时钟
static rcu_periph_enum COM_CLK[COMn] = {COM0_CLK, COM1_CLK, COM2_CLK, COM3_CLK, COM4_CLK, COM5_CLK, COM6_CLK, COM7_CLK};
//复用为串口TX的GPIO的时钟
static rcu_periph_enum COM_TX_GPIO_CLK[COMn] = {COM0_TX_GPIO_CLK, COM1_TX_GPIO_CLK, COM2_TX_GPIO_CLK, COM3_TX_GPIO_CLK, COM4_TX_GPIO_CLK, COM5_TX_GPIO_CLK, COM6_TX_GPIO_CLK, COM7_TX_GPIO_CLK};
//复用为串口RX的GPIO的时钟
static rcu_periph_enum COM_RX_GPIO_CLK[COMn] = {COM0_RX_GPIO_CLK, COM1_RX_GPIO_CLK, COM2_RX_GPIO_CLK, COM3_RX_GPIO_CLK, COM4_RX_GPIO_CLK, COM5_RX_GPIO_CLK, COM6_RX_GPIO_CLK, COM7_RX_GPIO_CLK};

串口DMA相关参数列表
//外设基地址-数据寄存器
static uint32_t COM_DATA_ADDR[COMn] = {USART0_DATA_ADDR, USART1_DATA_ADDR, USART2_DATA_ADDR, UART3_DATA_ADDR, UART4_DATA_ADDR, USART5_DATA_ADDR, UART6_DATA_ADDR, UART7_DATA_ADDR};
//DMA基地址
uint32_t COM_DMA_ADDR[COMn] = {USART0_DMA_ADDR, USART1_DMA_ADDR, USART2_DMA_ADDR, UART3_DMA_ADDR, UART4_DMA_ADDR, USART5_DMA_ADDR, UART6_DMA_ADDR, UART7_DMA_ADDR};
//DMA时钟
static rcu_periph_enum COM_DMA_RCU[COMn] = {USART0_DMA_RCU, USART1_DMA_RCU, USART2_DMA_RCU, UART3_DMA_RCU, UART4_DMA_RCU, USART5_DMA_RCU, UART6_DMA_RCU, UART7_DMA_RCU};
//DMA TX通道
dma_channel_enum COM_DMA_TX_CHANNEL[COMn] = {USART0_DMA_TX_CHANNEL, USART1_DMA_TX_CHANNEL, USART2_DMA_TX_CHANNEL, UART3_DMA_TX_CHANNEL, UART4_DMA_TX_CHANNEL, USART5_DMA_TX_CHANNEL, UART6_DMA_TX_CHANNEL, UART7_DMA_TX_CHANNEL};
//DMA RX通道
dma_channel_enum COM_DMA_RX_CHANNEL[COMn] = {USART0_DMA_RX_CHANNEL, USART1_DMA_RX_CHANNEL, USART2_DMA_RX_CHANNEL, UART3_DMA_RX_CHANNEL, UART4_DMA_RX_CHANNEL, USART5_DMA_RX_CHANNEL, UART6_DMA_RX_CHANNEL, UART7_DMA_RX_CHANNEL};
//DMA总线通道
static dma_subperipheral_enum COM_DMA_SUBPERI[COMn] = {USART0_DMA_SUBPERI, USART1_DMA_SUBPERI, USART2_DMA_SUBPERI, UART3_DMA_SUBPERI, UART4_DMA_SUBPERI, USART5_DMA_SUBPERI, UART6_DMA_SUBPERI, UART7_DMA_SUBPERI};
//static uint32_t COM_DMA_MEMORY[COMn] = {USART0_DMA_MEMORY, USART1_DMA_MEMORY, USART2_DMA_MEMORY, UART3_DMA_MEMORY, UART4_DMA_MEMORY, USART5_DMA_MEMORY, UART6_DMA_MEMORY, UART7_DMA_MEMORY};


//↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓普通串口收发↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓//
/**
  *功能:初始化串口相关参数
  *入参1:串口号
  *入参2:波特率
  *入参3:串口中断抢占优先级
  *入参4:串口中断响应优先级
	*入参5:串口DMA发送使能
	*入参6:串口DMA接收使能
  */
void uart_init(uint8_t uart_id, uint32_t baudval, uint8_t nvic_irq_pre_priority, uint8_t nvic_irq_sub_priority, 
								UART_DMA_TX_STU dma_tx_stu, UART_DMA_RX_STU dma_rx_stu)
{	
    //清空使用串口的状态和缓存区
	init_uart_state(uart_id);

	//使能GPIO时钟
	if(COM_TX_GPIO_CLK[uart_id] == COM_RX_GPIO_CLK[uart_id])
	{
		//TX和RX是同一组GPIO的时钟
		rcu_periph_clock_enable(COM_TX_GPIO_CLK[uart_id]);
	}else{
		//TX和RX不是同一组GPIO的时钟
		rcu_periph_clock_enable(COM_TX_GPIO_CLK[uart_id]);
		rcu_periph_clock_enable(COM_RX_GPIO_CLK[uart_id]);
	}

    //使能串口时钟
    rcu_periph_clock_enable(COM_CLK[uart_id]);

	//配置GPIO复用为串口TX
    gpio_af_set(COM_TX_GPIO_PORT[uart_id], COM_AF[uart_id], COM_TX_PIN[uart_id]);

    //配置GPIO复用为串口RX
	gpio_af_set(COM_RX_GPIO_PORT[uart_id], COM_AF[uart_id], COM_RX_PIN[uart_id]);

    //配置TX引脚模式,上下拉,速率等
    gpio_mode_set(COM_TX_GPIO_PORT[uart_id], GPIO_MODE_AF, GPIO_PUPD_PULLUP,COM_TX_PIN[uart_id]);
    gpio_output_options_set(COM_TX_GPIO_PORT[uart_id], GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ,COM_TX_PIN[uart_id]);

    //配置RX引脚模式,上下拉,速率等
    gpio_mode_set(COM_RX_GPIO_PORT[uart_id], GPIO_MODE_AF, GPIO_PUPD_PULLUP,COM_RX_PIN[uart_id]);
    gpio_output_options_set(COM_RX_GPIO_PORT[uart_id], GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ,COM_RX_PIN[uart_id]);
		
    //配置串口相关参数
    usart_deinit(COM_NUM[uart_id]);
    usart_baudrate_set(COM_NUM[uart_id], baudval);
    usart_receive_config(COM_NUM[uart_id], USART_RECEIVE_ENABLE);
    usart_transmit_config(COM_NUM[uart_id], USART_TRANSMIT_ENABLE);
    usart_enable(COM_NUM[uart_id]);
		
	//设置串口中断优先级
	nvic_irq_enable(COM_IRQn[uart_id], nvic_irq_pre_priority, nvic_irq_sub_priority);
		
	//根据入参,选择是否使用DMA 
	if(dma_rx_stu == DMA_RX_EB){
		// 接收超时设置,100个波特率的比特位(10个字节的超时中断触发)
		usart_receiver_timeout_threshold_config(COM_NUM[uart_id], 100);
		//使用DMA收数的话,要使能串口接收超时中断
		//usart_interrupt_enable(COM_NUM[uart_id], USART_INT_RT);
			
		usart_interrupt_enable(COM_NUM[uart_id], USART_INT_IDLE);
		//使能串口超时功能
		usart_receiver_timeout_enable(COM_NUM[uart_id]);
		//初始化串口接收DMA功能
		usart_dma_rx_init(uart_id);
		//将对应串口接收使用DMA的情况进行更新,方便别的地方使用
		uart_rx_dma_en_stu[uart_id] = DMA_RX_EB;
	}else{
		//不使用DMA收数时,要使能串口非空中断
		usart_interrupt_enable(COM_NUM[uart_id], USART_INT_RBNE);
		//将对应串口接收使用DMA的情况进行更新,方便别的地方使用
		uart_rx_dma_en_stu[uart_id] = DMA_RX_NEB;
	}
		
	if(dma_tx_stu == DMA_TX_EB){
		//初始化串口发送DMA功能
		usart_dma_tx_init(uart_id);
	}
}


/**
  *功能:串口数据发送函数
  *入参1:串口号
  *入参2:要发送的字节数据
  */
void uart_send_byte(uint8_t uart_id, uint8_t ch)
{
	//串口发送数据
	curr_send_uart_addr = COM_NUM[uart_id];
	usart_data_transmit(COM_NUM[uart_id], ch);
	//增加定时器计时,超过一秒没有发送完,清除TC标志位,跳出函数
	timer_enable(TIMER1);
	while(usart_flag_get(COM_NUM[uart_id], USART_FLAG_TBE) == RESET);
	timer_disable(TIMER1);
	timer_counter_value_config(TIMER1, 0);
}

/**
  *功能:串口buf数据发送函数
  *入参1:串口号
  *入参2:要发送的字节数据
  */
void uart_send_buf(uint8_t uart_id, uint8_t *ch, uint16_t len)
{
	for(uint32_t i = 0; i < len; i++){
		uart_send_byte(uart_id, *(ch + i));
	}
}


/**
  *功能:printf函数重映射
  */
int fputc(int ch, FILE *f)
{
  while(usart_flag_get(COM1, USART_FLAG_TBE) == RESET ){;}
  usart_data_transmit(COM1, (uint8_t)ch);
  return ch;
}

//↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓DMA串口收发↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓//
/**
  *功能:初始化串口DMA tx配置
  *入参1:串口号
  *返回值:是否为复位指令
  */
void usart_dma_tx_init(uint8_t uart_id)
{
	dma_single_data_parameter_struct dma_init_struct;

    /* enable dma_addr */
    rcu_periph_clock_enable(COM_DMA_RCU[uart_id]);

    /* deinitialize DMA channe7(USART0 TX) */
    dma_deinit(COM_DMA_ADDR[uart_id], COM_DMA_TX_CHANNEL[uart_id]);
    dma_init_struct.direction = DMA_MEMORY_TO_PERIPH;					//存储器到外设方向
	dma_init_struct.memory0_addr = (uint32_t)uart_dma_state_ptr[uart_id]->uart_tx_buffer;	//存储器地址
    dma_init_struct.memory_inc = DMA_MEMORY_INCREASE_ENABLE;	//存储器地址自增
    dma_init_struct.number = 0;																//传输数据个数,赋值BUFFER_SIZE时,上电初始化DMA会发送一堆0; 
    dma_init_struct.periph_addr = COM_DATA_ADDR[uart_id];			//外设基地址-数据寄存器
    dma_init_struct.periph_inc = DMA_PERIPH_INCREASE_DISABLE;	//外设地址自增关闭-需要固定
    dma_init_struct.periph_memory_width = DMA_PERIPH_WIDTH_8BIT;	//外设数据位宽8bit
    dma_init_struct.priority = DMA_PRIORITY_ULTRA_HIGH;						//设置优先级最高
    dma_single_data_mode_init(COM_DMA_ADDR[uart_id], COM_DMA_TX_CHANNEL[uart_id], &dma_init_struct);		//初始化对应DMA的对应通道
    dma_channel_subperipheral_select(COM_DMA_ADDR[uart_id], COM_DMA_TX_CHANNEL[uart_id], COM_DMA_SUBPERI[uart_id]);	//使能总得通道
    /* configure DMA mode */
    dma_circulation_disable(COM_DMA_ADDR[uart_id], COM_DMA_TX_CHANNEL[uart_id]);			//关闭DMA循环模式配置
	usart_dma_transmit_config(COM_NUM[uart_id], USART_DENT_ENABLE);			//DMA串口发送配置
		
	/* 使能 DMA 通道 */
    dma_channel_enable(COM_DMA_ADDR[uart_id], COM_DMA_TX_CHANNEL[uart_id]);
}


/**
  *功能:初始化串口DMA rx配置
  *入参1:串口号
  *返回值:是否为复位指令
  */
void usart_dma_rx_init(uint8_t uart_id){
			
	dma_single_data_parameter_struct dma_init_struct;
	
    /* enable dma_addr */
    rcu_periph_clock_enable(COM_DMA_RCU[uart_id]);

    /* deinitialize DMA channe7(USART0 TX) */
    dma_deinit(COM_DMA_ADDR[uart_id], COM_DMA_RX_CHANNEL[uart_id]);
    dma_init_struct.direction = DMA_PERIPH_TO_MEMORY;					//外设到存储器
	dma_init_struct.memory0_addr = (uint32_t)uart_dma_state_ptr[uart_id]->uart_rx_buffer.Ring_Buff;	//存储器地址
    dma_init_struct.memory_inc = DMA_MEMORY_INCREASE_ENABLE;	//存储器地址自增
    dma_init_struct.number = RINGBUFF_LEN; 										//传输数据个数
    dma_init_struct.periph_addr = COM_DATA_ADDR[uart_id];							//外设基地址-数据寄存器
    dma_init_struct.periph_inc = DMA_PERIPH_INCREASE_DISABLE;	//外设地址自增关闭-需要固定
    dma_init_struct.periph_memory_width = DMA_PERIPH_WIDTH_8BIT;	//外设数据位宽8bit
    dma_init_struct.priority = DMA_PRIORITY_ULTRA_HIGH;						//设置优先级最高
    dma_single_data_mode_init(COM_DMA_ADDR[uart_id], COM_DMA_RX_CHANNEL[uart_id], &dma_init_struct);		//初始化对应DMA的对应通道
    dma_channel_subperipheral_select(COM_DMA_ADDR[uart_id], COM_DMA_RX_CHANNEL[uart_id], COM_DMA_SUBPERI[uart_id]);	//使能总得通道
    /* configure DMA mode */
	dma_circulation_disable(COM_DMA_ADDR[uart_id], COM_DMA_RX_CHANNEL[uart_id]);			//关闭DMA循环模式配置
	  //dma_circulation_enable(COM_DMA_ADDR[uart_id], COM_DMA_RX_CHANNEL[uart_id]);			//关闭DMA循环模式配置
		
	usart_dma_receive_config(COM_NUM[uart_id], USART_DENR_ENABLE);			//DMA串口接收配置
	
    /* 使能 DMA 通道 */
    dma_channel_enable(COM_DMA_ADDR[uart_id], COM_DMA_RX_CHANNEL[uart_id]);
}
		

/**
  *功能:DMA串口发送数据
  *入参1:存储数据的buffer指针
  *入参2:要发送的数据长度
  *返回值:是否为复位指令
  */
void uart_dma_send(uint8_t uart_id, uint8_t *tx_buffer, uint32_t len) 
{	
	dma_channel_disable(COM_DMA_ADDR[uart_id], COM_DMA_TX_CHANNEL[uart_id]);
	dma_flag_clear(COM_DMA_ADDR[uart_id], COM_DMA_TX_CHANNEL[uart_id], DMA_FLAG_FTF);  // 清除DMA传输完成标志位
	dma_memory_address_config(COM_DMA_ADDR[uart_id], COM_DMA_TX_CHANNEL[uart_id], DMA_MEMORY_0, (uint32_t)(tx_buffer)); // 存储器地址
	dma_transfer_number_config(COM_DMA_ADDR[uart_id], COM_DMA_TX_CHANNEL[uart_id], len);
	dma_channel_enable(COM_DMA_ADDR[uart_id], COM_DMA_TX_CHANNEL[uart_id]);  // 使能DMA传输

	// 等待传输完成
	//增加定时器计时,超过一秒没有发送完,清除TC标志位,跳出函数
	timer_enable(TIMER1);
	while(dma_flag_get(COM_DMA_ADDR[uart_id], COM_DMA_TX_CHANNEL[uart_id], DMA_FLAG_FTF) == RESET);
	timer_disable(TIMER1);
	timer_counter_value_config(TIMER1, 0);
}


/**
  *功能:获取DMA接收数据的长度
  *入参1:串口号
  *返回值:当前DMA接收数据的长度
  */
unsigned int get_uart_dma_recv_data_size(uint8_t uart_id)
{
    /*
    dma_transfer_number_get(DMA_CH2);是获取当前指针计数值,
    用内存缓冲区大小 - 此计数值 = 接收到的数据长度(这里单位为字节)。
    需要说明下在读取数据长度的时候需要先把接收DMA关闭,读取完了或者是数据处理完了在打开接收DMA,防止在处理的过程中有数据到来而出错。
    */
    return (RINGBUFF_LEN - (dma_transfer_number_get(COM_DMA_ADDR[uart_id], COM_DMA_RX_CHANNEL[uart_id])));
}


/**
  *功能:重新配置DMA rx
  *入参1:串口号
  *返回值:是否为复位指令
  */
void uart_dma_rx_refcg(uint8_t uart_id)
{
	//关闭DMA,在没有读取该接收帧数据之前禁止DMA再接收数据
	dma_channel_disable(COM_DMA_ADDR[uart_id], COM_DMA_RX_CHANNEL[uart_id]); 
	dma_memory_address_config(COM_DMA_ADDR[uart_id], COM_DMA_RX_CHANNEL[uart_id], DMA_MEMORY_0, 
	(uint32_t)(uart_dma_state_ptr[uart_id]->uart_rx_buffer.Ring_Buff)); // 存储器地址
	dma_transfer_number_config(COM_DMA_ADDR[uart_id], COM_DMA_RX_CHANNEL[uart_id], RINGBUFF_LEN);
	
    uart_dma_state_ptr[uart_id]->uart_dma_rx_state = 0;
	uart_dma_state_ptr[uart_id]->uart_dma_rx_len = 0;
    
	// 使能通道
	dma_channel_enable(COM_DMA_ADDR[uart_id], COM_DMA_RX_CHANNEL[uart_id]);
}


/**
  *功能:初始化串口相关状态、DMA、buffer等
  *入参1:串口号
  */
void init_uart_state(uint8_t uart_id){
	memset(uart_dma_state_ptr[uart_id], 0, sizeof(uart_state));
}

3. 中断处理函数:"gd32f4xx_it.c",主要实现了串口接收中断以及定时器中断

(1)extern相关的一些变量,均为Uart_hdl.c中定义的一些串口配置相关的数组,用于方便在中断处理函数中操作;

(2)串口0、1、2、5为USART,判断的中断标志为超时中断,即USART_INT_FLAG_RT(当然也可以使用空闲中断USART_INT_FLAG_IDLE);串口3、4、6、7为UART,不支持超时中断,所以判断的是空闲中断标志,即USART_INT_FLAG_IDLE;

(3)每个串口中断函数中,还分为了两种情况,一种是使用DMA接收数据,一种是不使用DMA直接触发串口接收中断来接收数据;其中使用DMA的方式接收数据时,是在中断函数种将接收数据标志位置位,具体接收数据已经自动存储到DMA初始化时绑定的接收buffer中,而直接串口中断接收的数据,每次中断接收一个字节数据,直接在中断函数调用Write_RingBuff方法中存储到环形buffer中;

(4)timer1中断,触发中断来将发送完成标志位置位,防止串口发发送过程中出现以外卡死的问题;

/*!
    \file    gd32f4xx_it.c
    \brief   interrupt service routines
    \version 2023-05-10, V1.0.0
		\author  tbj
*/

#include "gd32f4xx_it.h"
#include "task_hdl.h"


//*******************************串口中断相关*****************************//
//串口DMA状态结构体,包含接收数据状态,buffer等
extern uart_state *uart_dma_state_ptr;
//DMA基地址
extern uint32_t COM_DMA_ADDR[COMn];
//DMA TX通道
extern  dma_channel_enum COM_DMA_TX_CHANNEL[COMn];
//DMA RX通道
extern  dma_channel_enum COM_DMA_RX_CHANNEL[COMn];
//串口是否使用DMA通道收数据,用于串口中断函数判断使用
extern  UART_DMA_RX_STU uart_rx_dma_en_stu[COMn];
//存储当前发送数据的串口基地址
extern  uint32_t curr_send_uart_addr;


/*!
    \brief      this function handles NMI exception
    \param[in]  none
    \param[out] none
    \retval     none
*/
void NMI_Handler(void)
{
}

/*!
    \brief      this function handles HardFault exception
    \param[in]  none
    \param[out] none
    \retval     none
*/
void HardFault_Handler(void)
{
    /* if Hard Fault exception occurs, go to infinite loop */
    while (1){
    }
}

/*!
    \brief      this function handles MemManage exception
    \param[in]  none
    \param[out] none
    \retval     none
*/
void MemManage_Handler(void)
{
    /* if Memory Manage exception occurs, go to infinite loop */
    while (1){
    }
}

/*!
    \brief      this function handles BusFault exception
    \param[in]  none
    \param[out] none
    \retval     none
*/
void BusFault_Handler(void)
{
    /* if Bus Fault exception occurs, go to infinite loop */
    while (1){
    }
}

/*!
    \brief      this function handles UsageFault exception
    \param[in]  none
    \param[out] none
    \retval     none
*/
void UsageFault_Handler(void)
{
    /* if Usage Fault exception occurs, go to infinite loop */
    while (1){
    }
}

/*!
    \brief      this function handles SVC exception
    \param[in]  none
    \param[out] none
    \retval     none
*/
void SVC_Handler(void)
{
}

/*!
    \brief      this function handles DebugMon exception
    \param[in]  none
    \param[out] none
    \retval     none
*/
void DebugMon_Handler(void)
{
}

/*!
    \brief      this function handles PendSV exception
    \param[in]  none
    \param[out] none
    \retval     none
*/
void PendSV_Handler(void)
{
}

/*!
    \brief      this function handles SysTick exception
    \param[in]  none
    \param[out] none
    \retval     none
*/
void SysTick_Handler(void)
{
	//延时计数使用
  delay_decrement();
	//系统开机计时
	add_system_time();
	//任务处理状态监测
	TaskTick();
}


/**
  *功能:串口0中断接收函数
  */
void USART0_IRQHandler(void)
{
	//判断是使用DMA收数还是正常收数
	if(uart_rx_dma_en_stu[0] == DMA_RX_EB){
		/* UART接收超时中断 */
		if ((usart_interrupt_flag_get(USART0, USART_INT_FLAG_RT) != RESET) &&
	        (usart_flag_get(USART0, USART_FLAG_RT) != RESET))
		{
			//关闭DMA,在没有读取该接收帧数据之前禁止DMA再接收数据
			dma_channel_disable(COM_DMA_ADDR[0], COM_DMA_RX_CHANNEL[0]);	
			//清除DMA传输完成标志位 
			dma_flag_clear(COM_DMA_ADDR[0], COM_DMA_RX_CHANNEL[0], DMA_FLAG_FTF);   

			/* Clear receiver timeout flag */
			//usart_flag_clear(BLE_UART, USART_FLAG_RT);
			usart_interrupt_flag_clear(USART0,USART_INT_FLAG_RT);
			usart_data_receive(USART0); /* 清除接收完成标志位 */
			
			// 设置接收的数据长度
			uart_dma_state_ptr[0].uart_dma_rx_len = get_uart_dma_recv_data_size(0);
			
			/* 接收超时后,说明一帧数据接收完毕,置接收完成标志 */
			uart_dma_state_ptr[0].uart_dma_rx_state = 1;
		}
	}else{
		if( usart_interrupt_flag_get(USART0,USART_INT_FLAG_RBNE) != RESET)
		{
			uint8_t data;
			usart_interrupt_flag_clear(USART0, USART_INT_FLAG_RBNE);
			data = usart_data_receive(USART0);
			Write_RingBuff(&data, 1, &uart_dma_state_ptr[0].uart_rx_buffer);
			
		}
	}
}

/**
  *功能:串口1中断接收函数
  */
void USART1_IRQHandler(void)
{
	//判断是使用DMA收数还是正常收数
	if(uart_rx_dma_en_stu[1] == DMA_RX_EB){
		/* UART接收超时中断 */
		if ((usart_interrupt_flag_get(USART1, USART_INT_FLAG_RT) != RESET) &&
	        (usart_flag_get(USART1, USART_FLAG_RT) != RESET))
		{
			//关闭DMA,在没有读取该接收帧数据之前禁止DMA再接收数据
			dma_channel_disable(COM_DMA_ADDR[1], COM_DMA_RX_CHANNEL[1]);	
			//清除DMA传输完成标志位 
			dma_flag_clear(COM_DMA_ADDR[1], COM_DMA_RX_CHANNEL[1], DMA_FLAG_FTF);   

			/* Clear receiver timeout flag */
			//usart_flag_clear(BLE_UART, USART_FLAG_RT);
			usart_interrupt_flag_clear(USART1,USART_INT_FLAG_RT);
			usart_data_receive(USART1); /* 清除接收完成标志位 */

			// 设置接收的数据长度
			uart_dma_state_ptr[1].uart_dma_rx_len = get_uart_dma_recv_data_size(1);

			/* 接收超时后,说明一帧数据接收完毕,置接收完成标志 */
			uart_dma_state_ptr[1].uart_dma_rx_state = 1;
		}
	}else{
		if( usart_interrupt_flag_get(USART1,USART_INT_FLAG_RBNE) != RESET)
		{
			uint8_t data;
			usart_interrupt_flag_clear(USART1, USART_INT_FLAG_RBNE);
			data = usart_data_receive(USART1);
			Write_RingBuff(&data, 1, &uart_dma_state_ptr[1].uart_rx_buffer);
		}
	}
}


/**
  *功能:串口2中断接收函数
  */
void USART2_IRQHandler(void)
{
	//判断是使用DMA收数还是正常收数
	if(uart_rx_dma_en_stu[2] == DMA_RX_EB){
		/* UART接收超时中断 */
		if ((usart_interrupt_flag_get(USART2, USART_INT_FLAG_RT) != RESET) &&
	        (usart_flag_get(USART2, USART_FLAG_RT) != RESET))
		{
			//关闭DMA,在没有读取该接收帧数据之前禁止DMA再接收数据
			dma_channel_disable(COM_DMA_ADDR[2], COM_DMA_RX_CHANNEL[2]);	
			//清除DMA传输完成标志位 
			dma_flag_clear(COM_DMA_ADDR[2], COM_DMA_RX_CHANNEL[2], DMA_FLAG_FTF);   

			/* Clear receiver timeout flag */
			//usart_flag_clear(BLE_UART, USART_FLAG_RT);
			usart_interrupt_flag_clear(USART2,USART_INT_FLAG_RT);
			usart_data_receive(USART2); /* 清除接收完成标志位 */

			// 设置接收的数据长度
			uart_dma_state_ptr[2].uart_dma_rx_len = get_uart_dma_recv_data_size(2);

			/* 接收超时后,说明一帧数据接收完毕,置接收完成标志 */
			uart_dma_state_ptr[2].uart_dma_rx_state = 1;
		}
	}else{
		if( usart_interrupt_flag_get(USART2,USART_INT_FLAG_RBNE) != RESET)
		{
			uint8_t data;
			usart_interrupt_flag_clear(USART2, USART_INT_FLAG_RBNE);
			data = usart_data_receive(USART2);
			Write_RingBuff(&data, 1, &uart_dma_state_ptr[2].uart_rx_buffer);
		}
	}
}


/**
  *功能:串口3中断接收函数
  */
void UART3_IRQHandler(void)
{
	//判断是使用DMA收数还是正常收数
	if(uart_rx_dma_en_stu[3] == DMA_RX_EB){
		/* UART接收超时中断 */
		if ((usart_interrupt_flag_get(UART3, USART_INT_FLAG_IDLE) != RESET))
		{
			//关闭DMA,在没有读取该接收帧数据之前禁止DMA再接收数据
			dma_channel_disable(COM_DMA_ADDR[3], COM_DMA_RX_CHANNEL[3]);	
			//清除DMA传输完成标志位 
			dma_flag_clear(COM_DMA_ADDR[3], COM_DMA_RX_CHANNEL[3], DMA_FLAG_FTF);   

			//清除空闲中断标志位
			usart_data_receive(UART3); 

			// 设置接收的数据长度
			uart_dma_state_ptr[3].uart_dma_rx_len = get_uart_dma_recv_data_size(3);

			/* 接收超时后,说明一帧数据接收完毕,置接收完成标志 */
			uart_dma_state_ptr[3].uart_dma_rx_state = 1;
		}
	}else{
		if( usart_interrupt_flag_get(UART3,USART_INT_FLAG_RBNE) != RESET)
		{
			uint8_t data;
			usart_interrupt_flag_clear(UART3, USART_INT_FLAG_RBNE);
			data = usart_data_receive(UART3);
			Write_RingBuff(&data, 1, &uart_dma_state_ptr[3].uart_rx_buffer);
		}
	}
}


/**
  *功能:串口4中断接收函数
  */
void UART4_IRQHandler(void)
{
	//判断是使用DMA收数还是正常收数
	if(uart_rx_dma_en_stu[4] == DMA_RX_EB){
		/* UART接收超时中断 */
		if ((usart_interrupt_flag_get(UART4, USART_INT_FLAG_IDLE) != RESET))
		{
			//关闭DMA,在没有读取该接收帧数据之前禁止DMA再接收数据
			dma_channel_disable(COM_DMA_ADDR[4], COM_DMA_RX_CHANNEL[4]);	
			//清除DMA传输完成标志位 
			dma_flag_clear(COM_DMA_ADDR[4], COM_DMA_RX_CHANNEL[4], DMA_FLAG_FTF);   

			//清除空闲中断标志位
			usart_data_receive(UART4); 

			// 设置接收的数据长度
			uart_dma_state_ptr[4].uart_dma_rx_len = get_uart_dma_recv_data_size(4);

			/* 接收超时后,说明一帧数据接收完毕,置接收完成标志 */
			uart_dma_state_ptr[4].uart_dma_rx_state = 1;
		}
	}else{
		if( usart_interrupt_flag_get(UART4,USART_INT_FLAG_RBNE) != RESET)
		{
			uint8_t data;
			usart_interrupt_flag_clear(UART4, USART_INT_FLAG_RBNE);
			data = usart_data_receive(UART4);
			Write_RingBuff(&data, 1, &uart_dma_state_ptr[4].uart_rx_buffer);
		}
	}
}


/**
  *功能:串口5中断接收函数
  */
void USART5_IRQHandler(void)
{
	//判断是使用DMA收数还是正常收数
	if(uart_rx_dma_en_stu[5] == DMA_RX_EB){
		/* UART接收超时中断 */
		if ((usart_interrupt_flag_get(USART5, USART_INT_FLAG_RT) != RESET) &&
	        (usart_flag_get(USART5, USART_FLAG_RT) != RESET))
		{
			//关闭DMA,在没有读取该接收帧数据之前禁止DMA再接收数据
			dma_channel_disable(COM_DMA_ADDR[5], COM_DMA_RX_CHANNEL[5]);	
			//清除DMA传输完成标志位 
			dma_flag_clear(COM_DMA_ADDR[5], COM_DMA_RX_CHANNEL[5], DMA_FLAG_FTF);   

			/* Clear receiver timeout flag */
			//usart_flag_clear(BLE_UART, USART_FLAG_RT);
			usart_interrupt_flag_clear(USART5,USART_INT_FLAG_RT);
			usart_data_receive(USART5); /* 清除接收完成标志位 */

			// 设置接收的数据长度
			uart_dma_state_ptr[5].uart_dma_rx_len = get_uart_dma_recv_data_size(5);

			/* 接收超时后,说明一帧数据接收完毕,置接收完成标志 */
			uart_dma_state_ptr[5].uart_dma_rx_state = 1;
		}
	}else{
		if( usart_interrupt_flag_get(USART5,USART_INT_FLAG_RBNE) != RESET)
		{
			uint8_t data;
			usart_interrupt_flag_clear(USART5, USART_INT_FLAG_RBNE);
			data = usart_data_receive(USART5);
			Write_RingBuff(&data, 1, &uart_dma_state_ptr[5].uart_rx_buffer);
		}
	}
}


/**
  *功能:串口6中断接收函数
  */
void UART6_IRQHandler(void)
{
	//判断是使用DMA收数还是正常收数
	if(uart_rx_dma_en_stu[6] == DMA_RX_EB){
		/* UART接收超时中断 */
		if ((usart_interrupt_flag_get(UART6, USART_INT_FLAG_IDLE) != RESET))
		{
			//关闭DMA,在没有读取该接收帧数据之前禁止DMA再接收数据
			dma_channel_disable(COM_DMA_ADDR[6], COM_DMA_RX_CHANNEL[6]);	
			//清除DMA传输完成标志位 
			dma_flag_clear(COM_DMA_ADDR[6], COM_DMA_RX_CHANNEL[6], DMA_FLAG_FTF);   

			//清除空闲中断标志位
			usart_data_receive(UART6); 

			// 设置接收的数据长度
			uart_dma_state_ptr[6].uart_dma_rx_len = get_uart_dma_recv_data_size(6);

			/* 接收超时后,说明一帧数据接收完毕,置接收完成标志 */
			uart_dma_state_ptr[6].uart_dma_rx_state = 1;
		}
	}else{
		if( usart_interrupt_flag_get(UART6,USART_INT_FLAG_RBNE) != RESET)
		{
			uint8_t data;
			usart_interrupt_flag_clear(UART6, USART_INT_FLAG_RBNE);
			data = usart_data_receive(UART6);
			Write_RingBuff(&data, 1, &uart_dma_state_ptr[6].uart_rx_buffer);
		}
	}
}


/**
  *功能:串口7中断接收函数
  */
void UART7_IRQHandler(void)
{
	//判断是使用DMA收数还是正常收数
	if(uart_rx_dma_en_stu[7] == DMA_RX_EB){
		/* UART接收超时中断 */
		if ((usart_interrupt_flag_get(UART7, USART_INT_FLAG_IDLE) != RESET))
		{
			//关闭DMA,在没有读取该接收帧数据之前禁止DMA再接收数据
			dma_channel_disable(COM_DMA_ADDR[7], COM_DMA_RX_CHANNEL[7]);	
			//清除DMA传输完成标志位 
			dma_flag_clear(COM_DMA_ADDR[7], COM_DMA_RX_CHANNEL[7], DMA_FLAG_FTF);   

			//清除空闲中断标志位
			usart_data_receive(UART7); 

			// 设置接收的数据长度
			uart_dma_state_ptr[7].uart_dma_rx_len = get_uart_dma_recv_data_size(7);

			/* 接收超时后,说明一帧数据接收完毕,置接收完成标志 */
			uart_dma_state_ptr[7].uart_dma_rx_state = 1;
		}
	}else{
		if( usart_interrupt_flag_get(UART7,USART_INT_FLAG_RBNE) != RESET)
		{
			uint8_t data;
			usart_interrupt_flag_clear(UART7, USART_INT_FLAG_RBNE);
			data = usart_data_receive(UART7);
			Write_RingBuff(&data, 1, &uart_dma_state_ptr[7].uart_rx_buffer);
		}
	}
}


/**
  *功能:timer1中断函数
  */
void TIMER1_IRQHandler(void)
{
  if ( timer_interrupt_flag_get(TIMER1 , TIMER_INT_UP) != RESET ) 
  {
    timer_interrupt_flag_clear(TIMER1 , TIMER_INT_UP);
		usart_flag_clear(curr_send_uart_addr, USART_FLAG_TC);
		timer_disable(TIMER1);
  }
}

4. data_hdl.c文件中的方法将串口中断中接收的数据提取出来,并进行操作,例如转发,或是进一步进行数据帧格式的解析

(1)extern串口状态和DMA相关变量,用于方便这里操作和状态读取,同时定义提取数据后存放的buffer、前后两段时间接收数据的长度,用于判断数据是否接收完毕;

(2)写入和读取环形buffer数据的方

(3)在使用和不使用DMA的两种情况下,通过前后两次接收数据的长度是否还有变化,判断数据接收是否完毕,接收完毕的话,进行下一步的帧数据的解析或者是转发,没有接收完毕的话,等待下次的接收在进行判断;

/*!
    \file    data_hdl.c
    \brief   data_hdl arm project
    \version 2024-01-09, V1.0.0
		\author  tbj
*/

#include "data_hdl.h"

//串口DMA状态结构体,包含接收数据状态,buffer等
extern uart_state *uart_dma_state_ptr;
//串口是否使用DMA通道收数据,用于串口中断函数判断使用
extern  UART_DMA_RX_STU uart_rx_dma_en_stu[COMn];
//存放提取帧格式后的串口接收数据
uint8_t extc_data_buff[RINGBUFF_LEN] = {0};
//当前任务时刻串口接收到的数据长度-对应8个串口
uint32_t curr_recv_count[8] = {0};
//上一任务时刻串口接收到的数据长度-对应8个串口
uint32_t last_recv_count[8] = {0};

/**
  *功能:初始化环形buffer
  *入参1:buffer指针
  */
void RingBuff_Init(RingBuff_t *ringBuff)
{
	//初始化相关信息
	ringBuff->Head = 0;
	ringBuff->Tail = 0;
	ringBuff->Length = 0;
}


/**
  *功能:数据写入环形缓冲区
  *入参1:要写入的数据
  *入参2:buffer指针
  *返回值:buffer是否已满
  */
uint8_t Write_RingBuff(uint8_t *data, uint16_t dataLen, RingBuff_t *ringBuff)
{
	if(ringBuff->Length >= RINGBUFF_LEN) //判断缓冲区是否已满
	{
		//如果buffer爆掉了,清空buffer,进行重新初始化
		memset(ringBuff, 0, RINGBUFF_LEN);
		RingBuff_Init(ringBuff);
		return 1;
	}
   
	if(ringBuff->Tail + dataLen > RINGBUFF_LEN){
		memcpy(ringBuff->Ring_Buff + ringBuff->Tail, data, RINGBUFF_LEN - ringBuff->Tail);
		memcpy(ringBuff->Ring_Buff, data + RINGBUFF_LEN - ringBuff->Tail, dataLen - (RINGBUFF_LEN - ringBuff->Tail));
	}else{
		memcpy(ringBuff->Ring_Buff + ringBuff->Tail, data, dataLen);
	}
	
	ringBuff->Tail = ( ringBuff->Tail + dataLen ) % RINGBUFF_LEN;//防止越界非法访问
	ringBuff->Length = ringBuff->Length + dataLen;

	return 0;
}


/**
  *功能:读取缓存区整帧数据
  *入参1:存放提取数据的指针
  *入参2:环形区buffer指针
  *返回值:是否成功提取数据
  */
uint8_t Read_RingBuff(uint8_t *rData, RingBuff_t *ringBuff)
{
	if(ringBuff->Length == 0)//判断非空
	{
		return 1;
	}
	
	if(ringBuff->Head < ringBuff->Tail){
		memcpy(rData, ringBuff->Ring_Buff + ringBuff->Head, ringBuff->Length);
		memset(ringBuff->Ring_Buff + ringBuff->Head, 0, ringBuff->Length);
	}else{
		memcpy(rData, ringBuff->Ring_Buff + ringBuff->Head, RINGBUFF_LEN - ringBuff->Head);
		memcpy(rData + RINGBUFF_LEN - ringBuff->Head, ringBuff->Ring_Buff, ringBuff->Length - (RINGBUFF_LEN - ringBuff->Head));
		memset(ringBuff->Ring_Buff + ringBuff->Head, 0, RINGBUFF_LEN - ringBuff->Head);
		memset(ringBuff->Ring_Buff, 0, ringBuff->Length - (RINGBUFF_LEN - ringBuff->Head));
	}

	ringBuff->Head = (ringBuff->Head + ringBuff->Length) % RINGBUFF_LEN;//防止越界非法访问
	ringBuff->Length = 0;
	
	return 0;
}


/**
  *功能:提取串口中断接收的数据到缓存区,进行下一步操作
  *入参1:串口号
  *返回值:只用于没接收完时的函数结束返回,无特定意义
  */
int uart_recv_data_hdl(uint8_t uart_id){
	
	//串口使用DMA
	if(uart_rx_dma_en_stu[uart_id] == DMA_RX_EB){
		//记录上次接收数据长度
		last_recv_count[uart_id] = curr_recv_count[uart_id];	
		//记录当前接收数据的长度,由于使用DMA超时中断,只有发生超时中断时才会有接收数据长度,否则长度一直是0
		curr_recv_count[uart_id] = uart_dma_state_ptr[uart_id].uart_dma_rx_len;	
		if(last_recv_count[uart_id] == curr_recv_count[uart_id] && curr_recv_count[uart_id] != 0){	//接收完毕
			//将数据从串口接收buf中提取出来
			memset(extc_data_buff, 0, RINGBUFF_LEN);
			memcpy(extc_data_buff, uart_dma_state_ptr[uart_id].uart_rx_buffer.Ring_Buff, curr_recv_count[uart_id]);
			//重置串口DMA接收中断配置,重新进行接收
			uart_dma_rx_refcg(uart_id);
			//判断提取数据的帧格式,根据串口号,判断是提取十六进制还是字符串
			if(uart_id == 0 || uart_id == 1 || uart_id == 2 || uart_id == 3 || uart_id == 5 || uart_id == 7){
				//*************To Do Something**************//
			}else if(uart_id == 6){
				//*************To Do Something**************//
			}else if(uart_id == 4){
				//*************To Do Something**************//
			}else{
				return 0;
			}
			//清空记录接收数据长度的字段
			curr_recv_count[uart_id] = 0;
			last_recv_count[uart_id] = 0;
		}else{	//还在接收
			return 0;
		}
	}else{	//串口不使用DMA
		last_recv_count[uart_id] = curr_recv_count[uart_id];
		curr_recv_count[uart_id] = uart_dma_state_ptr[uart_id].uart_rx_buffer.Length;
		if(last_recv_count[uart_id] == curr_recv_count[uart_id] && curr_recv_count[uart_id] != 0){	//接收完毕
			memset(extc_data_buff, 0, RINGBUFF_LEN);
			Read_RingBuff(extc_data_buff, &uart_dma_state_ptr[uart_id].uart_rx_buffer);
			//判断提取数据的帧格式,根据串口号,判断是提取十六进制还是字符串
			if(uart_id == 0 || uart_id == 1 || uart_id == 2 || uart_id == 3 || uart_id == 5 || uart_id == 7){
				//*************To Do Something**************//
			}else if(uart_id == 6){
				//*************To Do Something**************//
			}else if(uart_id == 4){
				//*************To Do Something**************//
			}else{
				return 0;
			}
			curr_recv_count[uart_id] = 0;
			last_recv_count[uart_id] = 0;
		}else{	//还在接收
			return 0;
		}
	}
	return 0;
}


5. 串口应用

(1)先初始化串口相关的配置,包括GPIO复用、串口参数配置、DMA配置等

(2)循环执行串口接收数据处理函数来处理数据(如果出现串口接收数据不正常,建议新建一个任务,每隔几秒循环依次,将串口数据处理函数uart_recv_data_hdl放在任务中每隔几秒执行一次,因为while中循环执行过快,可能出现问题)

/*!
    \brief      main function
    \param[in]  none
    \param[out] none
    \retval     none
*/
int main(void)
{
	
	/* configure systick */
	systick_config();

	//防止串口发送卡死的定时器初始化
	timer1_config();

	//串口资源初始化
	uart_init(0, 460800U, 1, 2, DMA_TX_EB, DMA_RX_EB);
	uart_init(1, 460800U, 1, 1, DMA_TX_EB, DMA_RX_EB);
	uart_init(2, 460800U, 1, 1, DMA_TX_EB, DMA_RX_EB);
	uart_init(3, 115200U, 1, 2, DMA_TX_EB, DMA_RX_EB);
	uart_init(4, 115200U, 1, 1, DMA_TX_EB, DMA_RX_EB);
	uart_init(5, 115200U, 1, 2, DMA_TX_EB, DMA_RX_EB);
	uart_init(6, 460800U, 1, 0, DMA_TX_NEB, DMA_RX_NEB);
	uart_init(7, 460800U, 1, 1, DMA_TX_NEB, DMA_RX_NEB);

	
	while(1){	
	    //循环执行串口数据接收处理任务
	    uart_recv_data_hdl(0);
	    uart_recv_data_hdl(1);
	    uart_recv_data_hdl(2);
	    uart_recv_data_hdl(3);
	    uart_recv_data_hdl(4);
	    uart_recv_data_hdl(5);
	    uart_recv_data_hdl(6);
	    uart_recv_data_hdl(7);
    }
}

创作不易,希望大家点赞、收藏、关注哦!!!ヾ(o◕∀◕)ノ

  • 18
    点赞
  • 77
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 5
    评论
在使用STM32DMA方式进行串口数据收发时,可以使用空闲中断来判断数据接收完成。具体步骤如下: 1. 配置串口DMA模式,设置DMA通道和缓存地址等参数。 2. 启动DMA传输,使其开始接收数据。 3. 在空闲中断中判断DMA传输是否完成,可以通过检查DMA传输的剩余数据度来判断。如果剩余数据度为0,则说明数据传输完成。 4. 在空闲中断中处理接收到的数据,例如将数据存储到缓存中等操作。 5. 处理完接收到的数据后,重新配置DMA通道和缓存地址等参数,使其可以继续接收数据。 下面是一个简单的示例代码: ```c void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) { // 检查DMA传输是否完成 if (__HAL_DMA_GET_COUNTER(huart->hdmarx) == 0) { // 处理接收到的数据 // ... // 重新配置DMA通道和缓存地址等参数 HAL_UART_Receive_DMA(huart, rx_buffer, BUFFER_SIZE); } } int main() { // 初始化串口DMA通道等参数 HAL_UART_Receive_DMA(&huart, rx_buffer, BUFFER_SIZE); // 启用空闲中断 __HAL_UART_ENABLE_IT(&huart, UART_IT_IDLE); while (1) { // 主循环 // ... } } ``` 在以上示例代码中,我们先使用HAL_UART_Receive_DMA函数启动DMA传输,并在空闲中断中检查DMA传输是否完成。如果传输完成,则处理接收到的数据,并重新配置DMA通道和缓存地址等参数,使其可以继续接收数据。同时,我们也启用了空闲中断,以便能够及时检测到数据传输的完成。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

披着假发的程序唐

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值