当前位置: 首页 > article >正文

梁山派入门指南3——串口使用详解,包括串口发送数据、重定向、中断接收不定长数据、DMA+串口接收不定长数据,以及对应的bsp文件和使用示例

梁山派入门指南3——串口使用详解,包括串口发送数据、重定向、中断接收不定长数据、DMA+串口接收不定长数据,以及对应的bsp文件和使用示例

  • 1. 串口发送数据
    • 1.1 串口简介
    • 1.2 梁山派上的串口开发
    • 1.3 bsp_uart文件(只发送不接收,兼容串口0和串口1)
    • 1.4串口打印信息示例
  • 2. 串口接收数据(通过中断)
    • 2.1 串口中断简介
    • 2.2 梁山派上的串口中断开发
    • 2.3 bsp_uart文件(发送和中断接收,兼容串口0和串口1)
    • 1.4 中断接收数据示例
  • 3. 串口+中断+DMA接收
    • 3.1 DMA简介
    • 3.2 梁山派上的DMA开发
    • 3.3 bsp_uart文件(发送、中断接收和DMA接收二合一,兼容串口0和串口1)
    • 3.4 DMA接收不定长数据示例

上期我们介绍了梁山派中的:滴答定时器&位带操作&按键输入,这一期我们来调通串口

1. 串口发送数据

1.1 串口简介

串口是指外设和处理器之间通过数据信号线、地线和控制线等,按位进行传输数据的一种通讯方式。尽管传输速度比并行传输低。但串口可以在使用一根线发送数据的同时用另一根线接收数据。这种通信方式使用的数据线少,在远距离通信中可以节约通信成本。串口通信最重要的参数是波特率、数据位、停止位和奇偶校验位,这些参数在两个通信端口之间必须一致。

串口的具体内容这里不做介绍,可以参考:夜深人静学32系列11——串口通信 和 蓝桥杯单片机学习8——串口通信(UART的使用示例)。

下面直接介绍GD32上的串口开发流程:

1.2 梁山派上的串口开发

  1. 第一步:了解GD32F470ZGT6上的串口资源.

【注】:数据手册第67页
在这里插入图片描述

  • 可以看到梁山派上一共有八个串口,最大时钟频率为15MHz。
  • 串口0、1、2、5为USART(通用同步/异步收发器)
  • 串口3、4、6、7为UART(异步收发器)
  • 串口0和5挂载在APB2总线上
  • 其他串口挂载到APB1总线上

【注】:数据手册第10页
在这里插入图片描述

  1. 第二步:查询梁山派上串口相关的原理图
    在这里插入图片描述
    可以看到,梁山派上是默认使用PA9和PA10作为串口的发送和介绍引脚的(实际上大部分板子上都是这样的),了解了基本信息,我们就可以来查阅用户手册中串口相关的部分,开始配置串口。

  2. 第三步:配置串口

下图是梁山派用户手册中提供的具体配置流程:
在这里插入图片描述
有了这个之后,我们就可以根据手册进行一步一步的配置,或者自己查阅固件库手册,自行配置,不管哪种结果,下面是配置好的样子:

/* 这部分是宏定义,需要放到对应的bsp头文件中 */
/* 串口时钟定义 */
#define BSP_USART0_RCU   	RCU_USART0 
#define BSP_USART0_TX_RCU 	RCU_GPIOA
#define BSP_USART0_RX_RCU 	RCU_GPIOA

/* 串口引脚定义 */
#define BSP_USART0_TX_PORT 	GPIOA       	
#define BSP_USART0_TX_PIN  	GPIO_PIN_9 	

#define BSP_USART0_RX_PORT 	GPIOA       	
#define BSP_USART0_RX_PIN 	GPIO_PIN_10 	

/* GPIO复用串口功能定义 */
#define BSP_USART0_TX_AF 	   	GPIO_AF_7
#define BSP_USART0_RX_AF 	   	GPIO_AF_7

/* 定义串口句柄 */
#define BSP_USART0 USART0

/************************************************
函数名称 : uart_gpio_config
功    能 : 串口初始化函数,默认使用串口0
参    数 : band_rate:波特率
返 回 值 : 无
作    者 : 不想写代码的我
*************************************************/
void uart_gpio_config(uint32_t band_rate)
{
	/* 开启串口时钟和端口时钟 */
	rcu_periph_clock_enable(BSP_USART0_RCU);
	rcu_periph_clock_enable(BSP_USART0_TX_RCU);
	rcu_periph_clock_enable(BSP_USART0_RX_RCU);
	
	/* 配置GPIO的复用模式 */
	gpio_af_set(BSP_USART0_TX_PORT,BSP_USART0_TX_AF,BSP_USART0_TX_PIN);
	gpio_af_set(BSP_USART0_RX_PORT,BSP_USART0_RX_AF,BSP_USART0_RX_PIN);
	
	/* 配置GPIO模式:复用模式,上拉 */
	gpio_mode_set(BSP_USART0_TX_PORT,GPIO_MODE_AF,GPIO_PUPD_PULLUP,BSP_USART0_TX_PIN);
	gpio_mode_set(BSP_USART0_RX_PORT,GPIO_MODE_AF,GPIO_PUPD_PULLUP,BSP_USART0_RX_PIN);
	
	/* 配置GPIO的输出 */
	gpio_output_options_set(BSP_USART0_TX_PORT,GPIO_OTYPE_PP,GPIO_OSPEED_50MHZ,BSP_USART0_TX_PIN);
	gpio_output_options_set(BSP_USART0_RX_PORT,GPIO_OTYPE_PP,GPIO_OSPEED_50MHZ,BSP_USART0_RX_PIN);
	
	/* 配置串口波特率为输入参数,无校验、8位数据、1位停止位 */
	usart_deinit(BSP_USART0);
	usart_baudrate_set(BSP_USART0,band_rate);
	usart_parity_config(BSP_USART0,USART_PM_NONE);
	usart_word_length_set(BSP_USART0,USART_WL_8BIT);
	usart_stop_bit_set(BSP_USART0,USART_STB_1BIT);
	
	/* 使能串口和串口打印功能 */
	usart_enable(BSP_USART0);
	usart_transmit_config(BSP_USART0,USART_TRANSMIT_ENABLE);
}
  1. 完成了串口配置之后,我们就可以编写对应的功能函数,比如发送数据,发送字符串和串口重定向,就像下面这样:
/************************************************
函数名称 : usart_send_data
功    能 : 串口发送一个字节
参    数 : ucch:要发送的字节
返 回 值 : 无
作    者 : 不想写代码的我
*************************************************/
void usart_send_data(uint8_t ucch)
{
	usart_data_transmit(BSP_USART0,(uint8_t)ucch);							 // 发送数据
	while(RESET == usart_flag_get(BSP_USART0,USART_FLAG_TBE));  // 等待发送数据缓冲区标志置位
}


/************************************************
函数名称 : usart_send_String
功    能 : 串口发送字符串
参    数 : ucstr:要发送的字符串
返 回 值 : 无
作    者 : 不想写代码的我
*************************************************/
void usart_send_string(uint8_t *ucstr)
{
	while(ucstr && *ucstr)        // 地址为空或者值为空跳出
	{
	  usart_send_data(*ucstr++);  // 发送单个字符
	}
}

/************************************************
函数名称 : fputc
功    能 : 串口重定向函数
参    数 : 
返 回 值 : 
作    者 : 不想写代码的我
*************************************************/
int fputc(int ch, FILE *f)
{
     usart_send_data(ch);
     // 等待发送数据缓冲区标志置位
     return ch;
}

完成以上四步,你就可以愉快的使用串口重定向打印了。梁山派上的串口开发流程也就随之结束,当然也可以举一反三,尝试一下串口1的配置,并将其封装成bsp文件,下面是我配置并封装好了的样子

1.3 bsp_uart文件(只发送不接收,兼容串口0和串口1)

  1. bsp_uart.c
#include "bsp_uart.h"


