printf这个函数相信学习过编程的人应该都用过,这是一个用来向终端打印数据的函数。这个函数不仅在调试软件代码的时候经常有使用,单片机开发时也经常用于串口打印调试。所以,在此就如何让单片机使用printf来调试代码,开始接下来的学习,在此,请允许我以stm32hal库为例。
首先,要求开发环境支持c语言的标准库函数。(这个应该都支持的吧,问题不大)
方案一 串口重定向
也是最常用的方案,这个操作我们重写fputc函数来重新定向printf的输出对象。
int fputc(int ch, FILE *f)
#endif
{
//具体哪个串口可以更改huart1为其它串口
HAL_UART_Transmit(&huart1 , (uint8_t *)&ch, 1 , 0x0f);
return ch;
}
但是问题来了,有些编译器不是用fput实现的,而是用__io_putchar实现的,比如stm32cubeide。所以我们将代码改写一下,以应对不同的开发环境。
#ifdef __GNUC__
#define PUTCHAR_PROTOTYPE int __io_putchar(int ch)
#else
#define PUTCHAR_PROTOTYPE int fputc(int ch, FILE *f)
#endif
PUTCHAR_PROTOTYPE
{
//具体哪个串口可以更改huart1为其它串口
HAL_UART_Transmit(&huart1 , (uint8_t *)&ch, 1 , 0x0f);
return ch;
}
方案二 写自己的print函数
这个是本文重点讲述的方法。需要添加头文件stdarg.h、stdio.h和string.h。重定向有很大的局限性,比如,有些编译环境就是很奇怪,怎么写就是打印不出来。再者就是一个printf不足以满足我各种骚气的操作需求。比如,我想操作两个串口,又想打印在屏幕上一个printf就显得有些不够用。所以我们编写一个自己的printf函数来弥补这种不足。
首先需要了解一下可变参数函数,printf,scanf就是典型的可变参数函数。可变参数函数平时用的比较少,实际上用起来也很简单。
函数声明的参数列表的最后一个参数用省略号“…”,作为可变参数。
在函数体内创建va_list变量用于存储参数列表。
调用va_start初始化va_list参数列表。
操作va_list。
调用va_end完成清理工作。
va_start(arg,nr):arg就是可变参数变量,nr是函数参数列表总可变参数的起始位置的参数,也就是可变参数的前一个参数。不是可变参数的个数哦,这里刚开始的时候我理解错了。
现在可以编写自己的print函数了。
void print(UART_HandleTypeDef* huart, const char* buf, ...)
{
const char *p = buf;
char str[255] = {0};
va_list v;
va_start(v, buf);
vsprintf(str, buf, v); //使用可变参数的字符串打印。类似sprintf
HAL_UART_Transmit(huart, str, strlen(str), 0xff);
va_end(v);
}
printf这个函数相信学习过编程的人应该都用过,这是一个用来向终端打印数据的函数。这个函数不仅在调试软件代码的时候经常有使用,单片机开发时也经常用于串口打印调试。所以,在此就如何让单片机使用printf来调试代码,开始接下来的学习,在此,请允许我以stm32hal库为例。
首先,要求开发环境支持c语言的标准库函数。(这个应该都支持的吧,问题不大)
方案一 串口重定向
也是最常用的方案,这个操作我们重写fputc函数来重新定向printf的输出对象。
int fputc(int ch, FILE *f)
#endif
{
//具体哪个串口可以更改huart1为其它串口
HAL_UART_Transmit(&huart1 , (uint8_t *)&ch, 1 , 0x0f);
return ch;
}
但是问题来了,有些编译器不是用fput实现的,而是用__io_putchar实现的,比如stm32cubeide。所以我们将代码改写一下,以应对不同的开发环境。
#ifdef __GNUC__
#define PUTCHAR_PROTOTYPE int __io_putchar(int ch)
#else
#define PUTCHAR_PROTOTYPE int fputc(int ch, FILE *f)
#endif
PUTCHAR_PROTOTYPE
{
//具体哪个串口可以更改huart1为其它串口
HAL_UART_Transmit(&huart1 , (uint8_t *)&ch, 1 , 0x0f);
return ch;
}
方案二 写自己的print函数
这个是本文重点讲述的方法。需要添加头文件stdarg.h、stdio.h和string.h。重定向有很大的局限性,比如,有些编译环境就是很奇怪,怎么写就是打印不出来。再者就是一个printf不足以满足我各种骚气的操作需求。比如,我想操作两个串口,又想打印在屏幕上一个printf就显得有些不够用。所以我们编写一个自己的printf函数来弥补这种不足。
首先需要了解一下可变参数函数,printf,scanf就是典型的可变参数函数。可变参数函数平时用的比较少,实际上用起来也很简单。
函数声明的参数列表的最后一个参数用省略号“…”,作为可变参数。
在函数体内创建va_list变量用于存储参数列表。
调用va_start初始化va_list参数列表。
操作va_list。
调用va_end完成清理工作。
va_start(arg,nr):arg就是可变参数变量,nr是函数参数列表总可变参数的起始位置的参数,也就是可变参数的前一个参数。不是可变参数的个数哦,这里刚开始的时候我理解错了。
现在可以编写自己的print函数了。
void print(UART_HandleTypeDef* huart, const char* buf, ...)
{
const char *p = buf;
char str[255] = {0};
va_list v;
va_start(v, buf);
vsprintf(str, buf, v); //使用可变参数的字符串打印。类似sprintf
HAL_UART_Transmit(huart, str, strlen(str), 0xff);
va_end(v);
}
举报