梁山派入门指南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_USART0usart_data_transmit(BSP_USART0,(uint8_t)ucch);							 // 发送数据while(RESET == usart_flag_get(BSP_USART0,USART_FLAG_TBE));  // 等待发送数据缓冲区标志置位
#endif /* USING_USART0 */#if USING_USART1usart_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 USART0void 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_USART0usart_data_transmit(BSP_USART0,(uint8_t)ucch);							 // 发送数据while(RESET == usart_flag_get(BSP_USART0,USART_FLAG_TBE));  // 等待发送数据缓冲区标志置位
#endif /* USING_USART0 *//* 使用串口1 */
#if USING_USART1usart_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_USART0usart_data_transmit(BSP_USART0,(uint8_t)ucch);							 // 发送数据while(RESET == usart_flag_get(BSP_USART0,USART_FLAG_TBE));  // 等待发送数据缓冲区标志置位
#endif /* USING_USART0 *//* 使用串口1 */
#if USING_USART1usart_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_INTERRUPTvoid 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_USART0dma_init_struct.periph_addr = (uint32_t)&USART_DATA(BSP_USART0);   	// 外设地址#endif#if USING_USART1dma_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);      // 把接收到的数据放到缓冲区中}#endifif(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进行传输#endifg_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);      // 把接收到的数据放到缓冲区中}#endifif(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进行传输#endifg_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);   // 配置DMAextern 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.mzph.cn/bicheng/68370.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Linux和Docker常用终端命令:保姆级图文详解

文章目录 前言1、Docker 常用命令1.1、镜像管理1.2、容器管理1.3、网络管理1.4、数据卷管理1.5、监控和性能管理 2、Linux 常用命令分类2.1、文件和目录管理2.2、用户管理2.3、系统监控和性能2.4、软件包管理2.5、网络管理 前言 亲爱的家人们&#xff0c;创作很不容易&#xf…

智能科技与共情能力加持,哈曼重新定义驾乘体验

2025年1月6日&#xff0c;拉斯维加斯&#xff0c;2025年国际消费电子展——想象一下&#xff0c;当您步入一辆汽车&#xff0c;它不仅能响应您的指令&#xff0c;更能理解您的需求、适应您的偏好&#xff0c;并为您创造一个独特且专属的交互环境。作为汽车科技领域的知名企业和…

关于2025年智能化招聘管理系统平台发展趋势

2025年&#xff0c;招聘管理领域正站在变革的十字路口&#xff0c;全新的技术浪潮与不断变化的职场生态相互碰撞&#xff0c;促使招聘管理系统成为重塑企业人才战略的关键力量。智能化招聘管理系统平台在这一背景下迅速崛起&#xff0c;其发展趋势不仅影响企业的招聘效率与质量…

机器视觉5-全连接神经网络

机器视觉5-全连接神经网络1 图像表示多层感知器全连接神经网络一、两层全连接网络表达式二、三层全连接网络表达式三、关于非线性操作的说明四、全连接神经网络的映射原理 全连接神经网络的权值一、线性分类器二、两层全连接网络三、总结 全连接神经网络线性不可分全连接神经网…

解锁转型密码:不同方向的技能与素质修炼手册

前言&#xff1a;哈喽&#xff0c;大家好&#xff0c;今天给大家分享一篇文章&#xff01;并提供具体代码帮助大家深入理解&#xff0c;彻底掌握&#xff01;创作不易&#xff0c;如果能帮助到大家或者给大家一些灵感和启发&#xff0c;欢迎收藏关注哦 &#x1f495; 目录 解锁…

ChatGPT提示词合集(国内大模型可参考使用)

行为迅速的Linux终端我想让你充当 linux 终端。我将输入命令&#xff0c;您将回复终端应显示的内容。我希望您只在一个唯一的代码块内回复终端输出&#xff0c;而不是其他任何内容。不要写解释。除非我指示您这样做&#xff0c;否则不要键入命令。当我需要用英语告诉你一些事情…

第三十八章 Spring之假如让你来写MVC——适配器篇

Spring源码阅读目录 第一部分——IOC篇 第一章 Spring之最熟悉的陌生人——IOC 第二章 Spring之假如让你来写IOC容器——加载资源篇 第三章 Spring之假如让你来写IOC容器——解析配置文件篇 第四章 Spring之假如让你来写IOC容器——XML配置文件篇 第五章 Spring之假如让你来写…

深度剖析RabbitMQ:从基础组件到管理页面详解

文章目录 一、简介二、Overview2.1 Overview->Totals2.2 Overview->Nodesbroker的属性2.3 Overview->Churn statistics2.4 Overview->Ports and contexts2.5 Overview->Export definitions2.6 Overview->Import definitions 三、Connections连接的属性 四、C…

使用 Python 编写一个简单的聊天机器人