/************************************************
函数名称 : uart_gpio_config
功    能 : 串口初始化函数,默认使用串口0
参    数 : band_rate:波特率
返 回 值 : 无
作    者 : 不想写代码的我
*************************************************/
void uart_gpio_config(uint32_t band_rate)
{
#if USING_USART0	
	/* 开启串口时钟和端口时钟 */
	rcu_periph_clock_enable(BSP_USART0_RCU);
	rcu_periph_clock_enable(BSP_USART0_TX_RCU);
	rcu_periph_clock_enable(BSP_USART0_RX_RCU);
	
	/* 配置GPIO的复用模式 */
	gpio_af_set(BSP_USART0_TX_PORT,BSP_USART0_TX_AF,BSP_USART0_TX_PIN);
	gpio_af_set(BSP_USART0_RX_PORT,BSP_USART0_RX_AF,BSP_USART0_RX_PIN);
	
	/* 配置GPIO模式:复用模式,上拉 */
	gpio_mode_set(BSP_USART0_TX_PORT,GPIO_MODE_AF,GPIO_PUPD_PULLUP,BSP_USART0_TX_PIN);
	gpio_mode_set(BSP_USART0_RX_PORT,GPIO_MODE_AF,GPIO_PUPD_PULLUP,BSP_USART0_RX_PIN);
	
	/* 配置GPIO的输出 */
	gpio_output_options_set(BSP_USART0_TX_PORT,GPIO_OTYPE_PP,GPIO_OSPEED_50MHZ,BSP_USART0_TX_PIN);
	gpio_output_options_set(BSP_USART0_RX_PORT,GPIO_OTYPE_PP,GPIO_OSPEED_50MHZ,BSP_USART0_RX_PIN);
	
	/* 配置串口波特率为输入参数,无校验、8位数据、1位停止位 */
	usart_deinit(BSP_USART0);
	usart_baudrate_set(BSP_USART0,band_rate);
	usart_parity_config(BSP_USART0,USART_PM_NONE);
	usart_word_length_set(BSP_USART0,USART_WL_8BIT);
	usart_stop_bit_set(BSP_USART0,USART_STB_1BIT);
	
	/* 使能串口和串口打印功能 */
	usart_enable(BSP_USART0);
	usart_transmit_config(BSP_USART0,USART_TRANSMIT_ENABLE);
#endif /* USING_USART0 */

#if USING_USART1	
	/* 开启串口时钟和端口时钟 */
	rcu_periph_clock_enable(BSP_USART1_RCU);
	rcu_periph_clock_enable(BSP_USART1_TX_RCU);
	rcu_periph_clock_enable(BSP_USART1_RX_RCU);
	
	/* 配置GPIO的复用模式 */
	gpio_af_set(BSP_USART1_TX_PORT,BSP_USART1_TX_AF,BSP_USART1_TX_PIN);
	gpio_af_set(BSP_USART1_RX_PORT,BSP_USART1_RX_AF,BSP_USART1_RX_PIN);
	
	/* 配置GPIO模式:复用模式,上拉 */
	gpio_mode_set(BSP_USART1_TX_PORT,GPIO_MODE_AF,GPIO_PUPD_PULLUP,BSP_USART1_TX_PIN);
	gpio_mode_set(BSP_USART1_RX_PORT,GPIO_MODE_AF,GPIO_PUPD_PULLUP,BSP_USART1_RX_PIN);
	
	/* 配置GPIO的输出 */
	gpio_output_options_set(BSP_USART1_TX_PORT,GPIO_OTYPE_PP,GPIO_OSPEED_50MHZ,BSP_USART1_TX_PIN);
	gpio_output_options_set(BSP_USART1_RX_PORT,GPIO_OTYPE_PP,GPIO_OSPEED_50MHZ,BSP_USART1_RX_PIN);
	
	/* 配置串口波特率为输入参数,无校验、8位数据、1位停止位 */
	usart_deinit(BSP_USART1);
	usart_baudrate_set(BSP_USART1,band_rate);
	usart_parity_config(BSP_USART1,USART_PM_NONE);
	usart_word_length_set(BSP_USART1,USART_WL_8BIT);
	usart_stop_bit_set(BSP_USART1,USART_STB_1BIT);
	
	/* 使能串口和串口打印功能 */
	usart_enable(BSP_USART1);
	usart_transmit_config(BSP_USART1,USART_TRANSMIT_ENABLE);
#endif /* USING_USART1 */

}

/************************************************
函数名称 : usart_send_data
功    能 : 串口发送一个字节
参    数 : ucch:要发送的字节
返 回 值 : 无
作    者 : 不想写代码的我
*************************************************/
void usart_send_data(uint8_t ucch)
{
#if USING_USART0
	usart_data_transmit(BSP_USART0,(uint8_t)ucch);							 // 发送数据
	while(RESET == usart_flag_get(BSP_USART0,USART_FLAG_TBE));  // 等待发送数据缓冲区标志置位
#endif /* USING_USART0 */
	
#if USING_USART1
	usart_data_transmit(BSP_USART1,(uint8_t)ucch);							 // 发送数据
	while(RESET == usart_flag_get(BSP_USART1,USART_FLAG_TBE));  // 等待发送数据缓冲区标志置位
#endif /* USING_USART1 */
}


/************************************************
函数名称 : usart_send_String
功    能 : 串口发送字符串
参    数 : ucstr:要发送的字符串
返 回 值 : 无
作    者 : 不想写代码的我
*************************************************/
void usart_send_string(uint8_t *ucstr)
{
	while(ucstr && *ucstr)        // 地址为空或者值为空跳出
	{
	  usart_send_data(*ucstr++);  // 发送单个字符
	}
}

/************************************************
函数名称 : fputc
功    能 : 串口重定向函数
参    数 : 
返 回 值 : 
作    者 : 不想写代码的我
*************************************************/
int fputc(int ch, FILE *f)
{
     usart_send_data(ch);
     // 等待发送数据缓冲区标志置位
     return ch;
}

【注】:这个代码时我在梁山派(GD32F470ZGT6)上写的,还没有对其他芯片做兼容,但是大部分应该都没问题。

  1. bsp_uart.h
#ifndef _BSP_UART_H
#define _BSP_UART_H

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

/* 默认使用串口0 */	
#define USING_USART0   1	
/* 当串口0被使用的时候,也可以使用串口1,但在连接电脑打印时,需要将串口0和串口1打印
PA9  《----》 PA2
PA10 《----》 PA3
不过一般不会这么用,这里只是举个例子
*/
#define USING_USART1   0	

#if USING_USART0
/* 串口时钟定义 */
#define BSP_USART0_RCU   	RCU_USART0 
#define BSP_USART0_TX_RCU 	RCU_GPIOA
#define BSP_USART0_RX_RCU 	RCU_GPIOA

/* 串口引脚定义 */
#define BSP_USART0_TX_PORT 	GPIOA       	
#define BSP_USART0_TX_PIN  	GPIO_PIN_9 	

#define BSP_USART0_RX_PORT 	GPIOA       	
#define BSP_USART0_RX_PIN 	GPIO_PIN_10 	

/* GPIO复用串口功能定义 */
#define BSP_USART0_TX_AF 	   	GPIO_AF_7
#define BSP_USART0_RX_AF 	   	GPIO_AF_7

/* 定义串口句柄 */
#define BSP_USART0 USART0

#endif /* USING_USART0 */

#if USING_USART1
/* 串口时钟定义 */
#define BSP_USART1_RCU 		RCU_USART1
#define BSP_USART1_TX_RCU 	RCU_GPIOA
#define BSP_USART1_RX_RCU 	RCU_GPIOA

/* 串口引脚定义 */
#define BSP_USART1_TX_PORT 	GPIOA      	
#define BSP_USART1_TX_PIN 	GPIO_PIN_2	

#define BSP_USART1_RX_PORT 	GPIOA   	
#define BSP_USART1_RX_PIN 	GPIO_PIN_3 	

