我们常常会用到串口,甚至 ST-Link 自带了一个虚拟串口,有时候串口时为了传输数据给上位机,但有时只是为了输出个结果供我们观察程序运行状态,对于后者,实现一个 printf
函数就十分有用了。网上通常的方案是修改fputc
来实现。但我们可以利用标准库实现一个自己的 printf
函数,也可以是一个printf
宏。
从HAL库的串口传输开始
串口传输可以是阻塞模式、中断模式、DMA模式。我们以阻塞模式为例。
1 | HAL_StatusTypeDef HAL_UART_Transmit(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size, uint32_t Timeout); |
串口发送数据的函数为HAL_UART_Transmit
。我们的思路是将我们的数据转换成字符串然后发送出去。
函数版本
我们希望串口发送函数具备printf
的特点,那么就需要用到可变参数
。下面是与此相关的头文件。
1 |
我们需要简单的了解一下 va_list
、va_start
、 va_end
、vsprintf
。
既然需要用到可变参数,那不妨就顺便学习下最简单的用法。
其中va_list
需要被va_start
初始化 va_end
最后释放内存。希望实现的串口printf
,需要用到的是vsprintf
。
vsprintf
的形参为
int vsprintf (char * s, const char * format, va_list arg );
其中arg
应当用 char*
来初始化。1
2
3char* format = something;
va_list ap;
va_start(ap, format);
既然转换为了字符串,那么自然需要知道它的长度,因为字符串的长度即为发送的size。那么需要引入头文件 string.h
然后调用 strlen
函数。并且我们需要一个数组存放字符串。
1 |
|
宏版本
实现了函数版本后,我们可以实现一个宏版本。同时思考到,函数版本有一个UART的句柄 UART_HandleTypeDef
,串口用作printf时通常我们是固定的一个串口,通常不会发生一会儿使用 uart1,一会儿使用 uart2的情况。我们可以将它固定下来。在宏版本中,我们来完成这一工作,你也可以修改对应的函数版本。
1 | // 固定下串口print的句柄 |
关键还是在可变参数中。这次是在宏中使用可变参数。对于宏的可变参数而言,有一个专门的宏__VA_ARGS__
。
我们需要转换为字符串,因此需要sprintf
函数,用法非常简单。
1 |
|
注:
__VA_ARGS__
只能用于宏定义,从C99开始引入,不能用于函数实现。宏定义加入括号,可以直接跟if语句而不加大括号。如
if (…)
uart_printf
else
do something