梁山派入门指南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;							// 清空长度
		}
	
    }
}

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

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

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/906007.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

notepad++ compare插件的离线下载和安装

一、离线安装 去改地址找到最新的插件&#xff1a;https://github.com/notepad-plus-plus/nppPluginList/blob/master/doc/plugin_list_x64.md下载之后复制到插件文件夹&#xff0c;插件文件夹的打开方式如下 注意目录&#xff1a; 二、问题汇总 &#xff08;1&#xff09…

你的网站需要防护吗?

你的网站经常被恶意爬虫&#xff0c;重要数据被批量搬运吗&#xff1f; 你想知道你的网站是不是安全的&#xff0c;有没有被 xss攻击、sql注入、命令注入等等这些乱七八糟的攻击手段攻击吗&#xff1f; 2014年我还是学生的时候&#xff0c;负责学院官网的维护&#xff0c;一…

在postman设置请求里带动态token,看看这两种方法!

问题描述 在使用postman调试接口时&#xff0c;遇到一些需要在请求里加上token的接口&#xff0c;若token出现变化&#xff0c;需要手动修改接口的token值&#xff0c;带来重复的工作量&#xff0c;翻看postman使用手册后&#xff0c;我发现了两种方法可以解决这个问题。 01 …

商家如何在高德地图上申请店铺入驻?

在当今数字化时代&#xff0c;互联网成为了消费者寻找商品和服务的主要渠道。高德地图作为国内领先的地图导航软件&#xff0c;不仅拥有庞大的用户基础&#xff0c;还为商家提供了优质的店铺展示平台。因此&#xff0c;对于实体店商家而言&#xff0c;入驻高德地图是提升店铺曝…

Java并发常见面试题总结(下)

Map&#xff08;重要&#xff09; HashMap 和 Hashtable 的区别 线程是否安全&#xff1a; HashMap 是非线程安全的&#xff0c;Hashtable 是线程安全的,因为 Hashtable 内部的方法基本都经过synchronized 修饰。&#xff08;如果你要保证线程安全的话就使用 ConcurrentHashMa…

数字化导师坚鹏:2025年银行开门红营销规划、方法及案例工作坊

2025年银行开门红营销规划、方法及案例工作坊 ——数字化赋能 新策略启航 开门红必胜 课程背景&#xff1a; 面对即将打响的开门红战役&#xff0c;很多银行存在以下问题&#xff1a; 不知道如何分析银行开门红面临形势及机遇&#xff1f; 不知道如何制定科学高效的开…

普通的Java程序员,需要深究源码吗?

作为Java开发者&#xff0c;面试肯定被问过多线程。对于它&#xff0c;大多数好兄弟面试前都是看看八股文背背面试题以为就OK了&#xff1b;殊不知现在的面试官都是针对一个点往深了问&#xff0c;你要是不懂其中原理&#xff0c;面试就挂了。可能你知道什么是进程什么是线程&a…

ctfshow——web(总结持续更新)

文章目录 1、基础知识部分2、php伪协议2.1 php://input协议2.2 data://text/plain协议 3、webshell连接工具3.1 蚁剑连接一句话木马 4、各个web中间件重要文件路径4.1 Nginx 5、sqlmap使用6、php特性6.1 md5加密漏洞 7、TOP 10漏洞7.1 SQL注入 1、基础知识部分 识别base64编码…

MYSQL---TEST5(Trigger触发器综合练习)