/* GPIO复用串口功能定义 */
#define BSP_USART1_TX_AF 	   	GPIO_AF_7
#define BSP_USART1_RX_AF 	   	GPIO_AF_7

/* 定义串口句柄 */
#define BSP_USART1 USART1

#endif /* USING_USART0 */

void uart_gpio_config(uint32_t band_rate); 		// 串口初始化函数,参数为波特率,默认使用串口0
void usart_send_data(uint8_t ucch);          	// 发送一个字符
void usart_send_string(uint8_t *ucstr);     	// 发送一个字符串

#endif /* _BSP_UART_H */
 

【注】:这个代码时我在梁山派(GD32F470ZGT6)上写的,还没有对其他芯片做兼容,但是大部分应该都没问题。

1.4串口打印信息示例

下面是一个串口大小消息的使用示例,虽然很简单,但是也记录一下,避免后面重复造轮子。

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

#include "bsp_led.h"
#include "bsp_key.h"
#include "bsp_uart.h"

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

	
	/* 配置sysTick的时钟和中断周期*/
    systick_config();
	/* 串口初始化 */
	uart_gpio_config(115200);
	
    while(1) 
	{
		printf("Hello!!\r\n");
		delay_1ms(500);
    }
}

运行结果,显而易见,这里就不展示了

2. 串口接收数据(通过中断)

2.1 串口中断简介

关于串口中断,这里不做介绍,可以参考:夜深人静学32系列11——串口通信,我们直接进入下一步。

2.2 梁山派上的串口中断开发

  1. 配置串口中断

查阅梁山派的用户手册,可以看到串口中断的配置流程大致如下:
在这里插入图片描述

根据上文中 1.2 和 中断配置的内容,大概修改一下,就可以得到:


/* 是否开启串口中断接收 */
#define ENABLE_USART_INTERRUPT 1

/* 串口时钟定义 */
#define BSP_USART0_RCU   	RCU_USART0 
#define BSP_USART0_TX_RCU 	RCU_GPIOA
#define BSP_USART0_RX_RCU 	RCU_GPIOA

/* 串口引脚定义 */
#define BSP_USART0_TX_PORT 	GPIOA       	
#define BSP_USART0_TX_PIN  	GPIO_PIN_9 	

#define BSP_USART0_RX_PORT 	GPIOA       	
#define BSP_USART0_RX_PIN 	GPIO_PIN_10 	

/* GPIO复用串口功能定义 */
#define BSP_USART0_TX_AF 	   	GPIO_AF_7
#define BSP_USART0_RX_AF 	   	GPIO_AF_7

/* 定义串口句柄 */
#define BSP_USART0 USART0

void uart_gpio_config(uint32_t band_rate)
{	
	/* 开启串口时钟和端口时钟 */
	rcu_periph_clock_enable(BSP_USART0_RCU);
	rcu_periph_clock_enable(BSP_USART0_TX_RCU);
	rcu_periph_clock_enable(BSP_USART0_RX_RCU);
	
	/* 配置GPIO的复用模式 */
	gpio_af_set(BSP_USART0_TX_PORT,BSP_USART0_TX_AF,BSP_USART0_TX_PIN);
	gpio_af_set(BSP_USART0_RX_PORT,BSP_USART0_RX_AF,BSP_USART0_RX_PIN);
	
	/* 配置GPIO模式:复用模式,上拉 */
	gpio_mode_set(BSP_USART0_TX_PORT,GPIO_MODE_AF,GPIO_PUPD_PULLUP,BSP_USART0_TX_PIN);
	gpio_mode_set(BSP_USART0_RX_PORT,GPIO_MODE_AF,GPIO_PUPD_PULLUP,BSP_USART0_RX_PIN);
	
	/* 配置GPIO的输出 */
	gpio_output_options_set(BSP_USART0_TX_PORT,GPIO_OTYPE_PP,GPIO_OSPEED_50MHZ,BSP_USART0_TX_PIN);
	gpio_output_options_set(BSP_USART0_RX_PORT,GPIO_OTYPE_PP,GPIO_OSPEED_50MHZ,BSP_USART0_RX_PIN);
	
	/* 配置串口波特率为输入参数,无校验、8位数据、1位停止位 */
	usart_deinit(BSP_USART0);
	usart_baudrate_set(BSP_USART0,band_rate);
	usart_parity_config(BSP_USART0,USART_PM_NONE);
	usart_word_length_set(BSP_USART0,USART_WL_8BIT);
	usart_stop_bit_set(BSP_USART0,USART_STB_1BIT);
	
	/* 使能串口和串口打印和接收功能 */
	usart_enable(BSP_USART0);
	usart_transmit_config(BSP_USART0,USART_TRANSMIT_ENABLE);
	usart_receive_config(BSP_USART0, USART_RECEIVE_ENABLE);
	
	/* 判断是否使能串口中断 */
	#if ENABLE_USART_INTERRUPT 
	
		/* 使能串口中断 */
		usart_interrupt_enable(BSP_USART0, USART_INT_RBNE);
		
		/* 开启接收中断 */
		nvic_irq_enable(USART0_IRQn, 2, 0);
		
	#endif /* ENABLE_USART_INTERRUPT */
}
  1. 编写中断服务函数:
/************************************************
函数名称 : USART0_IRQHandler
功    能 : 串口0中断服务函数
参    数 : 无
返 回 值 : 无
作    者 : 不想写代码的我
*************************************************/
void USART0_IRQHandler(void)
{

	/*判断是否有接收中断*/
	if(usart_interrupt_flag_get(BSP_USART0, USART_INT_FLAG_RBNE) != RESET)
	{
		// 清除中断
		usart_interrupt_flag_clear(BSP_USART0, USART_INT_FLAG_RBNE);
		
		/*接收数据*/
		uint8_t uData = (uint8_t)usart_data_receive(BSP_USART0);
		
		/*将接收到数据做处理*/
		printf("USART0:%d\r\n",uData);
	}
}
	
  1. 举一反三,引入对USART1的兼容,这样你就可以得到一个完整的bsp_uart文件(这个bsp_uart文件我放在 2.3 小节中)

2.3 bsp_uart文件(发送和中断接收,兼容串口0和串口1)

  1. bsp_uart.c
#include "bsp_uart.h"