&#x1f496; 欢迎来到我的博客&#xff01; 非常高兴能在这里与您相遇。在这里&#xff0c;您不仅能获得有趣的技术分享&#xff0c;还能感受到轻松愉快的氛围。无论您是编程新手&#xff0c;还是资深开发者&#xff0c;都能在这里找到属于您的知识宝藏&#xff0c;学习和成长…

Unity 自定义批量打包工具

打包配置项 using UnityEngine; using System.Collections.Generic;namespace MYTOOL.Build {[System.Flags]public enum VersionOptions{None 0,Major 1,Minor 4,Build 8,Revision 0x10,}/// <summary>/// 批量打包配置文件/// </summary>[CreateAssetMenu]…

JAVA实现五子棋小游戏(附源码)

文章目录 一、设计来源捡金币闯关小游戏讲解1.1 主界面1.2 黑棋胜利界面1.3 白棋胜利界面 二、效果和源码2.1 动态效果2.2 源代码 源码下载更多优质源码分享 作者&#xff1a;xcLeigh 文章地址&#xff1a;https://blog.csdn.net/weixin_43151418/article/details/145161039 JA…

Flink概述

一、Flink是什么 二、Flink特点 三、Flink vs SparkStreaming 表 Flink 和 Streaming对比 Flink Streaming 计算模型 流计算 微批处理 时间语义 事件时间、处理时间 处理时间 窗口 多、灵活 少、不灵活&#xff08;窗口必须是批次的整数倍&#xff09; 状态 有 …

基于SpringBoot的企业级工位管理系统【源码+文档+部署讲解】

系统介绍 基于SpringBootVue实现的企业级工位管理系统采用前后端分离架构方式&#xff0c;系统设计了管理员、员工两种角色&#xff0c;系统实现了用户登录与注册、个人中心、员工管理、部门信息管理、工位信息管理、使用情况管理、工位分配管理等功能。 技术选型 开发工具&…

Linux系统离线部署MySQL详细教程(带每步骤图文教程)

1、登录官网下载对应的安装包 MySQL :: Developer Zone 2、将压缩包上传到服务器上&#xff0c;这里直接上传到/usr/local路径上 使用sftp工具上传到/usr/local目录上 3、解压压缩包 tar -xf mysql-8.0.39-linux-glibc2.17-x86_64.tar.xz 4、将mysql-8.0.39-linux-glibc2.17…

leetcode刷题记录(六十一)——73. 矩阵置零

&#xff08;一&#xff09;问题描述 73. 矩阵置零 - 力扣&#xff08;LeetCode&#xff09;73. 矩阵置零 - 给定一个 m x n 的矩阵&#xff0c;如果一个元素为 0 &#xff0c;则将其所在行和列的所有元素都设为 0 。请使用 原地 [http://baike.baidu.com/item/%E5%8E%9F%E5%9…

人机交互(包含推荐软件)

视觉交互、语音交互、笔式交互、触觉交互、虚拟环境交互。 主要的研究方面包括&#xff1a;人机交互界面表示模型与设计方法、可用性工程、可用性评估模型和方法、多模态智能交互技术、智能交互认知技术、语音识别交互、web界面交互设计、移动界面交互设计。 交互设计流程&am…

解锁未来情感科技:AI 机器人 Ropet 搭载的前沿智能黑科技

2025年的国际消费电子产品展览会&#xff08;CES&#xff09;上&#xff0c;一只可爱的“毛绒玩具”成了全场焦点。 当然&#xff0c;这并不是一个单纯的玩偶&#xff0c;而是和《超能陆战队》的大白一样温暖的陪伴机器人。 相信有很多人和小编一样&#xff0c;当年看完《超能…

HarmonyOS 鸿蒙 ArkTs(5.0.1 13)实现Scroll下拉到顶刷新/上拉触底加载,Scroll滚动到顶部

HarmonyOS 鸿蒙 ArkTs(5.0.1 13)实现Scroll下拉到顶刷新/上拉触底加载 效果展示 使用方法 import LoadingText from "../components/LoadingText" import PageToRefresh from "../components/PageToRefresh" import FooterBar from "../components/…

Flink(八):DataStream API (五) Join

1. Window Join Window join 作用在两个流中有相同 key 且处于相同窗口的元素上。这些窗口可以通过 window assigner 定义&#xff0c;并且两个流中的元素都会被用于计算窗口的结果。两个流中的元素在组合之后&#xff0c;会被传递给用户定义的 JoinFunction 或 FlatJoinFunct…

Flink CDC 在阿里云实时计算Flink版的云上实践

摘要&#xff1a;本文整理自阿里云高级开发工程师&#xff0c;Apache Flink Committer 阮航老师在 Flink Forward Asia 2024 生产实践&#xff08;三&#xff09;专场中的分享&#xff0c;主要分为以下四个方面&#xff1a; Flink CDC & 实时计算 Flink CDC YAML 核心功能…