微信公眾號 | strongerHuang
單片機學(xué)習、做項目,經(jīng)常都會用到類似 printf 這種打印輸出的時候。
比如:
-
- 打印輸出日志信息
- 打印輸出調(diào)試信息查看實時數(shù)據(jù)等
而 printf 用的最多的就是UART(重定向串口)?。其實除了串口,也可以使用其他底層接口,I2C、SPI、CAN等這些常見通信接口也是可以的。
因為CAN的通信速率相對更高,我之前很多項目不管是傳輸數(shù)據(jù),還是輸出信息都用到 CAN 總線進行通信。所以,其實底層也可以“重定向CAN”
單片機printf打印輸出
單片機 printf 打印輸出,最常見的一種是使用UART串口重定向,然后使用微庫,比如Keil環(huán)境下,只需要在配置界面勾選“微庫”即可:
然后串口進行重定向:
#include <stdio.h>
int fputc(int ch, FILE *f)
{
??USART_SendChar((uint8_t)ch);
return ch;
}
經(jīng)過上述簡單配置,即可在應(yīng)用中直接調(diào)用 printf 函數(shù):
printf("公眾號:strongerHuang");
以上只是最常見的一種方法,其實單片機打印輸出還有很多方法:1.UART打印輸出;2.仿真打印輸出;3.SWO打印輸出;4.JLink-RTT打印輸出
單片機常見自定義printf函數(shù)
上面分享的 printf 打印輸出都是標準的方法,有些時候,這些標準的方法可能不適合當前的項目。
比如內(nèi)存、Flash不足了,輸出的方式不能滿足等,這個時候,就需要自定義printf函數(shù)了。這里簡單分享一下常見的方式。
1、底層通信函數(shù)
UART串口是最常見的一種printf通信接口,其實,CAN這種通信速率更高的接口也是可以的。
USART_SendChar((uint8_t)ch);
//或者CAN總線作為通信接口
CAN_SendChar((uint8_t)ch);
2、重定向底層通信接口
這里是通過UART串口重定向?qū)崿F(xiàn)一些基礎(chǔ)的字符和字符串輸出函數(shù)。
void putchar(char c)
{
USART_SendChar(c);
}
void puts(const char *str)
{
while(*str)
{
putchar(*str++);
}
putchar('n');
}
3、實現(xiàn)整數(shù)輸出
我們這里使用簡單的 itoa(整數(shù)轉(zhuǎn)字符串)算法。
void print_int(int num)
{
char buffer[12];
char *ptr = buffer + 11;
char is_negative = 0;
if (num < 0) {
is_negative = 1;
num = -num;
}
*ptr = '';
do {
*--ptr = (num % 10) + '0';
num /= 10;
} while (num);
if (is_negative) {
*--ptr = '-';
}
puts(ptr);
}
4、實現(xiàn)小數(shù)點輸出
如果需要支持小數(shù)輸出,可以添加一個簡單的浮點數(shù)打印函數(shù)。但是,考慮到有些單片機不支持浮點運算硬件,這里使用定點數(shù)(整數(shù)和小數(shù)部分分離)來實現(xiàn)。
void print_fixed_point(int num, int fraction_digits)
{
int integer_part = num / (int)pow(10, fraction_digits);
int fractional_part = num % (int)pow(10, fraction_digits);
print_int(integer_part);
putchar('.');
for (int i = 0; i < fraction_digits; i++)
{
fractional_part *= 10;
putchar((fractional_part / (int)pow(10, fraction_digits - i)) + '0');
}
}
5、自定義封裝成 printf 風格的函數(shù)
將以上函數(shù)封裝成一個類似 printf 的函數(shù)。為了簡單說明案例,這里只支持 %d 和 %f(使用定點數(shù))格式說明符。
void my_printf(const char *format, ...)
{
va_list args;
va_start(args, format);
const char *ptr = format;
while (*ptr)
{
if (*ptr == '%')
{
ptr++;
switch (*ptr)
{
case 'd':
{
int num = va_arg(args, int);
print_int(num);
break;
}
case 'f':
{ // 使用定點數(shù)表示
int num = va_arg(args, int); // 整數(shù)部分和小數(shù)部分合并的定點數(shù)
int fraction_digits = va_arg(args, int); // 小數(shù)位數(shù)
print_fixed_point(num, fraction_digits);
break;
}
default:
putchar('%');
putchar(*ptr);
break;
}
} else {
putchar(*ptr);
}
ptr++;
}
va_end(args);
}
最后,printf的基本功能就實現(xiàn)了。。。
最最后要說明一下,這里舉例只是簡單給大家說明原理,真正項目,其實還需要添加很多內(nèi)容,比如:串口發(fā)送超時、參數(shù)驗證等容錯設(shè)計。