/************************************************
函数名称 : uart_gpio_config
功    能 : 串口初始化函数,默认使用串口0
参    数 : band_rate:波特率
返 回 值 : 无
作    者 : 不想写代码的我
*************************************************/
void uart_gpio_config(uint32_t band_rate)
{
/* 使用串口0 */
#if USING_USART0	
	
	/* 开启串口时钟和端口时钟 */
	rcu_periph_clock_enable(BSP_USART0_RCU);
	rcu_periph_clock_enable(BSP_USART0_TX_RCU);
	rcu_periph_clock_enable(BSP_USART0_RX_RCU);
	
	/* 配置GPIO的复用模式 */
	gpio_af_set(BSP_USART0_TX_PORT,BSP_USART0_TX_AF,BSP_USART0_TX_PIN);
	gpio_af_set(BSP_USART0_RX_PORT,BSP_USART0_RX_AF,BSP_USART0_RX_PIN);
	
	/* 配置GPIO模式:复用模式,上拉 */
	gpio_mode_set(BSP_USART0_TX_PORT,GPIO_MODE_AF,GPIO_PUPD_PULLUP,BSP_USART0_TX_PIN);
	gpio_mode_set(BSP_USART0_RX_PORT,GPIO_MODE_AF,GPIO_PUPD_PULLUP,BSP_USART0_RX_PIN);
	
	/* 配置GPIO的输出 */
	gpio_output_options_set(BSP_USART0_TX_PORT,GPIO_OTYPE_PP,GPIO_OSPEED_50MHZ,BSP_USART0_TX_PIN);
	gpio_output_options_set(BSP_USART0_RX_PORT,GPIO_OTYPE_PP,GPIO_OSPEED_50MHZ,BSP_USART0_RX_PIN);
	
	/* 配置串口波特率为输入参数,无校验、8位数据、1位停止位 */
	usart_deinit(BSP_USART0);
	usart_baudrate_set(BSP_USART0,band_rate);
	usart_parity_config(BSP_USART0,USART_PM_NONE);
	usart_word_length_set(BSP_USART0,USART_WL_8BIT);
	usart_stop_bit_set(BSP_USART0,USART_STB_1BIT);
	
	/* 使能串口和串口打印和接收功能 */
	usart_enable(BSP_USART0);
	usart_transmit_config(BSP_USART0,USART_TRANSMIT_ENABLE);
	usart_receive_config(BSP_USART0, USART_RECEIVE_ENABLE);
	
	/* 判断是否使能串口中断 */
	#if ENABLE_USART_INTERRUPT 
	
		/* 使能串口中断 */
		usart_interrupt_enable(BSP_USART0, USART_INT_RBNE);
		
		/* 开启接收中断 */
		nvic_irq_enable(USART0_IRQn, 2, 0);
		
	#endif /* ENABLE_USART_INTERRUPT */
	
#endif /* USING_USART0 */

/* 使用串口1 */
#if USING_USART1	
	/* 开启串口时钟和端口时钟 */
	rcu_periph_clock_enable(BSP_USART1_RCU);
	rcu_periph_clock_enable(BSP_USART1_TX_RCU);
	rcu_periph_clock_enable(BSP_USART1_RX_RCU);
	
	/* 配置GPIO的复用模式 */
	gpio_af_set(BSP_USART1_TX_PORT,BSP_USART1_TX_AF,BSP_USART1_TX_PIN);
	gpio_af_set(BSP_USART1_RX_PORT,BSP_USART1_RX_AF,BSP_USART1_RX_PIN);
	
	/* 配置GPIO模式:复用模式,上拉 */
	gpio_mode_set(BSP_USART1_TX_PORT,GPIO_MODE_AF,GPIO_PUPD_PULLUP,BSP_USART1_TX_PIN);
	gpio_mode_set(BSP_USART1_RX_PORT,GPIO_MODE_AF,GPIO_PUPD_PULLUP,BSP_USART1_RX_PIN);
	
	/* 配置GPIO的输出 */
	gpio_output_options_set(BSP_USART1_TX_PORT,GPIO_OTYPE_PP,GPIO_OSPEED_50MHZ,BSP_USART1_TX_PIN);
	gpio_output_options_set(BSP_USART1_RX_PORT,GPIO_OTYPE_PP,GPIO_OSPEED_50MHZ,BSP_USART1_RX_PIN);
	
	/* 配置串口波特率为输入参数,无校验、8位数据、1位停止位 */
	usart_deinit(BSP_USART1);
	usart_baudrate_set(BSP_USART1,band_rate);
	usart_parity_config(BSP_USART1,USART_PM_NONE);
	usart_word_length_set(BSP_USART1,USART_WL_8BIT);
	usart_stop_bit_set(BSP_USART1,USART_STB_1BIT);
	
	/* 使能串口和串口打印和接收功能 */
	usart_enable(BSP_USART1);
	usart_transmit_config(BSP_USART1,USART_TRANSMIT_ENABLE);
	usart_receive_config(BSP_USART1, USART_RECEIVE_ENABLE);
	
	/* 判断是否使能串口中断 */
	#if ENABLE_USART_INTERRUPT 
	
		/* 使能串口中断 */
		usart_interrupt_enable(BSP_USART1, USART_INT_RBNE);
		
		/* 开启接收中断 */
		nvic_irq_enable(USART1_IRQn, 2, 0);
		
	#endif /* ENABLE_USART_INTERRUPT */
	
#endif /* USING_USART1 */

}

/************************************************
函数名称 : usart_send_data
功    能 : 串口发送一个字节
参    数 : ucch:要发送的字节
返 回 值 : 无
作    者 : 不想写代码的我
*************************************************/
void usart_send_data(uint8_t ucch)
{
/* 使用串口0 */
#if USING_USART0
	usart_data_transmit(BSP_USART0,(uint8_t)ucch);							 // 发送数据
	while(RESET == usart_flag_get(BSP_USART0,USART_FLAG_TBE));  // 等待发送数据缓冲区标志置位
#endif /* USING_USART0 */
	
/* 使用串口1 */
#if USING_USART1
	usart_data_transmit(BSP_USART1,(uint8_t)ucch);							 // 发送数据
	while(RESET == usart_flag_get(BSP_USART1,USART_FLAG_TBE));  // 等待发送数据缓冲区标志置位
#endif /* USING_USART1 */
}


/************************************************
函数名称 : usart_send_String
功    能 : 串口发送字符串
参    数 : ucstr:要发送的字符串
返 回 值 : 无
作    者 : 不想写代码的我
*************************************************/
void usart_send_string(uint8_t *ucstr)
{
	while(ucstr && *ucstr)        // 地址为空或者值为空跳出
	{
	  usart_send_data(*ucstr++);  // 发送单个字符
	}
}

/************************************************
函数名称 : fputc
功    能 : 串口重定向函数
参    数 : 
返 回 值 : 
作    者 : 不想写代码的我
*************************************************/
int fputc(int ch, FILE *f)
{
     usart_send_data(ch);
     // 等待发送数据缓冲区标志置位
     return ch;
}

/* 判断是否使能了串口中断 */
#if ENABLE_USART_INTERRUPT 

/* 使用串口0 */
#if USING_USART0	

/************************************************
函数名称 : USART0_IRQHandler
功    能 : 串口0中断服务函数
参    数 : 无
返 回 值 : 无
作    者 : 不想写代码的我
*************************************************/
void USART0_IRQHandler(void)
{

	/*判断是否有接收中断*/
	if(usart_interrupt_flag_get(BSP_USART0, USART_INT_FLAG_RBNE) != RESET)
	{
		// 清除中断
		usart_interrupt_flag_clear(BSP_USART0, USART_INT_FLAG_RBNE);
		
		/*接收数据*/
		uint8_t uData = (uint8_t)usart_data_receive(BSP_USART0);
		
		/*将接收到数据做处理*/
		printf("USART0:%d\r\n",uData);
	}
}
	
#endif /* USING_USART0 */
	
/* 使用串口1 */
#if USING_USART1

/************************************************
函数名称 : USART0_IRQHandler
功    能 : 串口0中断服务函数
参    数 : 无
返 回 值 : 无
作    者 : 不想写代码的我
*************************************************/
void USART1_IRQHandler(void)
{
	/*判断是否有接收中断*/
	if(usart_interrupt_flag_get(BSP_USART1, USART_INT_FLAG_RBNE) != RESET)
	{
		// 清除中断
		usart_interrupt_flag_clear(BSP_USART1, USART_INT_FLAG_RBNE);
		
		/*接收数据*/
		uint8_t uData = (uint8_t)usart_data_receive(BSP_USART1);
		
		/*将接收到数据做处理*/
		printf("USART1:%d\r\n",uData);
	}
}

#endif /* USING_USART1 */

#endif /* ENABLE_USART_INTERRUPT */

【注】:这个代码时我在梁山派(GD32F470ZGT6)上写的,还没有对其他芯片做兼容,但是大部分应该都没问题。

  1. bsp_uart.h
#ifndef _BSP_UART_H
#define _BSP_UART_H

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

/* 是否开启串口中断接收 */
#define ENABLE_USART_INTERRUPT 1

/* 默认使用串口0 */	
#define USING_USART0   1
#define USING_USART1   0
/* 当串口0被使用的时候,也可以使用串口1,但在连接电脑打印时,需要将串口0和串口1打印
PA9  《----》 PA2
PA10 《----》 PA3
不过一般不会这么用,这里只是举个例子
*/
	

#if USING_USART0
/* 串口时钟定义 */
#define BSP_USART0_RCU   	RCU_USART0 
#define BSP_USART0_TX_RCU 	RCU_GPIOA
#define BSP_USART0_RX_RCU 	RCU_GPIOA

