梁山派入门指南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 梁山派上的串口开发
- 第一步:了解GD32F470ZGT6上的串口资源.
【注】:数据手册第67页
- 可以看到梁山派上一共有八个串口,最大时钟频率为15MHz。
- 串口0、1、2、5为USART(通用同步/异步收发器)
- 串口3、4、6、7为UART(异步收发器)
- 串口0和5挂载在APB2总线上
- 其他串口挂载到APB1总线上
【注】:数据手册第10页
-
第二步:查询梁山派上串口相关的原理图
可以看到,梁山派上是默认使用PA9和PA10作为串口的发送和介绍引脚的(实际上大部分板子上都是这样的),了解了基本信息,我们就可以来查阅用户手册中串口相关的部分,开始配置串口。 -
第三步:配置串口
下图是梁山派用户手册中提供的具体配置流程:
有了这个之后,我们就可以根据手册进行一步一步的配置,或者自己查阅固件库手册,自行配置,不管哪种结果,下面是配置好的样子:
/* 这部分是宏定义,需要放到对应的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);
}
- 完成了串口配置之后,我们就可以编写对应的功能函数,比如发送数据,发送字符串和串口重定向,就像下面这样:
/************************************************
函数名称 : 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)
- 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)上写的,还没有对其他芯片做兼容,但是大部分应该都没问题。
- 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.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 */
}
- 编写中断服务函数:
/************************************************
函数名称 : 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);
}
}
- 举一反三,引入对USART1的兼容,这样你就可以得到一个完整的bsp_uart文件(这个bsp_uart文件我放在 2.3 小节中)
2.3 bsp_uart文件(发送和中断接收,兼容串口0和串口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)上写的,还没有对其他芯片做兼容,但是大部分应该都没问题。
- 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);
}
- 编写中断服务函数
/************************************************
函数名称 : 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的兼容,并且将DMA接收和中断接收同时兼容到bsp_uart文件中,这样你就得到了一个完美的bsp_uart文件(这个bsp_uart文件我放在 2.4 小节中)
3.3 bsp_uart文件(发送、中断接收和DMA接收二合一,兼容串口0和串口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)上写的,还没有对其他芯片做兼容,但是大部分应该都没问题。
- 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; // 清空长度
}
}
}
运行结果如下:
以上就是本期的所有内容,创造不易,点个关注再走呗。