触发器Trigger 数据库mydb16_trigger创建 表的创建 goods create table goods( gid char(8) primary key, #商品号 name varchar(10), #商品名 price decimal(8,2), #价格 num int&#xff1b;&#xff09; #数量orders create tabl…

layui 自定义验证单选框必填

对于输入框类型必填验证&#xff0c;只需要在 input 输入框加入 lay-verify "required" 即可。但对于单选按钮这种特殊的该怎么办呢&#xff1f;layui 为我们提供了自定义验证。 1. 在单选按钮上添加自定义验证的名称 2. 验证规则如下 // 单选框自定义验证form.ve…

OpenCV开发笔记(八十二):两图拼接使用渐进色蒙版场景过渡缝隙

若该文为原创文章&#xff0c;转载请注明原文出处 本文章博客地址&#xff1a;https://hpzwl.blog.csdn.net/article/details/143432922 长沙红胖子Qt&#xff08;长沙创微智科&#xff09;博文大全&#xff1a;开发技术集合&#xff08;包含Qt实用技术、树莓派、三维、OpenCV…

「C/C++」C/C++标准库 之 #include<ctime> 时间日期库

✨博客主页何曾参静谧的博客&#x1f4cc;文章专栏「C/C」C/C程序设计&#x1f4da;全部专栏「VS」Visual Studio「C/C」C/C程序设计「UG/NX」BlockUI集合「Win」Windows程序设计「DSA」数据结构与算法「UG/NX」NX二次开发「QT」QT5程序设计「File」数据文件格式「PK」Parasoli…

安达发|零部件APS车间排程系统销售预测的优点

2024制造业面临着前所未有的挑战与机遇。为了保持竞争力&#xff0c;企业必须确保其生产系统能够高效、灵活地运作。在这方面&#xff0c;采用高级计划与排程系统&#xff0c;特别是零部件APS车间排程系统的预测方法&#xff0c;已成为提升生产效率和响应能力的关键策略。这种系…

【笔试刷题】笔记4

目录 1、过河卒 dfs bfs 动态规划 2、扑克牌顺子 排序 模拟 找规律 3、最长回文子串 中心拓展法 1、过河卒 5493. 过河卒 - AcWing题库 这道题我们很容易就能够想到dfs或bfs&#xff0c;但这两种算法都是会超时的 dfs #include <iostream> #include <v…

大模型中的token是什么;常见大语言模型的 token 情况

目录 大模型中的token是什么 常见大语言模型的 token 情况 大模型中的token是什么 定义 在大模型中,token 是文本处理的基本单位。它可以是一个字、一个词,或者是其他被模型定义的语言单元。简单来说,模型在理解和生成文本时,不是以完整的句子或段落为单位进行一次性处理…

kafka里的consumer 是推还是拉?

大家好&#xff0c;我是锋哥。今天分享关于【kafka里的consumer 是推还是拉&#xff1f;】面试题&#xff1f;希望对大家有帮助&#xff1b; kafka里的consumer 是推还是拉&#xff1f; 1000道 互联网大厂Java工程师 精选面试题-Java资源分享网 在Kafka中&#xff0c;消费者&…

少儿编程培训市场突破500亿元:教育新蓝海的崛起与未来展望

近年来&#xff0c;随着科技的迅速发展和家长对教育方式的重视&#xff0c;少儿编程市场成为一片新的蓝海。据最新市场调研报告显示&#xff0c;2024年中国少儿编程培训市场规模已突破500亿元&#xff0c;预计未来五年将持续增长。这一趋势反映了少儿编程教育的迅速崛起&#x…

【如何使用api接入星火大模型】(超详细,亲测有效!)

1 实现思路&#xff1a; 1.鉴权说明: 先在控制台创建应用&#xff0c;利用应用中提供的appid&#xff0c;APIKey&#xff0c; APISecret进行鉴权&#xff0c;生成最终请求的鉴权url。1.2 鉴权参数 host&#xff08;请求主机&#xff09;、date&#xff08;当前时间戳&#xff0…

国标GB28181软件EasyGBS国标GB28181网页直播平台在邮政快递场景中的应用

随着电子商务的迅猛发展&#xff0c;邮政快递行业迎来了前所未有的发展机遇&#xff0c;但同时也面临着诸多挑战。如何在保障货物安全、提高运输效率的同时&#xff0c;实现全面的监控和管理&#xff0c;成为邮政快递企业亟需解决的问题。国标GB28181网页直播平台EasyGBS作为一…

渗透测试练习题解析 7 (CTF web)

一、[红明谷CTF 2021]write_shell 1 考点&#xff1a; 1、PHP 短标签 2、 符号的使用 通过代码可知 check 是一个过滤函数&#xff0c;利用正则的方式过滤掉 空格、php、eval 等一些关键字或符号&#xff0c;$dir 是路径&#xff0c;这个值可以通过 actionpwd 获取到&#…