/* 串口引脚定义 */
#define BSP_USART0_TX_PORT 	GPIOA       	
#define BSP_USART0_TX_PIN  	GPIO_PIN_9 	

#define BSP_USART0_RX_PORT 	GPIOA       	
#define BSP_USART0_RX_PIN 	GPIO_PIN_10 	

/* GPIO复用串口功能定义 */
#define BSP_USART0_TX_AF 	   	GPIO_AF_7
#define BSP_USART0_RX_AF 	   	GPIO_AF_7

/* 定义串口句柄 */
#define BSP_USART0 USART0

#endif /* USING_USART0 */

#if USING_USART1
/* 串口时钟定义 */
#define BSP_USART1_RCU 		RCU_USART1
#define BSP_USART1_TX_RCU 	RCU_GPIOA
#define BSP_USART1_RX_RCU 	RCU_GPIOA

/* 串口引脚定义 */
#define BSP_USART1_TX_PORT 	GPIOA      	
#define BSP_USART1_TX_PIN 	GPIO_PIN_2	

#define BSP_USART1_RX_PORT 	GPIOA   	
#define BSP_USART1_RX_PIN 	GPIO_PIN_3 	

/* GPIO复用串口功能定义 */
#define BSP_USART1_TX_AF 	   	GPIO_AF_7
#define BSP_USART1_RX_AF 	   	GPIO_AF_7

/* 定义串口句柄 */
#define BSP_USART1 USART1

#endif /* USING_USART0 */

void uart_gpio_config(uint32_t band_rate); 		// 串口初始化函数,参数为波特率,默认使用串口0
void usart_send_data(uint8_t ucch);          	// 发送一个字符
void usart_send_string(uint8_t *ucstr);     	// 发送一个字符串

#endif /* _BSP_UART_H */
 
 

【注】:这个代码时我在梁山派(GD32F470ZGT6)上写的,还没有对其他芯片做兼容,但是大部分应该都没问题。

1.4 中断接收数据示例

这里直接打开串口助手看一下就行,不展示。

3. 串口+中断+DMA接收

上面介绍了串口中断的方式接收数据,但是在数据量比较多的时候,我们需要引入DMA,减轻CPU的压力。话不多说,我们直接开始:

3.1 DMA简介

DMA(Direct Memory Access)控制器提供了一种硬件的方式在外设和存储器之间或者存储器和存储器之间传输数据,而无需 CPU 的介入,避免了 CPU 多次进入中断进行大规模的数据拷贝,最终提高整体的系统性能。

DMA 是一种能够在无需 CPU 参与的情况下,将数据块在内存和外设之间高效传输的硬件机制。实现这种功能的集成电路单元叫做 DMA Controller,即 DMA 控制器。

DMA控制器在没有CPU参与的情况下从一个地址向另一个地址传输数据,它支持多种数据宽度,突发类型,地址生成算法,优先级和传输模式,可以灵活的配置以满足应用的需求。

GD32F450ZGT6 有两个 DMA 控制器(DMA0,DMA1),每个 DMA 控制器包含了两个 AHB 总线接口和 8 个 4 字深度的 FIFO,使 DMA 可以高效的传输数据。每个 DMA 控制器有 8 个通道,一共是16 个通道,每个通道可以被分配给一个或多个特定的外设进行数据传输。两个内置的总线仲裁器用来处理 DMA 请求的优先级问题。DMA 控制器支持 8 位,16 位和 32 位的数据宽度。

关于DMA的内容,可以参考GD32F4系列用户手册的DMA章节,这里只展示重要的信息。

在这里插入图片描述
在这里插入图片描述

3.2 梁山派上的DMA开发

1.DMA配置流程

查阅梁山派的用户手册,可以看到DMA的配置流程大致如下:
在这里插入图片描述
根据图片中DMA的配置流程内容,大概修改一下,就可以得到:


/* 串口缓冲区的数据长度 */
#define USART_RECEIVE_LENGTH 200

/* 串口时钟定义 */
#define BSP_USART0_RCU   	RCU_USART0 
#define BSP_USART0_TX_RCU 	RCU_GPIOA
#define BSP_USART0_RX_RCU 	RCU_GPIOA

/* 串口引脚定义 */
#define BSP_USART0_TX_PORT 	GPIOA       	
#define BSP_USART0_TX_PIN  	GPIO_PIN_9 	

#define BSP_USART0_RX_PORT 	GPIOA       	
#define BSP_USART0_RX_PIN 	GPIO_PIN_10 	

/* GPIO复用串口功能定义 */
#define BSP_USART0_TX_AF 	   	GPIO_AF_7
#define BSP_USART0_RX_AF 	   	GPIO_AF_7

/* 定义串口句柄 */
#define BSP_USART0 USART0

/* USART0_RX DMA1_CH2 */
#define BSP_DMA_RCU   		RCU_DMA1            // DMA时钟
#define BSP_DMA  			DMA1 				// DMA
#define BSP_DMA_CH  		DMA_CH2				// DMA通道
#define BSP_DMA_CH_IRQ  	DMA1_Channel2_IRQn	// DMA中断
#define BSP_DMA_CH_IRQHandler DMA1_Channel2_IRQHandler // DMA 中断服务函数
/************************************************
函数名称 : dma_config
功    能 : DMA配置
参    数 : 无
返 回 值 : 无
作    者 : 不想写代码的我
*************************************************/
void dma_config(void)
{
	/* 定义DMA单数据结构体 */
	dma_single_data_parameter_struct dma_init_struct;               
	/* 开启DMA时钟 */
	rcu_periph_clock_enable(BSP_DMA_RCU);													
  	/* 初始化DMA通道 */
	dma_deinit(BSP_DMA,BSP_DMA_CH);																
	
	 /* 配置DMA初始化参数 */
	dma_init_struct.periph_addr = (uint32_t)&USART_DATA(BSP_USART0);   	// 外设地址
	dma_init_struct.periph_inc = DMA_PERIPH_INCREASE_DISABLE;        	// 不使用增量模式,为固定模式  
	dma_init_struct.memory0_addr = (uint32_t)g_recv_buff;              	// 内存地址          
	dma_init_struct.memory_inc = DMA_MEMORY_INCREASE_ENABLE;      	 	// 增量模式                    
	dma_init_struct.periph_memory_width = DMA_PERIPH_WIDTH_8BIT;        // 一次传输长度8bit         
	dma_init_struct.circular_mode  = DMA_CIRCULAR_MODE_DISABLE;      	// 关闭循环模式               
	dma_init_struct.direction  = DMA_PERIPH_TO_MEMORY;          		// 外设到内存                  
	dma_init_struct.number = USART_RECEIVE_LENGTH;         			 	// 要传输的数据量                     
	dma_init_struct.priority = DMA_PRIORITY_ULTRA_HIGH;  				// 超高优先级
	/* 初始化DMA结构体 */
	dma_single_data_mode_init(BSP_DMA,BSP_DMA_CH,&dma_init_struct);
	
	/* 使能通道外设 */
	dma_channel_subperipheral_select(BSP_DMA,BSP_DMA_CH,DMA_SUBPERI4);

	/* 使能DMA通道 */
	dma_channel_enable(BSP_DMA,BSP_DMA_CH);

	/* 使能DMA通道中断 */
	dma_interrupt_enable(BSP_DMA,BSP_DMA_CH,DMA_CHXCTL_FTFIE);
	/* 配置中断优先级 */
	nvic_irq_enable(BSP_DMA_CH_IRQ, 2, 0);
	
	/* 使能串口DMA接收 */
	usart_dma_receive_config(BSP_USART0,USART_RECEIVE_DMA_ENABLE);
}
  1. 编写中断服务函数
