十、串口打印信息
1.1.配置流程
一般我们使用串口,都需要有以下几个步骤。
- 开启时钟(包括串口时钟和 GPIO 时钟)
- 配置 GPIO 复用模式
- 配置 GPIO 的模式
- 配置 GPIO 的输出
- 配置串口(配置一些参数)
- 使能串口(串口使能和发送使能)
1.1.1.开启时钟
使用串口 0 的话就是 PA9 和 PA10 引脚。那第一步就是先开启端口 A 的时钟,在库函数点灯那一章节给大家介绍了使能时钟的函数 rcu_periph_clock_enable,只需要传入对应的参数即可。使能端口 A 的时钟就把 RCU_GPIOA 当做参数传入。第二步就是开启串口的时钟,把对应的串口 0 的时钟 RCU_USART0 传入即可。为了方便后续修改,把端口 A 的时钟和串口 0 的时钟用宏定义定义,如下:
#define BSP_USART_RCU RCU_USART0
#define BSP_USART_TX_RCU RCU_GPIOA
#define BSP_USART_RX_RCU RCU_GPIOA
2
3
然后对应的使能时钟的代码就是
rcu_periph_clock_enable(BSP_USART_RCU); // 开启串口时钟
rcu_periph_clock_enable(BSP_USART_TX_RCU); // 开启端口时钟
rcu_periph_clock_enable(BSP_USART_RX_RCU); // 开启端口时钟
2
3
1.1.2.配置 GPIO 复用模式
GD32 的引脚是可以有复用功能的,就是说单个引脚可有很多个功能,默认的功能一般都是作为 GPIO 使用。在 gd32f4xx_gpio.h 中可以查找到设置复用的函数
void gpio_af_set(uint32_t gpio_periph, uint32_t alt_func_num, uint32_t pin);
这个函数有三个参数,第一个参数就是要配置的引脚端口,第二个参数就是要复用的功能,第三个参数就是要配置的引脚。第一个参数和第三个参数我们知道分别为 GPIOA,GPIO_PIN_9,GPIOA,GPIO_PIN_10。关于第二个参数可以到数据手册的第 46 页进行查找,如图 1-2-1 所示。
从图 1-2-1 可以看到 PA9 对应的 USART0_TX 的功能复用为 AF7,PA10 对应的 USART0_RX 的功能复用为 AF7,那第二个参数我们也知道了。接下来就可以开启 PA9,PA10 的复用功能了。代码编写如下。首先定义一下端口和复用功能的宏定义。
#define BSP_USART_TX_PORT GPIOA
#define BSP_USART_TX_PIN GPIO_PIN_9
#define BSP_USART_RX_PORT GPIOA
#define BSP_USART_RX_PIN GPIO_PIN_10
#define BSP_USART_AF GPIO_AF_7 // 串口是引脚复用功能7
2
3
4
5
然后调用复用函数使能复用功能。
/* 配置复用功能 */
gpio_af_set(BSP_USART_TX_PORT,BSP_USART_AF,BSP_USART_TX_PIN);
gpio_af_set(BSP_USART_RX_PORT,BSP_USART_AF,BSP_USART_RX_PIN);
2
3
通过上面两句就可以把 PA9,PA10 设置为串口功能了。
1.1.3.配置 GPIO 的模式
配置 GPIO 的模式还是使用 gpio_mode_set 这个函数,不同的是第二个参数要配置为复用功能而不是输出功能,第三个参数要配置为上拉,转化为代码如下。
/* 配置TX为复用模式 上拉模式 *_/
gpio_mode_set(BSP_USART_TX_PORT, GPIO_MODE_AF, GPIO_PUPD_PULLUP, BSP_USART_TX_PIN);
__/__*_ 配置RX为复用模式 上拉模式 */
gpio_mode_set(BSP_USART_RX_PORT, GPIO_MODE_AF, GPIO_PUPD_PULLUP, BSP_USART_RX_PIN);
2
3
4
1.1.4.配置 GPIO 的输出
配置 GPIO 的输出也还是用 gpio_output_options_set 这个函数,这个和之前库函数点灯配置的一样,修改一下引脚就可以直接套用。
/* 配置TX为推挽输出 50MHZ _/
gpio_output_options_set(BSP_USART_TX_PORT, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ, BSP_USART_TX_PIN);
__/_ 配置RX为推挽输出 50MHZ */
gpio_output_options_set(BSP_USART_RX_PORT, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ, BSP_USART_RX_PIN);
2
3
4
1.1.5.配置串口
和之前库函数点灯不同的是,使用串口还需要进行串口的配置。前面只是对 GPIO 的引脚做了配置,然后就可以操作 GPIO 进行输入输出,但是要使用串口,还需要设置串口的一些参数,后面使用其它的资源也是如此。
在前一章节的串口原理介绍中讲到串口有几个必要的参数,分别是波特率,校验位,数据位,停止位。在代码里也就是要设置这几个参数。关于串口函数的查找,大家可以参考 GD32F3x0 固件库使用指南,但不要完全按照里面的来,因为 GD32F3x0 和 GD32F450 还是有不同的地方,仅供参考。推荐大家到固件库的 gd32f4xx_uart.h 中去查找对应的函数,两者搭配起来使用效果最佳。
在使用一个外设之前,可以先复位一下,防止一些不必要的事情发生。在对应的外设库文件中一般都会有这个函数。在 gd32f4xx_uart.h 头文件中有
void usart_deinit(uint32_t usart_periph);
这个函数复位串口。有一个参数,就是要复位的串口。
void usart_baudrate_set(uint32_t usart_periph, uint32_t baudval);
这个函数设置波特率。有两个参数,分别为要设置的串口和要设置的波特率。
void usart_parity_config(uint32_t usart_periph, uint32_t paritycfg);
这个函数配置校验功能。有两个参数,第一个就是要配置的串口,第二个就是设置校验方式。关于校验方式的选项如图 1-5-1 所示。
从图 1-5-1 可以看到,校验方式可以选择奇校验、偶检验和无校验。一般配置为无校验即可。
void usart_word_length_set(uint32_t usart_periph, uint32_t wlen);
这个函数设置数据位的长度。有两个参数,第一个参数就是要配置的串口,第二个参数就是要设置的数据位的长度,关于数据位长度的选项如图 1-5-2 所示。
从图 1-5-2 可以知道关于数据长度有两个选项,一般选择 8 位即可。
void usart_stop_bit_set(uint32_t usart_periph, uint32_t stblen);
这个函数设置停止位的位数。有两个参数,第一个参数就是要配置的串口,第二个参数就是要设置的停止位的位数,关于停止位的位数的选项如图 1-5-3 所示。
停止位一般选择 1 位即可。
经过上面的学习,可编写代码如下。
/* 串口配置*/
usart_deinit(BSP_USART); // 复位串口
usart_baudrate_set(BSP_USART,dwbaud_rate); // 设置波特率
usart_parity_config(BSP_USART,USART_PM_NONE); // 没有校验位
usart_word_length_set(BSP_USART,USART_WL_8BIT); // 8位数据位
usart_stop_bit_set(BSP_USART,USART_STB_1BIT); // 1位停止位
2
3
4
5
6
这里要注意一下 BSP_USART 是一个宏定义
#define BSP_USART USART0
其实就是要使能的串口。
dwbaud_rate 是函数里的一个形参,为了方便修改串口的波特率,函数实体为
void usart_gpio_config(uint32_t dwbaud_rate),
在使用的时候,可以直接传入对应的波特率,例如配置波特率为 115200,转化为代码可写为
usart_gpio_config(115200); // 串口配置
如还需要配置其它的参数请查找对应的函数调用即可。
1.1.6.使能串口
串口配置好之后并不能开始工作,还需要去使能,就是相当于有一个开关可以打开关闭。值得注意的是发送和接收也需要分别去使能,串口使能相当于一个总开关,发送和接收相当于分别控制的开关。要使用串口,首先要打开总开关,
void usart_enable(uint32_t usart_periph);
这个函数使能串口,有一个参数,就是要使能的串口。
然后如果要发送数据的话还需要使能发送功能。
void usart_transmit_config(uint32_t usart_periph, uint32_t txconfig);
这个函数配置串口发送。有两个参数,第二个参数对应的选项如图 1-6-1 所示。
从图 1-6-1 中可以看到,如果使能串口发送就配置为 USART_TRANSMIT_ENABLE。
如果要接收数据的话还需要使能接收功能。
void usart_receive_config(uint32_t usart_periph, uint32_t rxconfig);
这个函数配置串口接收。有一个参数,对应的选项如图 1-6-2 所示。
从图 1-6-2 中可以看到,如果使能串口接收就配置为 USART_RECEIVE_ENABLE。
我们只使用串口发送功能,所以只需要配置串口使能和串口发送使能即可。
转化为代码为
usart_transmit_config(BSP_USART,USART_TRANSMIT_ENABLE); // 使能串口发送
usart_enable(BSP_USART); // 使能串口
2
到此,串口使能就完成了。
1.2.串口发送数据
配置好串口之后,下一步的操作就是要发送数据。
void usart_data_transmit(uint32_t usart_periph, uint32_t data);
这个函数可以发送数据,有两个参数,第一个参数是要使用的串口,第二个参数是要发送的数据,不过需要注意的是这个函数一次只能发送一个字节。要保证串口稳定的传输,就需要在发送完一个字节之后再发送下一个字节。需要去检测数据发送完成。
FlagStatus usart_flag_get(uint32_t usart_periph, usart_flag_enum flag);
这个函数是获取状态寄存器的标志。有两个参数,第一个参数是要使用的串口,第二个参数是要获取的状态位,状态位选项为图 2-1-1 所示(只截取一部分)。
可以获取发送缓冲区的标志位,看当前是否还有数据,如果有数据,将等待,如果没有数据就可以继续发送。
关于 TBE 的介绍如图 2-1-2 所示。
从图 2-1-2 可以了解到当将要发送的数据写入 USART_DATA 时,此位被清 0,当数据发送完成之后,此位置 1。所以当检测到此位为 1 时就表明当前数据缓冲区为空,可以继续发送数据。
关于串口发送数据可以封装为一个函数如下。
void usart_send_data(uint8_t ucch)
{
usart_data_transmit(BSP_USART, (uint8_t)ucch);
while(RESET == usart_flag_get(BSP_USART, USART_FLAG_TBE)); // 等待发送数据缓冲区标志置位
}
2
3
4
5
当我们调用
usart_send_data('h');
usart_send_data('e');
usart_send_data('l');
usart_send_data('l');
usart_send_data('o');
2
3
4
5
将会在串口助手打印 hello,如图 2-1-3 所示。
这样一个字符一个字符的打印输出是不是很麻烦,没关系,我们可以再进行封装一层,一次发送一个字符串。
void usart_send_String(uint8_t *ucstr)
{
while(ucstr && *ucstr) // 地址为空或者值为空跳出
{
usart_send_data(*ucstr++);
}
}
2
3
4
5
6
7
通过调用
usart_send_String("hello\r\n");
这一句就可以打印出 hello。
1.3.串口重定向
上面封装的发送字符串的方式打印信息看似方便,但如果我们想要打印数字,小数,该怎么打印呢?大家是否习惯使用了 printf 这个函数,可以通过 %d,%f 打印整形和小数,这一小节就教大家怎么把串口重定向到 printf 函数。
1.3.1.串口重定向介绍
C 语言中的 printf 函数默认输出设备是显示器,如果要在串口显示,必须重新定义标准库函数里调用的与输出设备相关的函数。需要注意的是,在 keil 中使用 printf 一定要勾选“微库”选项。
1.3.2.printf 重定向
首先 c 语言的 printf 函数中不断循环调用 fputc 函数,所以需要重写 fputc 函数,这个函数的功能就是打印输出一个字符,这不正和我们编写的 usart_send_data 函数功能一样。fputc 函数可写为
int fputc(int ch, FILE *f)
{
usart_data_transmit(BSP_USART, (uint8_t)ch);
while(RESET == usart_flag_get(BSP_USART, USART_FLAG_TBE)); // 等待发送数据缓冲区标志置位
return ch;
}
2
3
4
5
6
编写好 fputc 函数之后就可以使用 printf 函数输出信息了。
1.4.举一反三
通过上面的一系列步骤,我们可以用串口 0 发送数据,那如果用串口 1 发送数据,该如何操作呢?
还记得我们前面配置的宏定义吗,宏定义的好处就是方便移植。把串口 0 改为串口 1,函数主体都不用修改,只需要修改宏定义即可。
串口 1 的引脚接口为 PA2 和 PA3,所以串口 1 的宏定义如图 4-1-1 所示。
#define BSP_USART_RCU RCU_USART1
#define BSP_USART_TX_RCU RCU_GPIOA
#define BSP_USART_TX_PORT GPIOA
#define BSP_USART_TX_PIN GPIO_PIN_2
#define BSP_USART_RX_RCU RCU_GPIOA
#define BSP_USART_RX_PORT GPIOA
#define BSP_USART_RX_PIN GPIO_PIN_3
#define BSP_USART_AF GPIO_AF_7 // 串口是引脚复用功能7
#define BSP_USART USART1
2
3
4
5
6
7
8
9
代码中只需要将串口 0 的宏定义修改为串口 1 的宏定义就可以使用串口 1 发送信息了。
1.5.实验现象
关于这一章节的代码,在资源包/04 软件资料/代码例程/里面的 005 串口打印信息。
烧写我们的代码之后,打开串口调试助手,每隔 2s 会打印一次信息,信息分别为整数每隔 2s+1 和小数每隔 2s+0.11,打印信息如图 5-1-1 所示。