十、串口打印信息
配置流程
一般我们使用串口,都需要有以下几个步骤。
- 开启时钟(包括串口时钟和 GPIO 时钟)
- 配置 GPIO 复用模式
- 配置 GPIO 的模式
- 配置 GPIO 的输出
- 配置串口(配置一些参数)
- 使能串口(串口使能和发送使能)
开启时钟
使用串口 1 的话就是 PA9 和 PA10 引脚。那第一步就是先开启端口 A 的时钟,在库函数点灯那一章节给大家介绍了使能时钟的函数 RCC_APB2PeriphClockCmd,只需要传入对应的参数即可。使能端口 A 的时钟就把 GPIOA 的时钟当做参数传入。第二步就是开启串口的时钟,把对应的串口 1 的时钟 RCC_APB2Periph_USART1 传入即可。为了方便后续修改,把端口 A 的时钟和串口 1 的时钟用宏定义定义,如下:
#define BSP_USART_RCC RCC_APB2Periph_USART1
#define BSP_USART_TX_RCC RCC_AHB1Periph_GPIOA
#define BSP_USART_RX_RCC RCC_AHB1Periph_GPIOA
2
3
然后对应的使能时钟的代码就是
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE); //开启串口1的时钟
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA,ENABLE); // 开启端口时钟
// RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA,ENABLE); // 开启端口时钟
2
3
配置 GPIO 复用模式
STM32 的引脚是可以有复用功能的,就是说单个引脚可有很多个功能,默认的功能一般都是作为 GPIO 使用。在 STM32f4xx_gpio.h 中可以查找到设置复用的函数
void GPIO_PinAFConfig(GPIO_TypeDef* GPIOx, uint16_t GPIO_PinSource, uint8_t GPIO_AF);
这个函数有三个参数,第一个参数就是要配置的引脚端口,第二个参数就是要复用的功能,第三个参数就是要配置的引脚。关于最后一个 AF 参数可以到数据手册的第 62 页进行查找,如图所示。
从图可以看到 PA9 对应的 USART1_TX 的功能复用为 AF7,PA10 对应的 USART1_RX 的功能复用为 AF7,在库函数中官方已经封装了 AF 所以我们使用相应的参数:GPIO_AF_USART1 即可,代码编写如下。首先定义一下端口和复用功能的宏定义。
#define BSP_USART USART1
#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_USART1
#define BSP_USART_TX_AF_PIN GPIO_PinSource9
#define BSP_USART_RX_AF_PIN GPIO_PinSource10
2
3
4
5
6
7
8
然后调用复用函数使能复用功能。
//IO口用作串口引脚要配置复用模式
GPIO_PinAFConfig(BSP_USART_TX_PORT, BSP_USART_TX_AF_PIN, BSP_USART_AF);
GPIO_PinAFConfig(BSP_USART_RX_PORT, BSP_USART_RX_AF_PIN, BSP_USART_AF);
2
3
通过上面两句就可以把 PA9,PA10 设置为串口功能了。
配置 GPIO
配置 GPIO 的模式还是使用 GPIO_Init 这个函数,不同的是第二个参数要配置为复用功能而不是输出功能,第三个参数要配置为上拉,转化为代码如下。
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_StructInit(&GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = BSP_USART_TX_PIN; //TX引脚
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF; //IO口用作串口引脚要配置复用模式
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;
GPIO_Init(GPIOA,&GPIO_InitStructure);
GPIO_StructInit(&GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = BSP_USART_RX_PIN; //RX引脚
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF; //IO口用作串口引脚要配置复用模式
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;
GPIO_Init(GPIOA,&GPIO_InitStructure);
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
配置串口
我们依然使用结构体的方式进行配置串口数据,里面有些参数是必须要配置的:
- 波特率:我们使用形参__Baud 可以自由调整波特率
- 字节长度:8 位
- 停止位:1 位停止位
- 校验位:不需要校验位
- 收发模式:收发
- 流控选择:不流控
相关的代码:
USART_StructInit(&USART_InitStructure);
USART_InitStructure.USART_BaudRate = __Baud;//设置波特率
USART_InitStructure.USART_WordLength = USART_WordLength_8b;//字节长度为8bit
USART_InitStructure.USART_StopBits = USART_StopBits_1;//1个停止位
USART_InitStructure.USART_Parity = USART_Parity_No ;//没有校验位
USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;//将串口配置为收发模式
USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None; //不提供流控
USART_Init(BSP_USART,&USART_InitStructure);//将相关参数初始化给串口1
USART_ClearFlag(BSP_USART,USART_FLAG_RXNE);//初始配置时清除接受置位
2
3
4
5
6
7
8
9
10
使能串口
串口配置好之后并不能开始工作,还需要去使能,就是相当于有一个开关可以打开关闭。要使用串口,首先要打开总开关,
void USART_Cmd(USART_TypeDef* USARTx, FunctionalState NewState);
转化为代码为:
USART_Cmd(BSP_USART,ENABLE); //开启串口1
到此,串口使能就可以使用了。
串口发送数据
配置好串口之后,下一步的操作就是要发送数据。
void USART_SendData(USART_TypeDef* USARTx, uint16_t Data);
FlagStatus USART_GetFlagStatus(USART_TypeDef* USARTx, uint16_t USART_FLAG)
这个函数是获取状态寄存器的标志。状态位选项为图所示。
从图可以了解到当将要发送的数据写入 USART_DATA 时,此位被清 0,当数据发送完成之后,此位置 1。所以当检测到此位为 1 时就表明当前数据缓冲区为空,可以继续发送数据。
关于串口发送数据可以封装为一个函数如下。
void usart_send_data(uint8_t ucch)
{
USART_SendData(BSP_USART, (uint8_t)ucch);
// 等待发送数据缓冲区标志置位
while( RESET == USART_GetFlagStatus(BSP_USART, USART_FLAG_TXE) ){}
}
2
3
4
5
6
7
当我们调用
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,如图所示。
这样一个字符一个字符的打印输出是不是很麻烦,没关系,我们可以再进行封装一层,一次发送一个字符串。
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。
串口重定向
上面封装的发送字符串的方式打印信息看似方便,但如果我们想要打印数字,小数,该怎么打印呢?大家是否习惯使用了 printf 这个函数,可以通过 %d,%f 打印整形和小数,这一小节就教大家怎么把串口重定向到 printf 函数。
串口重定向介绍
C 语言中的 printf 函数默认输出设备是显示器,如果要在串口显示,必须重新定义标准库函数里调用的与输出设备相关的函数。需要注意的是,在 keil 中使用 printf 一定要勾选“微库”选项。
printf 重定向
首先 c 语言的 printf 函数中不断循环调用 fputc 函数,所以需要重写 fputc 函数,这个函数的功能就是打印输出一个字符,这不正和我们编写的 usart_send_data 函数功能一样。fputc 函数可写为
#if !defined(__MICROLIB)
//不使用微库的话就需要添加下面的函数
#if (__ARMCLIB_VERSION <= 6000000)
//如果编译器是AC5 就定义下面这个结构体
struct __FILE
{
int handle;
};
#endif
FILE __stdout;
//定义_sys_exit()以避免使用半主机模式
void _sys_exit(int x)
{
x = x;
}
#endif
/* retarget the C library printf function to the USART */
int fputc(int ch, FILE *f)
{
USART_SendData(BSP_USART, (uint8_t)ch);
while( RESET == USART_GetFlagStatus(BSP_USART, USART_FLAG_TXE) ){}
return ch;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
编写好 fputc 函数之后就可以使用 printf 函数输出信息了。
实验现象
这一章节的代码
在开发板介绍百度网盘链接中:立创·梁山派·天空星STM32F407VET6开发板资料/第03章软件资料/代码例程/005 串口打印信息。
烧写我们的代码之后,打开串口调试助手,每隔 1s 会打印一次信息,信息分别为整数每隔 1s+1 和小数每隔 1s+0.11,打印信息如图所示。