/************************************************
函数名称 : BSP_DMA_CH_IRQHandler
功    能 : DMA中断服务函数
参    数 : 无
返 回 值 : 无
作    者 : 不想写代码的我
*************************************************/
void BSP_DMA_CH_IRQHandler(void)
{
	/* 传输完成中断 */
	if(dma_interrupt_flag_get(BSP_DMA,BSP_DMA_CH,DMA_INT_FLAG_FTF) == SET) 
	{	
		/* 清除中断标志位 */
		dma_interrupt_flag_clear(BSP_DMA,BSP_DMA_CH,DMA_INT_FLAG_FTF);				
		//g_recv_complete_flag = 1;     // 数据传输完成 
  }
}
  1. 举一反三,引入对串口1的兼容,并且将DMA接收和中断接收同时兼容到bsp_uart文件中,这样你就得到了一个完美的bsp_uart文件(这个bsp_uart文件我放在 2.4 小节中)

3.3 bsp_uart文件(发送、中断接收和DMA接收二合一,兼容串口0和串口1)

  1. bsp_uart.c
#include "bsp_uart.h"

uint8_t g_recv_buff[USART_RECEIVE_LENGTH]; // 接收缓冲区
uint16_t g_recv_length = 0; 			// 接收数据长度
uint8_t g_recv_complete_flag = 0; // 接收完成标志位

/************************************************
函数名称 : uart_gpio_config
功    能 : 串口初始化函数,默认使用串口0
参    数 : band_rate:波特率
返 回 值 : 无
作    者 : 不想写代码的我
*************************************************/
void uart_gpio_config(uint32_t band_rate)
{
/* 使用串口0 */
#if USING_USART0	
	
	/* 开启串口时钟和端口时钟 */
	rcu_periph_clock_enable(BSP_USART0_RCU);
	rcu_periph_clock_enable(BSP_USART0_TX_RCU);
	rcu_periph_clock_enable(BSP_USART0_RX_RCU);
	
	/* 配置GPIO的复用模式 */
	gpio_af_set(BSP_USART0_TX_PORT,BSP_USART0_TX_AF,BSP_USART0_TX_PIN);
	gpio_af_set(BSP_USART0_RX_PORT,BSP_USART0_RX_AF,BSP_USART0_RX_PIN);
	
	/* 配置GPIO模式:复用模式,上拉 */
	gpio_mode_set(BSP_USART0_TX_PORT,GPIO_MODE_AF,GPIO_PUPD_PULLUP,BSP_USART0_TX_PIN);
	gpio_mode_set(BSP_USART0_RX_PORT,GPIO_MODE_AF,GPIO_PUPD_PULLUP,BSP_USART0_RX_PIN);
	
	/* 配置GPIO为推挽输出,50MHz */
	gpio_output_options_set(BSP_USART0_TX_PORT,GPIO_OTYPE_PP,GPIO_OSPEED_50MHZ,BSP_USART0_TX_PIN);
	gpio_output_options_set(BSP_USART0_RX_PORT,GPIO_OTYPE_PP,GPIO_OSPEED_50MHZ,BSP_USART0_RX_PIN);
	
	/* 配置串口波特率为输入参数,无校验、8位数据、1位停止位 */
	usart_deinit(BSP_USART0);
	usart_baudrate_set(BSP_USART0,band_rate);
	usart_parity_config(BSP_USART0,USART_PM_NONE);
	usart_word_length_set(BSP_USART0,USART_WL_8BIT);
	usart_stop_bit_set(BSP_USART0,USART_STB_1BIT);
	
	/* 使能串口和串口打印和接收功能 */
	usart_enable(BSP_USART0);
	usart_transmit_config(BSP_USART0,USART_TRANSMIT_ENABLE);
	usart_receive_config(BSP_USART0, USART_RECEIVE_ENABLE);
	
	/* 配置串口中断的优先级 */
	nvic_irq_enable(USART0_IRQn, 2, 0); 									
	
	/* 判断是使用串口中断接收还是DMA+中断接收 */
	#if RECEIVE_USART_INTERRUPT		/* 通过中断接收,需要开启缓冲区非空中断 */																	 // 使用中断
		usart_interrupt_enable(BSP_USART0,USART_INT_RBNE);      // 读数据缓冲区非空中断和溢出错误中断
		printf("Interrupt receive\r\n");
	#else							/* 通过DMA+串口接收,需要配置DMA */
	printf("DMA receive\r\n");
	dma_config();
	#endif
	
	/* 空闲检测中断 */
	usart_interrupt_enable(BSP_USART0,USART_INT_IDLE);         
	
#endif /* USING_USART0 */

/* 使用串口1 */
#if USING_USART1	
	/* 开启串口时钟和端口时钟 */
	rcu_periph_clock_enable(BSP_USART1_RCU);
	rcu_periph_clock_enable(BSP_USART1_TX_RCU);
	rcu_periph_clock_enable(BSP_USART1_RX_RCU);
	
	/* 配置GPIO的复用模式 */
	gpio_af_set(BSP_USART1_TX_PORT,BSP_USART1_TX_AF,BSP_USART1_TX_PIN);
	gpio_af_set(BSP_USART1_RX_PORT,BSP_USART1_RX_AF,BSP_USART1_RX_PIN);
	
	/* 配置GPIO模式:复用模式,上拉 */
	gpio_mode_set(BSP_USART1_TX_PORT,GPIO_MODE_AF,GPIO_PUPD_PULLUP,BSP_USART1_TX_PIN);
	gpio_mode_set(BSP_USART1_RX_PORT,GPIO_MODE_AF,GPIO_PUPD_PULLUP,BSP_USART1_RX_PIN);
	
	/* 配置GPIO的输出 */
	gpio_output_options_set(BSP_USART1_TX_PORT,GPIO_OTYPE_PP,GPIO_OSPEED_50MHZ,BSP_USART1_TX_PIN);
	gpio_output_options_set(BSP_USART1_RX_PORT,GPIO_OTYPE_PP,GPIO_OSPEED_50MHZ,BSP_USART1_RX_PIN);
	
	/* 配置串口波特率为输入参数,无校验、8位数据、1位停止位 */
	usart_deinit(BSP_USART1);
	usart_baudrate_set(BSP_USART1,band_rate);
	usart_parity_config(BSP_USART1,USART_PM_NONE);
	usart_word_length_set(BSP_USART1,USART_WL_8BIT);
	usart_stop_bit_set(BSP_USART1,USART_STB_1BIT);
	
	/* 使能串口和串口打印和接收功能 */
	usart_enable(BSP_USART1);
	usart_transmit_config(BSP_USART1,USART_TRANSMIT_ENABLE);
	usart_receive_config(BSP_USART1, USART_RECEIVE_ENABLE);
	
	/* 配置串口中断的优先级 */
	nvic_irq_enable(USART1_IRQn, 2, 0); 									
	
	/* 判断是使用串口中断接收还是DMA+中断接收 */
	#if RECEIVE_USART_INTERRUPT		/* 通过中断接收,需要开启缓冲区非空中断 */																	 // 使用中断
		usart_interrupt_enable(BSP_USART1,USART_INT_RBNE);      // 读数据缓冲区非空中断和溢出错误中断
		printf("Interrupt receive\r\n");
	#else							/* 通过DMA+串口接收,需要配置DMA */
	printf("DMA receive\r\n");
	dma_config();
	#endif
	
	/* 空闲检测中断 */
	usart_interrupt_enable(BSP_USART1,USART_INT_IDLE);
	
#endif /* USING_USART1 */

}

/************************************************
函数名称 : usart_send_data
功    能 : 串口发送一个字节
参    数 : ucch:要发送的字节
返 回 值 : 无
作    者 : 不想写代码的我
*************************************************/
void usart_send_data(uint8_t ucch)
{
/* 使用串口0 */
#if USING_USART0
	usart_data_transmit(BSP_USART0,(uint8_t)ucch);							 // 发送数据
	while(RESET == usart_flag_get(BSP_USART0,USART_FLAG_TBE));  // 等待发送数据缓冲区标志置位
#endif /* USING_USART0 */
	
/* 使用串口1 */
#if USING_USART1
	usart_data_transmit(BSP_USART1,(uint8_t)ucch);							 // 发送数据
	while(RESET == usart_flag_get(BSP_USART1,USART_FLAG_TBE));  // 等待发送数据缓冲区标志置位
#endif /* USING_USART1 */
}


