单片机如何用C语言重写vfprintf
在单片机中,标准库函数 vfprintf
可能不可用或占用过多资源(如内存或代码空间),因此我们可以通过自定义实现一个轻量级的 vfprintf
函数,专门用于单片机的串口输出。以下是一个用 C 语言重写 vfprintf
的示例,假设我们需要将格式化字符串通过串口发送。
1. 目标
-
实现一个轻量级的
vfprintf
函数,支持基本的格式化输出(如%d
,%s
,%c
,%x
等)。 -
将格式化后的字符串通过串口发送。
2. 实现代码
#include <stdarg.h>
#include <stdint.h>
// 假设 USART_SendChar 是一个发送单个字符到串口的函数
void USART_SendChar(char ch) {
usart_data_transmit(USART0, (uint8_t)ch);
while (RESET == usart_flag_get(USART0, USART_FLAG_TBE));
// 例如,STM32 中可以使用 HAL_UART_Transmit
}
// 自定义的 vfprintf 函数
void my_vfprintf(const char *format, va_list args) {
char buffer[20]; // 用于存储转换后的数字
const char *p = format;
while (*p != '\0') {
if (*p == '%') {
p++; // 跳过 '%'
switch (*p) {
case 'd': {
int value = va_arg(args, int);
int i = 0;
if (value < 0) {
USART_SendChar('-');
value = -value;
}
do {
buffer[i++] = (value % 10) + '0';
value /= 10;
} while (value > 0);
while (i > 0) {
USART_SendChar(buffer[--i]);
}
break;
}
case 's': {
char *str = va_arg(args, char *);
while (*str != '\0') {
USART_SendChar(*str++);
}
break;
}
case 'c': {
char ch = (char)va_arg(args, int);
USART_SendChar(ch);
break;
}
case 'x': {
unsigned int value = va_arg(args, unsigned int);
int i = 0;
do {
int digit = value % 16;
buffer[i++] = (digit < 10) ? (digit + '0') : (digit - 10 + 'A');
value /= 16;
} while (value > 0);
while (i > 0) {
USART_SendChar(buffer[--i]);
}
break;
}
case '%': {
USART_SendChar('%');
break;
}
default: {
// 不支持的格式,直接输出字符
USART_SendChar('%');
USART_SendChar(*p);
break;
}
}
} else {
USART_SendChar(*p);
}
p++;
}
}
// 自定义的 printf 函数
void my_printf(const char *format, ...) {
va_list args;
va_start(args, format);
my_vfprintf(format, args);
va_end(args);
}
3. 代码解析
(1)USART_SendChar
函数
-
这是一个假设的函数,用于将单个字符发送到串口。
-
在实际项目中,需要根据具体的硬件平台实现该函数。例如,在 STM32 中可以使用
HAL_UART_Transmit
。
(2)my_vfprintf
函数
-
这是自定义的
vfprintf
函数,支持以下格式化输出:-
%d
:输出整数。 -
%s
:输出字符串。 -
%c
:输出字符。 -
%x
:输出十六进制数。 -
%%
:输出百分号%
。
-
-
使用
va_list
和va_arg
处理可变参数。
(3)my_printf
函数
-
这是一个封装函数,用于调用
my_vfprintf
。 -
通过
va_start
和va_end
处理可变参数列表。
4. 使用示例
int main() {
int num = -1234;
char *str = "Hello, World!";
char ch = 'A';
unsigned int hex = 0xABCD;
my_printf("Number: %d\n", num);
my_printf("String: %s\n", str);
my_printf("Character: %c\n", ch);
my_printf("Hexadecimal: %x\n", hex);
my_printf("Percent: %%\n");
return 0;
}
输出结果:
Number: -1234
String: Hello, World!
Character: A
Hexadecimal: ABCD
Percent: %
5. 另外一种printf重映射方式
// 将库函数的Printf重定向到串口
int fputc(int ch, FILE* f)
{
#ifdef USART1_OPEN
usart_data_transmit(USART1, (uint8_t)ch);
while (RESET == usart_flag_get(USART1, USART_FLAG_TBE));
#else
usart_data_transmit(USART0, (uint8_t)ch);
while (RESET == usart_flag_get(USART0, USART_FLAG_TBE));
#endif
return ch;
}
/* @brief 重定向c库函数getchar,scanf到USARTx*/
int fgetc(FILE *f)
{
uint8_t ch = 0;
#ifdef USART1_OPEN
ch = usart_data_receive(USART1);
#else
ch = usart_data_receive(USART0);
#endif
return ch;
}
6. 优化与扩展
-
如果需要支持更多格式化选项(如
%f
,%u
,%o
等),可以在my_vfprintf
中添加相应的处理逻辑。 -
如果单片机的 RAM 有限,可以减少
buffer
的大小,或者直接使用循环发送字符,避免使用缓冲区。 -
如果需要更高的性能,可以将
USART_SendChar
替换为直接操作串口寄存器的代码。
7. 总结
-
通过自定义
my_vfprintf
和my_printf
函数,可以在单片机中实现轻量级的格式化输出。 -
该实现避免了使用标准库的
vfprintf
,减少了代码大小和内存占用,适合资源受限的单片机环境。 -
根据具体需求,可以进一步扩展和优化该实现。