提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
前言
串口通信是设备之间十分常见的数据通信方式,由于占用的硬件资源极少、通信协议简单以及易于使用等优势,串口成为微控制器中使用最频繁的通信接口之一。通过串口,微控制器不仅可以与计算机进行数据通信,还可以进行程序调试,甚至可以连接蓝牙、Wi-Fi和传感器等外部硬件模块,从而拓展更多的功能。在芯片选型时,串口数量也是工程师参考的重要指标之一。因此,掌握串口的相关知识及其用法,是微控制器学习的一个重要环节。 本文将详细介绍GD32F30x系列微控制器的串口功能框图、串口部分寄存器和固件库、异常和中断、NVIC寄存器和固件库,以及串口模块驱动设计。最后,通过一个实例介绍串口驱动的设计和应用。
一、实验内容
基于GD32F3苹果派开发板设计一个串口通信实验,每秒通过printf向计算机发送一条语句(ASCII格式),如This is the first GD32F303 Project, by Zhangsan,在计算机上通过串口助手显示。另外,计算机上的串口助手向开发板发送1字节数据(HEX格式),开发板收到后,进行加1处理,再发送回计算机,通过串口助手显示出来。例如,计算机通过串口助手向开发板发送0x15,开发板收到后,进行加1处理,向计算机发送0x16。
二、实验原理
1.串口通信协议
串口在不同的物理层上可分为UART口、COM口和USB口等,在电平标准上又可分为TTL、RS232和RS485等,下面主要介绍基于TTL电平标准的UART。 通用异步串行收发器(Universal Asynchronous Receiver/Transmitter,UART)是微控制器领域十分常用的通信设备,还有一种同步异步串行收发器(Universal Synchronous/Asynchronous Receiver/Transmitter,USART)。二者的区别是USART既可以进行同步通信,也可以进行异步通信,而UART只能进行异步通信。简单区分同步和异步通信的方式是根据通信过程中是否使用到时钟信号,在同步通信中,收发设备之间会通过一根信号线表示时钟信号,在时钟信号的驱动下同步数据,而异步通信不需要时钟信号进行数据同步。 相较于USART的同步通信功能,其异步通信功能使用更为频繁。当使用USART进行异步通信时,其用法与UART没有区别,只需要两根信号线和一根共地线即可完成双向通信,本实验即使用USART的异步通信功能来实现。下面介绍异步通信,即UART通信协议及其通信原理。 (1)UART物理层 UART采用异步串行全双工通信的方式,因此UART通信没有时钟线,通过两根数据线可实现双向同时传输。收发数据只能一位一位地在各自的数据线上传输,因此UART最多只有两根数据线,一根是发送数据线,一根是接收数据线。数据线是高低逻辑电平传输,因此还必须有参照的地线。最简单的UART接口由发送数据线TXD、接收数据线RXD和GND线组成。 UART一般采用TTL的逻辑电平标准表示数据,逻辑1用高电平表示,逻辑0用低电平表示。在TTL电平标准中,高/低电平为范围值,通常规定,引脚作 为输出时,电压低于0.4V稳定输出低电平,电压高于2.4V稳定输出高电平;引脚作为输入时,电压低于0.8V稳定输入低电平,电压高于2V稳定输入高电平。微控制器通常也采用TTL电平标准,但其对引脚输入输出高低电平的电压范围有额外的规定,实际应用时需要参考数据手册。 两个UART设备的连接非常简单,如下图所示,只需要将UART设备A的发送数据线TXD与UART设备B的接收数据线RXD相连接,将UART设备A的接收数据线RXD与UART设备B的发送数据线TXD相连接,此外,两个UART设备必须共地,即将两个设备的GND相连接。 (2)UART数据格式 UART数据按照一定的格式打包成帧,微控制器或计算机在物理层上是以帧为单位进行传输的。UART的一帧数据由起始位、数据位、校验位、停止位和空闲位组成,如下图所示。注意,一个完整的UART数据帧必须有起始位、数据位和停止位,但不一定有校验位和空闲位。 (a)起始位的长度为1位,起始位的逻辑电平为低电平。由于UART空闲状态时的电平为高电平,因此,在每一个数据帧的开始,需要先发出一个逻辑0,表示传输开始。 (b)数据位的长度通常为8位,也可以为9位;每个数据位的值可以为逻辑0,也可以为逻辑1,而且传输采用的是小端方式,即最低位(D0)在前,最高位(D7)在后。 (c)校验位不是必需项,因此可以将UART配置为没有校验位,即不对数据位进行校验;也可以将UART配置为带奇偶校验位。如果配置为带奇偶校验位,则校验位的长度为1位,校验位的值可以为逻辑0,也可以为逻辑1。在奇校验方式下,如果数据位中有奇数个逻辑1,则校验位为0;如果数据位中有偶数个逻辑1,则校验位为1。在偶校验方式下,如果数据位中有奇数个逻辑1,则校验位为1;如果数据位中有偶数个逻辑1,则校验位为0。 (d)停止位的长度可以是1位、1.5位或2位,通常情况下停止位是1位。停止位是一帧数据的结束标志,由于起始位是低电平,因此停止位为高电平。 (e)空闲位是当数据传输完毕后,线路上保持逻辑1电平的位,表示当前线路上没有数据传输。 (3)UART传输速率 UART传输速率用比特率来表示。比特率是每秒传输的二进制位数,单位为bps(bit per second)。波特率,即每秒传送码元的个数,单位为bps。由于UART使用NRZ(Non-Return to Zero,不归零)编码,因此UART的波特率和比特率是相同的。在实际应用中,常用的UART传输速率有1200bps、2400bps、4800bps、9600bps、19200bps、38400bps、57600bps和115200bps。 如果数据位为8位,校验方式为奇校验,停止位为1位,波特率为115200bps,计算每2ms最多可以发送多少字节数据。首先,通过计算可知,一帧数据有11位(1位起始位+8位数据位+1位校验位+1位停止位),其次,波特率为115200bps,即每秒传输115200bit,于是,每毫秒可以传输115.2bit,由于每帧数据有11位,因此每毫秒可以传输10字节数据,2ms就可以传输20字节数据。 综上所述,UART是以帧为单位进行数据传输的。一个UART数据帧由1位起始位、5~9位数据位、0位/1位校验位、1位/1.5位/2位停止位组成。除了起始位,其他3部分必须在通信前由通信双方设定好,即通信前必须确定数据位和停止位的位数、校验方式,以及波特率。这就相当于两个人在通过电话交谈之前,要先确定好交谈所使用的语言,否则,一方使用英语,另外一方使用汉语,就无法进行有效的交流。 (4)UART通信实例 由于UART采用异步串行通信,没有时钟线,只有数据线。那么,收到一个UART原始波形,如何确定一帧数据?如何计算传输的是什么数据?下面以一个UART波形为例来说明,假设UART波特率为115200bps,数据位为8位,无奇偶校验位,停止位为1位。 如下图所示,第1步,获取UART原始波形数据;第2步,按照波特率进行中值采样,每位的时间宽度为1/115200s≈8.68μs,将电平第一次由高到低的转换点作为基准点,即0μs时刻,在4.34μs时刻采样第1个点,在13.02μs时刻采样第2个点,依次类推,然后判断第10个采样点是否为高电平,如果为高电平,表示完成一帧数据的采样;第3步,确定起始位、数据位和停止位,采样的第1个点即为起始位,且起始位为低电平,采样的第2个点至第9个点为数据位,其中第2个点为数据最低位,第9个点为数据最高位,第10个点为停止位,且停止位为高电平。
2.串口电路原理图
串口硬件电路如下图所示,主要为USB转串口模块电路,包括Type-C型USB接口(编号为USB1)、USB转串口芯片CH340G(编号为U104)和12MHz晶振等。Type-C接口的UD1+和UD1-网络为数据传输线(使用USB通信协议),这两根线各通过一个22Ω电阻连接到CH340G芯片的UD+和UD-引脚。CH340G芯片可以实现USB通信协议和标准UART串行通信协议的转换,因此,还需将CH340G芯片的一对串口连接到GD32F303ZET6芯片的串口,这样即可实现GD32F3苹果派开发板通过Type-C接口与计算机进行数据通信。这里将CH340G芯片的TXD引脚通过CH340_TX网络连接到GD32F303ZET6芯片的PA10引脚(USART0_RX),将CH340G芯片的RXD引脚通过CH340_RX网络连接到GD32F303ZET6芯片的PA9引脚(USART0_TX)。此外,两颗芯片还需要共地。 注意,在CH340G和GD32F303ZET6之间添加了一个2×2Pin的排针,在进行串口通信实验之前,需要先使用两个跳线帽分别连接1号脚(CH340_TX)和2号脚(USART0_RX)、3号脚(CH340_RX)和4号脚(USART0_TX)。
3.串口功能框图
如下图所示是串口功能框图,下面依次介绍串口的功能引脚、数据寄存器、控制器和波特率发生器。
4.串口部分寄存器
本实验涉及的USART寄存器包括USART状态寄存器0(USART_STAT0)、USART数据寄存器(USART_DATA)、USART波特率寄存器(USART_BAUD)、USART控制寄存器0(USART_CTL0)和USART控制寄存器1(USART_CTL1)。 上述寄存器及更多其他寄存器的定义和功能参见《GD32F30x用户手册(中文版)》,其中介绍串口寄存器的部分位于第447~458页,也可参考《GD32F30x用户手册(英文版)》,对应页码为第469~481页。
5.串口部分固件库函数
本实验涉及的串口部分固件库函数有usart_deinit、usart_baudrate_set、usart_stop_bit_set、usart_word_length_set、usart_parity_config、usart_receive_config、usart_transmit_config、usart_enable、usart_interrupt_enable、usart_interrupt_disable、usart_data_transmit、usart_data_receive、usart_flag_get、usart_flag_clear、usart_interrupt_flag_get和usart_interrupt_flag_clear。这些函数在gd32f30x_usart.h文件中声明,在gd32f30x_usart.c文件中实现。关于以上固件库函数及更多其他串口固件库函数的的函数原型、输入/输出参数及用法等信息请参见《GD32F30x固件库使用指南》,其中,介绍串口固件库函数的部分位于第632~675页。
6.异常和中断、NVIC中断控制器、寄存器、固件库函数
本实验涉及的串口异常和中断、NVIC中断控制器、寄存器、固件库函数请查阅《GD32F30x用户手册(中文版)》和《GD32F30x固件库使用指南》。
7.串口模块驱动设计
串口模块驱动设计是本实验的核心,下面按照队列与循环队列、循环队列Queue模块函数、串口数据接收和数据发送路径,以及printf实现过程的顺序对串口模块进行介绍。 (1)队列与循环队列 队列是一种先入先出(FIFO)的线性表,它只允许在表的一端插入元素,在另一端取出元素,即最先进入队列的元素最先离开。在队列中,允许插入的一端称为队尾(rear),允许取出的一端称为队头(front)。 有时为了方便,将顺序队列臆造为一个环状的空间,称之为循环队列。下面举一个简单的例子。假设指针变量pQue指向一个队列,该队列为结构体变量,队列的容量为8,如下图所示。(a)起初,队列为空,队头pQue→front和队尾pQue→rear均指向地址0,队列中的元素数量为0;(b)插入J0、J1、…、J5这6个元素后,队头pQue→front依然指向地址0,队尾pQue→rear指向地址6,队列中的元素数量为6;(c)取出J0、J1、J2、J3这4个元素后,队头pQue→front指向地址4,队尾pQue→rear指向地址6,队列中的元素数量为2;(d)继续插入J6、J7、…、J11这6个元素后,队头pQue→front指向地址4,队尾pQue→rear也指向地址4,队列中的元素数量为8,此时队列为满。
(2)循环队列Queue模块函数 本实验使用到Queue模块,该模块有6个API函数,分别为InitQueue、ClearQueue、QueueEmpty、QueueLength、EnQueue和DeQueue。 (3)串口数据接收和数据发送路径 在快递柜出现以前,寄送快递的流程大致如下:①打电话给快递员,并等待快递员上门取件;②快递员到寄方取快递,并将快递寄送出去。同理,收快递也类似:①快递员通过快递公司拿到快递;②快递员打电话给收方,并约定派送时间;③快递员在约定时间将快递派送给收方。显然,这种传统的方式效率很低,因此,快递柜应运而生,快递柜相当于一个缓冲区,可以将寄件的快递柜称为寄件缓冲区,将取件的快递柜称为取件缓冲区,当然,在现实生活中,寄件和取件缓冲区是共用的。因此,新的寄送快递流程就变为:①寄方将快递投放到快递柜;②快递员在一个固定的时间从快递柜中取出每个寄方的快递,并将其通过快递公司寄送出去。同样,新的收快递流程为:①快递员从快递公司拿到快递;②统一将这些快递投放到各个快递柜中;③收方随时都可以取件。本书中的串口数据接收和数据发送过程与基于快递柜的快递收发流程十分相似。 本实验中的串口模块包含串口发送缓冲区和串口接收缓冲区,二者均为结构体,串口的数据接收和发送过程如下图所示。数据发送过程(写串口)分为三步:①调用WriteUART0函数将待发送的数据通过EnQueue函数写入发送缓冲区,同时开启中断使能;②当数据发送寄存器为空时,产生中断,在串口模块的USART0_IRQHandler中断服务函数中,通过ReadSendBuf函数调用DeQueue函数,取出发送缓冲区中的数据,再通过usart_data_transmit函数将待发送的数据写入USART数据寄存器(USART_DATA);③微控制器的硬件会将USART_DATA中的数据写入发送移位寄存器,然后按位将发送移位寄存器中的数据通过TX端口发送出去。数据接收过程(读串口)与写串口过程相反:①当微控制器的接收移位寄存器接收到一帧数据时,会由硬件将接收移位寄存器的数据发送到USART数据寄存器(USART_DATA),同时产生中断;②在串口模块的USART0_IRQHandler中断服务函数中,通过usart_data_receive函数读取USART_DATA,并通过WriteReceiveBuf函数调用EnQueue函数,将接收到的数据写入接收缓冲区;③调用ReadUART0函数读取接收到的数据。 (4)printf实现过程 串口在微控制器领域,除了用于数据传输,还可以对微控制器系统进行调试。C语言中的标准库函数printf可用于在控制台输出各种调试信息。GD32微控制器的集成开发环境,如Keil、IAR等也支持标准库函数。本书基于Keil集成开发环境,实验1已经涉及printf函数,而且printf函数输出的内容通过串口发送到计算机上的串口助手显示。 printf函数如何通过串口输出信息?fputc函数是printf函数的底层函数,因此,只需要对fputc函数进行改写即可,如程序清单所示。
int fputc(int ch, FILE *f)
{
usart_data_transmit(USART0, (unsigned char) ch);
while(RESET == usart_flag_get(USART0, USART_FLAG_TBE));
return ch;
}
fputc函数实现之后,还需要在Keil集成开发环境中勾选Options for Target→Target→Use MicroLIB,即启用微库(MicroLIB)。也就是说,不仅要重写fputc函数,还要启用微库,才能使用printf输出调试信息。
8.程序架构
串口通信实验的程序架构如下图所示。该图简要介绍了程序开始运行后各个函数的执行和调用流程,图中仅列出了与本实验相关的一部分函数。下面解释说明程序架构图。 (1)在main主函数中调用InitHardware函数进行硬件相关模块初始化,包含RCU、NVIC、UART和LED等模块,这里仅介绍串口模块初始化函数InitUART0。在InitUART0函数中先调用InitUARTBuf函数初始化串口缓冲区,再调用ConfigUART函数进行串口配置。 (2)调用InitSoftware函数进行软件相关模块初始化,本实验中,InitSoftware函数为空。 (3)调用Proc2msTask函数进行2ms任务处理,在该函数中,调用ReadUART0函数读取串口接收缓冲区中的数据,对数据进行处理(加1操作)后,再通过调用WriteUART0函数将数据写入串口发送缓冲区。 (4)2ms任务之后再调用Proc1SecTask函数进行1s任务处理,在该函数中,调用printf函数打印字符串,而重定向函数fputc为printf的底层函数,其功能是实现基于串口的信息输出。 (5)Proc2msTask和Proc1SecTask均在while循环中调用,因此,Proc1SecTask函数执行完后将再次执行Proc2msTask函数。 在下图中,编号①、⑤、⑥和⑨的函数在Main.c文件中声明和实现;编号②、⑦和⑧的函数在UART0.h文件中声明,在UART0.c文件中实现;编号③和④的函数在UART0.c文件中声明和实现。串口的数据收发还涉及UART0.c文件中的WriteReceiveBuf、ReadSendBuf和USART0_IRQHandler等函数,未在图中体现,具体的调用流程参考上图。USART0_IRQHandler为USART0的中断服务函数,当USART0产生中断时会自动调用该函数,该函数的函数名可在ARM分组下的startup_gd32f30x_hd.s启动文件中查找到,启动文件中列出了GD32F30x系列微控制器的所有中断服务函数名,后续实验使用到的其他中断服务函数的函数名也可以在该文件中查找。 本实验编程要点: 1.串口配置,包括时钟使能、GPIO配置、USART0配置和中断配置。 2.数据收发,包括串口缓冲区和4个读写串口缓冲区函数之间的数据流向与处理。 3.USART0中断服务函数的编写,包括中断标志的获取和清除,数据寄存器的读写等。 串口通信的核心即为数据收发,掌握以上编程要点即可快速完成本实验。
三、实验步骤和代码解析
1.复制并编译原始工程
首先,将“D:\GD32F3KeilTest\Material\05.UARTCommunication”文件夹复制到“D:\GD32F3KeilTest\Product”文件夹中。然后,双击运行“D:\GD32F3KeilTest\Product\05. UARTCommunication\Project”文件夹中的GD32KeilPrj.uvprojx。编译通过后,下载程序并进行复位,观察开发板上的两个LED是否交替闪烁。如果两个LED交替闪烁,表示原始工程是正确的,可以进入下一步操作。
2.添加UART0和Queue文件对
首先,将“D:\GD32F3KeilTest\Product\05.UARTCommunication\HW\UART0”文件夹中的UART0.c和Queue.c文件添加到HW分组中。然后,将“D:\GD32F3KeilTest\Product\05. UARTCommunication\HW\UART0”路径添加到Include Paths栏中。
3.完善UART0.h文件
单击按钮,进行编译。编译结束后,在Project面板中,双击UART0.c下的UART0.h文件。在“包含头文件”区,添加代码#include <stdio.h>。然后在“宏定义”区添加缓冲区大小宏定义代码,如下程序清单所示。
1.
4.#include <stdio.h>
5.
6.
9.#define UART0_BUF_SIZE 100
在“API函数声明”区,添加如下程序清单所示的API函数声明代码。其中,InitUART0函数用于初始化UART0模块;WriteUART0函数用于写串口,可以写若干字节;ReadUART0函数用于读串口,可以读若干字节。
void InitUART0(unsigned int bound);
unsigned char WriteUART0(unsigned char *pBuf, unsigned char len);
unsigned char ReadUART0(unsigned char *pBuf, unsigned char len);
4.完善UART0.c文件
在UART0.c文件的“包含头文件”区的最后,添加代码#include "gd32f30x_conf.h"和#include “Queue.h”。 在“枚举结构体”区,添加如下程序清单所示的枚举声明代码。枚举EnumUARTState中的UART_STATE_OFF表示串口关闭,对应的值为0;UART_STATE_ON表示串口打开,对应的值为1。
1.
2.typedef enum
3.{
4. UART_STATE_OFF,
5. UART_STATE_ON,
6. UART_STATE_MAX
7.}EnumUARTState;
在“内部变量定义”区,添加内部变量的定义代码,如下程序清单所示。其中,s_structUARTSendCirQue是串口发送缓冲区,s_structUARTRecCirQue是串口接收缓冲区,s_arrSendBuf是发送缓冲区的数组,s_arrRecBuf是接收缓冲区的数组,s_iUARTTxSts是串口发送状态位,该位为1表示串口正在发送数据,为0表示串口数据发送完成。
1.static StructCirQue s_structUARTSendCirQue;
2.static StructCirQue s_structUARTRecCirQue;
3.static unsigned char s_arrSendBuf[UART0_BUF_SIZE];
4.static unsigned char s_arrRecBuf[UART0_BUF_SIZE];
5.static unsigned char s_iUARTTxSts;
在“内部函数声明”区,添加内部函数的声明代码,如下程序清单所示。其中,InitUARTBuf函数用于初始化串口缓冲区,WriteReceiveBuf函数用于将接收到的数据写入接收缓冲区,ReadSendBuf函数用于读取发送缓冲区中的数据,ConfigUART函数用于配置UART,EnableUARTTx函数用于使能串口发送。
1.static void InitUARTBuf(void);
2.static unsigned char WriteReceiveBuf(unsigned char d);
3.static unsigned char ReadSendBuf(unsigned char *p);
4.
5.static void ConfigUART(unsigned int bound);
static void EnableUARTTx(void);
在“内部函数实现”区,添加InitUARTBuf函数的实现代码,如下程序清单所示。InitUARTBuf函数主要对发送缓冲区s_structUARTSendCirQue和接收缓冲区s_structUARTRecCirQue进行初始化,将发送缓冲区中的s_arrSendBuf数组和接收缓冲区中的s_arrRecBuf数组全部清零,同时将两个缓冲区的容量均配置为宏定义UART0_BUF_SIZE。
1.static void InitUARTBuf(void)
2.{
3. signed short i;
4.
5. for(i = 0; i < UART0_BUF_SIZE; i++)
6. {
7. s_arrSendBuf[i] = 0;
8. s_arrRecBuf[i] = 0;
9. }
10.
11. InitQueue(&s_structUARTSendCirQue, s_arrSendBuf, UART0_BUF_SIZE);
12. InitQueue(&s_structUARTRecCirQue, s_arrRecBuf, UART0_BUF_SIZE);
13.}
在“内部函数实现”区的InitUARTBuf函数实现区后,添加WriteReceiveBuf和ReadSendBuf函数的实现代码,如下程序清单所示。其中,WriteReceiveBuf函数调用EnQueue函数,将数据写入接收缓冲区s_structUARTRecCirQue;ReadSendBuf函数调用DeQueue函数,读取发送缓冲区s_structUARTSendCirQue中的数据。
1.static unsigned char WriteReceiveBuf(unsigned char d)
2.{
3. unsigned char ok = 0;
4.
5. ok = EnQueue(&s_structUARTRecCirQue, &d, 1);
6.
7. return ok;
8.}
9.
10.static unsigned char ReadSendBuf(unsigned char *p)
11.{
12. unsigned char ok = 0;
13.
14. ok = DeQueue(&s_structUARTSendCirQue, p, 1);
15.
16. return ok;
17.}
在“内部函数实现”区的ReadSendBuf函数实现区后,添加ConfigUART函数的实现代码,如下程序清单所示。
1.static void ConfigUART(unsigned int bound)
2.{
3. rcu_periph_clock_enable(RCU_GPIOA);
4. rcu_periph_clock_enable(RCU_USART0);
5.
6.
7. gpio_init(GPIOA, GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_9);
8.
9.
10. gpio_init(GPIOA, GPIO_MODE_IN_FLOATING, GPIO_OSPEED_50MHZ, GPIO_PIN_10);
11.
12.
13. usart_deinit(USART0);
14. usart_baudrate_set(USART0, bound);
15. usart_stop_bit_set(USART0, USART_STB_1BIT);
16. usart_word_length_set(USART0, USART_WL_8BIT);
17. usart_parity_config(USART0, USART_PM_NONE);
18. usart_receive_config(USART0, USART_RECEIVE_ENABLE);
19. usart_transmit_config(USART0, USART_TRANSMIT_ENABLE);
20.
21. usart_interrupt_enable(USART0, USART_INT_RBNE);
22. usart_interrupt_enable(USART0, USART_INT_TBE);
23. usart_enable(USART0);
24.
25. nvic_irq_enable(USART0_IRQn, 0, 0);
26.
27. s_iUARTTxSts = UART_STATE_OFF;
28.}
在“内部函数实现”区的ConfigUART函数实现区后,添加EnableUARTTx函数的实现代码,如下程序清单所示。EnableUARTTx函数实际上是将s_iUARTTxSts变量赋值为UART_STATE_ON,并调用usart_interrupt_enable函数使能发送缓冲区空中断,该函数在WriteUART0函数中调用,即每次发送数据之后,调用该函数使能发送缓冲区空中断。
1.static void EnableUARTTx(void)
2.{
3. s_iUARTTxSts = UART_STATE_ON;
4.
5. usart_interrupt_enable(USART0, USART_INT_TBE);
6.}
在“内部函数实现”区的EnableUARTTx函数实现区后,添加USART0_IRQHandler中断服务函数的实现代码,如下程序清单所示。在UART0.c的ConfigUART函数中使能了发送缓冲区空中断和接收缓冲区非空中断,因此,当USART0的接收缓冲区非空,或发送缓冲区空时,硬件会执行USART0_IRQHandler函数。
1.void USART0_IRQHandler(void)
2.{
3. unsigned char uData = 0;
4.
5. if(usart_interrupt_flag_get(USART0, USART_INT_FLAG_RBNE) != RESET)
6. {
7. usart_interrupt_flag_clear(USART0, USART_INT_FLAG_RBNE);
8. __NVIC_ClearPendingIRQ(USART0_IRQn);
9. uData = usart_data_receive(USART0);
10.
11. WriteReceiveBuf(uData);
12. }
13.
14. if(usart_interrupt_flag_get(USART0, USART_INT_FLAG_ERR_ORERR) == SET)
15. {
16. usart_interrupt_flag_clear(USART0, USART_INT_FLAG_ERR_ORERR);
17.
18. usart_data_receive(USART0);
19. }
20.
21. if(usart_interrupt_flag_get(USART0, USART_INT_FLAG_TBE)!= RESET)
22. {
23. __NVIC_ClearPendingIRQ(USART0_IRQn);
24.
25. ReadSendBuf(&uData);
26.
27. usart_data_transmit(USART0, uData);
28.
29. if(QueueEmpty(&s_structUARTSendCirQue))
30. {
31. s_iUARTTxSts = UART_STATE_OFF;
32. usart_interrupt_disable(USART0, USART_INT_TBE);
33. }
34. }
}
在“API函数实现”区,添加InitUART0函数的实现代码,如程序清单612所示。其中,InitUARTBuf函数用于初始化串口缓冲区,包括发送缓冲区和接收缓冲区;ConfigUART函数用于配置UART的参数,包括GPIO、RCU、USART0的常规参数和NVIC。
1.void InitUART0(unsigned int bound)
2.{
3. InitUARTBuf();
4.
5. ConfigUART(bound);
6.}
在“API函数实现”区的InitUART0函数实现区后,添加WriteUART0和ReadUART0函数的实现代码,如下程序清单所示。下面对WriteUART0和ReadUART0函数中的语句进行解释说明。
1.unsigned char WriteUART0(unsigned char *pBuf, unsigned char len)
2.{
3. unsigned char wLen = 0;
4.
5. wLen = EnQueue(&s_structUARTSendCirQue, pBuf, len);
6.
7. if(wLen < UART0_BUF_SIZE)
8. {
9. if(s_iUARTTxSts == UART_STATE_OFF)
10. {
11. EnableUARTTx();
12. }
13. }
14.
15. return wLen;
16.}
17.
18.unsigned char ReadUART0(unsigned char *pBuf, unsigned char len)
19.{
20. unsigned char rLen = 0;
21.
22. rLen = DeQueue(&s_structUARTRecCirQue, pBuf, len);
23.
24. return rLen;
25.}
在“API函数实现”区的ReadUART0函数实现区后,添加fputc函数的实现代码,如下程序清单所示。
1.int fputc(int ch, FILE *f)
2.{
3. usart_data_transmit(USART0, (uint8_t) ch);
4.
5. while(RESET == usart_flag_get(USART0, USART_FLAG_TBE));
6.
7. return ch;
8.}
5.完善串口通信实验应用层
在Project面板中,双击打开Main.c文件,在Main.c文件“包含头文件”区的最后,添加代码#include “UART0.h”。这样就可以在Main.c文件中调用UART0模块的宏定义和API函数,实现对UART0模块的操作。 在InitHardware函数中,添加调用InitUART0函数的代码,如下程序清单的第9行代码所示,这样就实现了对UART0模块的初始化。
1.static void InitHardware(void)
2.{
3. SystemInit();
4. InitRCU();
5. InitNVIC();
6. InitTimer();
7. InitLED();
8. InitSysTick();
9. InitUART0(115200);
10.}
在Proc2msTask函数中,添加第3行和第9至14行代码,如下程序清单所示。GD32F3苹果派开发板每2ms通过ReadUART0函数读取UART0接收缓冲区s_structUARTRecCirQue中的数据,然后对接收到的数据进行加1操作,最后通过WriteUART0函数将经过加1操作的数据发送出去。这样做是为了通过计算机上的串口助手来验证ReadUART0和WriteUART0两个函数,例如,当通过计算机上的串口助手向GD32F3苹果派开发板发送0x15时,开发板收到0x15之后会向计算机回发0x16。
1.static void Proc2msTask(void)
2.{
3. unsigned char recData;
4.
5. if(Get2msFlag())
6. {
7. LEDFlicker(250);
8.
9. while(ReadUART0(&recData, 1))
10. {
11. recData++;
12.
13. WriteUART0(&recData, 1);
14. }
15.
16. Clr2msFlag();
17. }
18.}
在Proc1SecTask函数中,添加调用printf函数的代码,如下程序清单的第5行代码所示。GD32F3苹果派开发板每秒通过printf输出一次This is the first GD32F303 Project, by Zhangsan,这些信息会通过计算机上的串口助手显示出来,这样做是为了验证printf。
1.static void Proc1SecTask(void)
2.{
3. if(Get1SecFlag())
4. {
5. printf("This is the first GD32F303 Project, by Zhangsan\r\n");
6.
7. Clr1SecFlag();
8. }
9.}
6.编译及下载验证
代码编写完成并编译通过后,下载程序并进行复位。打开串口助手,可以看到串口助手中输出如下图所示的信息,同时开发板上的LED1和LED2交替闪烁,表示串口模块的printf函数功能验证成功。 为了验证串口模块的WriteUART0和ReadUART0函数,在Proc1SecTask函数中注释掉printf语句,然后重新编译、下载程序并进行复位。打开串口助手,勾选“HEX显示”和“HEX发送”项,在“字符串输入框”中输入一个数据,如15,单击“发送”按钮,可以看到串口助手中输出16,如下图所示。同时,可以看到开发板上的LED1和LED2交替闪烁,表示串口模块的WriteUART0和ReadUART0函数功能验证成功。
总结
以上就是今天要讲的内容,本文介绍了GD32F30x系列微控制器的串口功能框图、串口部分寄存器和固件库、异常和中断、NVIC寄存器和固件库,以及串口模块驱动设计。通过一个实例介绍串口驱动的设计和应用,下一章将介绍GD32 LCD实验。
|