/************************************************
函数名称 : usart_send_String
功    能 : 串口发送字符串
参    数 : ucstr:要发送的字符串
返 回 值 : 无
作    者 : 不想写代码的我
*************************************************/
void usart_send_string(uint8_t *ucstr)
{
	while(ucstr && *ucstr)        // 地址为空或者值为空跳出
	{
	  usart_send_data(*ucstr++);  // 发送单个字符
	}
}

/************************************************
函数名称 : fputc
功    能 : 串口重定向函数
参    数 : 
返 回 值 : 
作    者 : 不想写代码的我
*************************************************/
int fputc(int ch, FILE *f)
{
     usart_send_data(ch);
     // 等待发送数据缓冲区标志置位
     return ch;
}

/************************************************
函数名称 : dma_config
功    能 : DMA配置
参    数 : 无
返 回 值 : 无
作    者 : 不想写代码的我
*************************************************/
#if !RECEIVE_USART_INTERRUPT

void dma_config(void)
{
	/* 定义DMA单数据结构体 */
	dma_single_data_parameter_struct dma_init_struct;               
	/* 开启DMA时钟 */
	rcu_periph_clock_enable(BSP_DMA_RCU);													
  	/* 初始化DMA通道 */
	dma_deinit(BSP_DMA,BSP_DMA_CH);																
	
	 /* 配置DMA初始化参数 */
	#if USING_USART0
	dma_init_struct.periph_addr = (uint32_t)&USART_DATA(BSP_USART0);   	// 外设地址
	#endif
	
	#if USING_USART1
	dma_init_struct.periph_addr = (uint32_t)&USART_DATA(BSP_USART1);   	// 外设地址
	#endif	
	dma_init_struct.periph_inc = DMA_PERIPH_INCREASE_DISABLE;        	// 不使用增量模式,为固定模式  
	dma_init_struct.memory0_addr = (uint32_t)g_recv_buff;              	// 内存地址          
	dma_init_struct.memory_inc = DMA_MEMORY_INCREASE_ENABLE;      	 	// 增量模式                    
	dma_init_struct.periph_memory_width = DMA_PERIPH_WIDTH_8BIT;        // 一次传输长度8bit         
	dma_init_struct.circular_mode  = DMA_CIRCULAR_MODE_DISABLE;      	// 关闭循环模式               
	dma_init_struct.direction  = DMA_PERIPH_TO_MEMORY;          		// 外设到内存                  
	dma_init_struct.number = USART_RECEIVE_LENGTH;         			 	// 要传输的数据量                     
	dma_init_struct.priority = DMA_PRIORITY_ULTRA_HIGH;  				// 超高优先级
	/* 初始化DMA结构体 */
	dma_single_data_mode_init(BSP_DMA,BSP_DMA_CH,&dma_init_struct);

	
	#if USING_USART0
	/* 使能通道外设 */
	dma_channel_subperipheral_select(BSP_DMA,BSP_DMA_CH,DMA_SUBPERI4);
	#endif
	
	#if USING_USART1
	/* 使能通道外设 */
	dma_channel_subperipheral_select(BSP_DMA,BSP_DMA_CH,DMA_SUBPERI4);
	#endif
	
	/* 使能DMA通道 */
	dma_channel_enable(BSP_DMA,BSP_DMA_CH);

	/* 使能DMA通道中断 */
	dma_interrupt_enable(BSP_DMA,BSP_DMA_CH,DMA_CHXCTL_FTFIE);
	/* 配置中断优先级 */
	nvic_irq_enable(BSP_DMA_CH_IRQ, 2, 0);
	
	#if USING_USART0
	/* 使能串口DMA接收 */
	usart_dma_receive_config(BSP_USART0,USART_RECEIVE_DMA_ENABLE);
	#endif
	
	#if USING_USART1
	/* 使能串口DMA接收 */
	usart_dma_receive_config(BSP_USART1,USART_RECEIVE_DMA_ENABLE);
	#endif
}
#endif /* !RECEIVE_USART_INTERRUPT */

/* 使用串口0 */
#if USING_USART0	

/************************************************
函数名称 : USART0_IRQHandler
功    能 : 串口0中断服务函数
参    数 : 无
返 回 值 : 无
作    者 : 不想写代码的我
*************************************************/
void USART0_IRQHandler(void)
{
	/* 使用中断 */
	#if RECEIVE_USART_INTERRUPT   
	if(usart_interrupt_flag_get(BSP_USART0,USART_INT_FLAG_RBNE) == SET)   // 接收缓冲区不为空
	{
		g_recv_buff[g_recv_length++] = usart_data_receive(BSP_USART0);      // 把接收到的数据放到缓冲区中
	}
	#endif
	
	if(usart_interrupt_flag_get(BSP_USART0,USART_INT_FLAG_IDLE) == SET)     // 检测到帧中断
	{
		usart_data_receive(BSP_USART0);			// 必须要读,读出来的值不能要
	
		#if !RECEIVE_USART_INTERRUPT   // 使用DMA
		/* 处理DMA接收到的数据 */
		g_recv_length = USART_RECEIVE_LENGTH - dma_transfer_number_get(BSP_DMA,BSP_DMA_CH);	// 计算实际接收的数据长度
		/* 重新设置DMA传输 */
		dma_channel_disable(BSP_DMA, BSP_DMA_CH);	 																							 // 失能DMA通道
		dma_config();  																																					 // 重新配置DMA进行传输
		#endif
		
		g_recv_buff[g_recv_length] = '\0';																											   // 数据接收完毕,数组结束标志
		g_recv_complete_flag = 1;																																   // 接收完成 
		
	}

	
}
	
#endif /* USING_USART0 */
	
/* 使用串口1 */
#if USING_USART1

/************************************************
函数名称 : USART1_IRQHandler
功    能 : 串口1中断服务函数
参    数 : 无
返 回 值 : 无
作    者 : 不想写代码的我
*************************************************/
void USART1_IRQHandler(void)
{
	/* 使用中断 */
	#if RECEIVE_USART_INTERRUPT   
	if(usart_interrupt_flag_get(BSP_USART1,USART_INT_FLAG_RBNE) == SET)   // 接收缓冲区不为空
	{
		g_recv_buff[g_recv_length++] = usart_data_receive(BSP_USART1);      // 把接收到的数据放到缓冲区中
	}
	#endif
	
	if(usart_interrupt_flag_get(BSP_USART1,USART_INT_FLAG_IDLE) == SET)     // 检测到帧中断
	{
		usart_data_receive(BSP_USART1);			// 必须要读,读出来的值不能要
	
		#if !RECEIVE_USART_INTERRUPT   // 使用DMA
		/* 处理DMA接收到的数据 */
		g_recv_length = USART_RECEIVE_LENGTH - dma_transfer_number_get(BSP_DMA,BSP_DMA_CH);	// 计算实际接收的数据长度
		/* 重新设置DMA传输 */
		dma_channel_disable(BSP_DMA, BSP_DMA_CH);	 																							 // 失能DMA通道
		dma_config();  																																					 // 重新配置DMA进行传输
		#endif
		
		g_recv_buff[g_recv_length] = '\0';																											   // 数据接收完毕,数组结束标志
		g_recv_complete_flag = 1;																																   // 接收完成 
		
	}
}

#endif /* USING_USART1 */

