一、C语言的格式化输出
C语言的printf是一个标准库函数,用于将格式化的数据输出到标准的输出设备(通常是终端)
- 基本语法:
int printf(const char *format, ...);
其中的第一个参数const char *format
表示输出格式,后面的参数是可变参数,用于填充格式化字符串中的占位符。
- 字符输出原理:
- 格式化字符串处理:printf函数将第一个参数
const char *format
中的格式占位符解析出来,然后根据占位符的类型和顺序依次取可变参数中的值,将这些值转换为字符串,并将其按照格式化字符串中的顺序和样式组合成最终的输出字符串。 - 输出字符串存储:printf函数将格式化后的输出字符串存储在内存缓冲区中。
- 输出字符串显示:printf函数将内存缓冲区中的输出字符串显示到标准输出设备上,通常是终端。
在学习C语言的时候,在调用头文件
#include "stdio.h"
的时候,就可以使用printf函数进行格式化打印
#include <stdio.h>
int a = 10;
char str[] = "hello,world!";
int main(void)
{
printf("%s\n",str);
printf("a = %d",a);
return 0;
}
[result]
hello,world!
a = 10
但是在Keil中,在stm32的使用中是不能直接使用C语言的打印函数的,需要添加支持设置,即调用MDK的微库(MicroLib) 称之为printf的重定向。其实不仅仅可以把打印字符重定向,而且还可以将获取字符重定向。
二、开发准备
-
基于STM32L431RCT6的小熊派开发板
-
windows系统并安装Cubemx和Keil MDK的电脑
三、初始化片上外设
本次开发介绍的主要是串口的重定向,因此就是需要初始化串口外设。
设置串口通信为异步通信,波特率115200
生成代码,并选择keil-MDK打开该工程
四、设置重定向
4.1 点击魔术棒,然后勾选使用微库(Use MicroLIB)
针对MicroLIB的介绍:
MicroLIB是Keil公司提供的一个C标准库,专为嵌入式系统设计而开发。相对于标准C库,MicroLIB库更加轻量级,代码量更小,适用于嵌入式系统等资源受限的环境。MicroLIB库支持ISO/ANSI C标准的大部分函数,并增加了一些嵌入式系统常用的函数,例如串口通信、GPIO控制等。在MDK的工程中,开发者可以选择使用MicroLIB库来进行开发,以减小程序的代码大小和占用内存的空间。
需要注意的是,MicroLIB库并不是一个完整的C标准库,它只实现了一部分的C标准函数,并且一些函数的实现与标准C库可能存在差异。如果需要使用标准C库的函数或者功能更加完整的C标准库,开发者需要使用其他的C标准库,例如GNU C Library(glibc)等。
4.2 添加串口重定向代码
在main函数中添加头文件:
#include “stdio.h"
在/* USER CODE BEGIN 4 */
内添加:
int fputc(int c,FILE *f)
{
uint8_t ch; //定义一个无符号8位整型变量ch 并将字符C赋值给它
ch = c;
HAL_UART_Transmit(&huart1,&ch,1,1000);
// 调用HAL库的串口发送函数,将ch发送到USART1串口,等待时间为1000ms
return c;
}
4.3 在主循环中添加代码
/* USER CODE BEGIN 2 */
uint8_t str[] = "Hello GearLong!";
uint8_t num1 = 10;
float f = 3.1415926;
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
printf("%s\r\n",str);
printf("num = %d\r\n",num1);
printf("f1 = %f\r\n",f);
printf("f2 = %.2f\r\n",f);
printf("f3 = %8.3f\r\n",f);
HAL_Delay(1000);
}
4.4 编译、下载,然后打开串口调试助手查看执行
记得将AT开关拨到AT_MCU
4.5 不使用微库打印数据
下面的代码来自正点原子的HAL库代码中的串口打印 ,将代码复制到
usart.c
的代码添加处,并取消勾选微库,并移除上面已经设置的重定向代码。
/* 加入以下代码, 支持printf函数, 而不需要选择use MicroLIB */
#if 1
#if (__ARMCC_VERSION >= 6010050) /* 使用AC6编译器时 */
__asm(".global __use_no_semihosting\n\t"); /* 声明不使用半主机模式 */
__asm(".global __ARM_use_no_argv \n\t"); /* AC6下需要声明main函数为无参数格式,否则部分例程可能出现半主机模式 */
#else
/* 使用AC5编译器时, 要在这里定义__FILE 和 不使用半主机模式 */
#pragma import(__use_no_semihosting)
struct __FILE
{
int handle;
/* Whatever you require here. If the only file you are using is */
/* standard output using printf() for debugging, no file handling */
/* is required. */
};
#endif
/* 不使用半主机模式,至少需要重定义_ttywrch\_sys_exit\_sys_command_string函数,以同时兼容AC6和AC5模式 */
int _ttywrch(int ch)
{
ch = ch;
return ch;
}
/* 定义_sys_exit()以避免使用半主机模式 */
void _sys_exit(int x)
{
x = x;
}
char *_sys_command_string(char *cmd, int len)
{
return NULL;
}
/* FILE 在 stdio.h里面定义. */
FILE __stdout;
/* 重定义fputc函数, printf函数最终会通过调用fputc输出字符串到串口 */
int fputc(int ch, FILE *f)
{
while ((USART1->ISR & 0X40) == 0); /* 等待上一个字符发送完成 */
USART1->TDR = (uint8_t)ch; /* 将要发送的字符 ch 写入到DR寄存器 */
return ch;
}
#endif
需要注意的是在使用Cubmex生成的HAL库代码中,USART1的相关寄存器变成了ISR和TDR
4.6 打印输出的结果是一致的