/************************************************
函数名称 : BSP_DMA_CH_IRQHandler
功    能 : DMA中断服务函数,只有RECEIVE_USART_INTERRUPT配置为0时才启用
参    数 : 无
返 回 值 : 无
作    者 : 不想写代码的我
*************************************************/
#if !RECEIVE_USART_INTERRUPT
void BSP_DMA_CH_IRQHandler(void)
{
	/* 传输完成中断 */
	if(dma_interrupt_flag_get(BSP_DMA,BSP_DMA_CH,DMA_INT_FLAG_FTF) == SET) 
	{	
		/* 清除中断标志位 */
		dma_interrupt_flag_clear(BSP_DMA,BSP_DMA_CH,DMA_INT_FLAG_FTF);				
		//g_recv_complete_flag = 1;     // 数据传输完成 
  }
}
#endif /* !RECEIVE_USART_INTERRUPT */

【注】:这个代码时我在梁山派(GD32F470ZGT6)上写的,还没有对其他芯片做兼容,但是大部分应该都没问题。

  1. bsp_uart.h
#ifndef _BSP_UART_H
#define _BSP_UART_H

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

/* 1 : 中断接收  0 :DMA接收+中断 */
#define RECEIVE_USART_INTERRUPT  0

/* 默认使用串口0 */	
#define USING_USART0   1
#define USING_USART1   0
/* 当串口0被使用的时候,也可以使用串口1,但在连接电脑打印时,需要将串口0和串口1打印
PA9  《----》 PA2
PA10 《----》 PA3
不过一般不会这么用,这里只是举个例子 */

#define ARRAYNUM(arr_name) (uint32_t)(sizeof(arr_name) / sizeof(*(arr_name)))
/* 串口缓冲区的数据长度 */
#define USART_RECEIVE_LENGTH 200


/* 串口0相关宏定义 */
#if USING_USART0
/* 串口时钟定义 */
#define BSP_USART0_RCU   	RCU_USART0 
#define BSP_USART0_TX_RCU 	RCU_GPIOA
#define BSP_USART0_RX_RCU 	RCU_GPIOA

/* 串口引脚定义 */
#define BSP_USART0_TX_PORT 	GPIOA       	
#define BSP_USART0_TX_PIN  	GPIO_PIN_9 	

#define BSP_USART0_RX_PORT 	GPIOA       	
#define BSP_USART0_RX_PIN 	GPIO_PIN_10 	

/* GPIO复用串口功能定义 */
#define BSP_USART0_TX_AF 	   	GPIO_AF_7
#define BSP_USART0_RX_AF 	   	GPIO_AF_7

/* 定义串口句柄 */
#define BSP_USART0 USART0

#if !RECEIVE_USART_INTERRUPT	//开启了DMA+中断接收	
/* USART0_RX DMA1_CH2 */
#define BSP_DMA_RCU   		RCU_DMA1            // DMA时钟
#define BSP_DMA  			DMA1 				// DMA
#define BSP_DMA_CH  		DMA_CH2				// DMA通道
#define BSP_DMA_CH_IRQ  	DMA1_Channel2_IRQn	// DMA中断
#define BSP_DMA_CH_IRQHandler DMA1_Channel2_IRQHandler // DMA 中断服务函数
#endif /* !RECEIVE_USART_INTERRUPT */

#endif /* USING_USART0 */

/* 串口1相关宏定义 */
#if USING_USART1
/* 串口时钟定义 */
#define BSP_USART1_RCU 		RCU_USART1
#define BSP_USART1_TX_RCU 	RCU_GPIOA
#define BSP_USART1_RX_RCU 	RCU_GPIOA

/* 串口引脚定义 */
#define BSP_USART1_TX_PORT 	GPIOA      	
#define BSP_USART1_TX_PIN 	GPIO_PIN_2	

#define BSP_USART1_RX_PORT 	GPIOA   	
#define BSP_USART1_RX_PIN 	GPIO_PIN_3 	

/* GPIO复用串口功能定义 */
#define BSP_USART1_TX_AF 	   	GPIO_AF_7
#define BSP_USART1_RX_AF 	   	GPIO_AF_7

/* 定义串口句柄 */
#define BSP_USART1 USART1

#if !RECEIVE_USART_INTERRUPT	//开启了DMA+中断接收	
/* USART0_RX DMA1_CH2 */
#define BSP_DMA_RCU   		RCU_DMA0            // DMA时钟
#define BSP_DMA  			DMA0 				// DMA
#define BSP_DMA_CH  		DMA_CH5				// DMA通道
#define BSP_DMA_CH_IRQ  	DMA0_Channel5_IRQn	// DMA中断
#define BSP_DMA_CH_IRQHandler DMA0_Channel5_IRQHandler // DMA 中断服务函数
#endif /* !RECEIVE_USART_INTERRUPT */

#endif /* USING_USART0 */



void dma_config(void);   // 配置DMA

extern uint8_t g_recv_buff[USART_RECEIVE_LENGTH]; // 接收缓冲区
extern uint16_t g_recv_length; // 接收数据长度
extern uint8_t g_recv_complete_flag; // 接收完成标志位

void uart_gpio_config(uint32_t band_rate); 		// 串口初始化函数,参数为波特率,默认使用串口0
void usart_send_data(uint8_t ucch);          	// 发送一个字符
void usart_send_string(uint8_t *ucstr);     	// 发送一个字符串

#endif /* _BSP_UART_H */
 

【注】:这个代码时我在梁山派(GD32F470ZGT6)上写的,还没有对其他芯片做兼容,但是大部分应该都没问题。

3.4 DMA接收不定长数据示例

main.c

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

#include "bsp_led.h"
#include "bsp_key.h"
#include "bsp_uart.h"

/*!
    \brief    main function
    \param[in]  none
    \param[out] none
    \retval     none
*/
int main(void)
{
	/* 配置中断优先级分组,4位抢占优先级,0位子优先级 */
	nvic_priority_group_set(NVIC_PRIGROUP_PRE4_SUB0);
	
	/* 配置sysTick的时钟和中断周期*/
    systick_config();
	
	/* 串口初始化 */
	uart_gpio_config(115200);
	
	
    while(1) 
	{
			
		/* 等待数据传输完成 */
		if(g_recv_complete_flag)  						// 数据接收完成
		{ 
			g_recv_complete_flag = 0;                   // 等待下次接收
			printf("g_recv_length:%d ",g_recv_length);  // 打印接收的数据长度
			printf("g_recv_buff:%s\r\n",g_recv_buff);	// 打印接收的数据
			memset(g_recv_buff,0,g_recv_length);		// 清空数组
			g_recv_length = 0;							// 清空长度
		}
	
    }
}

运行结果如下:
在这里插入图片描述

以上就是本期的所有内容,创造不易,点个关注再走呗。

在这里插入图片描述


http://www.kler.cn/a/380954.html

相关文章:

  • 使用WebStorm开发Vue3项目
  • 重读《人月神话》(12)-未雨绸缪(Plan to Throw One Away)
  • 2024年11月4日Github流行趋势
  • openstack之guardian介绍与实例创建过程
  • Vehicle OS软件平台解决方案
  • CentOS 7 安装 ntp,自动校准系统时间
  • 冒泡排序、选择排序、计数排序、插入排序、快速排序、堆排序、归并排序JAVA实现
  • 小新学习k8s第四天之发布管理
  • Pr 视频效果:透视
  • 【Nginx】前端项目开启 Gzip 压缩大幅提高页面加载速度
  • Ant Design Vue 的 a-table 行选择分页时bug处理
  • 官方redis安装
  • React Hooks 为什么不能在 if 语句中使用???
  • 根据提交的二维数据得到mysql建表和插入数据实用工具
  • 全渠道供应链打造中企业定制开发2+1链动模式S2B2C商城小程序的策略与影响
  • 【Python环境配置-Step1】PyCharm 2024最新官网下载、安装教程
  • PyTorch实践-CNN-验证码识别
  • 高可用架构-业务高可用
  • Android Studio:connect time out
  • Redis-基本了解
  • 数学期望和联合概率密度
  • 20241105编译Rockchip原厂的Android13并给荣品PRO-RK3566开发板刷机
  • 软设师知识点-计算机网络
  • CODESYS 输出日志 Log
  • Java如何实现企业微信审批流程
  • 《2024中国城市音乐产业发展指数报告》重磅发布