STM32Cube-09 | 重定向printf函數到串口輸出的多種方法

本文詳細的介紹瞭如何重定向printf輸出到串口輸出的多種方法,包括調用MDK微庫(MicroLib)的方法,調用標準庫的方法,以及適用於 GNUC 系列編譯器的方法。

1.printf與fputc

對於 printf 函數相信大家都不陌生,第一個C語言程序就是使用 printf 函數在屏幕上的控制檯打印出Hello World,之後使用 printf 函數輸出各種類型的數據,使用格式控制輸出各種長度的字符,甚至輸出各種各樣的圖案。

除此之外,在程序出錯的時候,懶得調試,直接簡單粗暴的加個 printf 找bug,有時候也不失爲一種有效的方法。

對於已經習慣的 printf 函數,你瞭解多少呢?

printf 定義在 <stdio.h> 頭文件中,如下:

int printf(const char *format, ...);

printf 函數根據 format 字符串給出的格式打印輸出到 stdout(標準輸出)中,當然,printf 函數是不會一個字符一個字符去輸出,它會調用更底層的 I/O 函數:fputc去逐個字符打印

fputc 也定義於頭文件 <stdio.h>中,如下:

int fputc(int ch, FILE *stream);

fputc 函數寫入字符 ch 到給定輸出流 stream,printf函數在調用該函數時,會向stream參數傳入stdout從而打印數據到標準輸出。

那麼,要實現printf打印到串口就變得非常簡單了,只需要重新定義fputc函數,在fputc的函數中將數據通過串口發送,稱之爲:fputc重定向或者printf重定向。

2.在MDK中使用MicroLib重定向printf

勾選Use MicroLib

MicroLib是對標準C庫進行了高度優化之後的庫,供MDK默認使用,相比之下,MicroLIB的代碼更少,資源佔用更少:

重定義fputc到串口

重新實現fputc函數,編寫代碼將這個字符通過串口發送,因爲發送每個字符時都會調用該函數,所以爲了效率,不再調用庫函數 HAL_UART_Transmit 發送,而是直接操作寄存器發送。

  • 檢測串口當前狀態

STM32L431的USART串口外設有一個 ISR 寄存器,全名 Interrupt and status register, 用來指示當前串口的狀態,如圖:

其中 BIT6 TC用來指示當前串口是否發送完成,如圖:

可以通過判斷該位來判斷串口當前是否處於發送狀態,代碼如下:

while((USART1->ISR & 0X40) == 0);
  • 串口發送字符ch

同樣,爲了提高發送效率,直接使用寄存器來操作:

USART1->TDR = (uint8_t) ch;

最後實現fputc函數就變的非常簡單了,這裏我放在usart.c文件的末尾:

/* USER CODE BEGIN 1 */
#if 1
#include <stdio.h>

int fputc(int ch, FILE *stream)
{
    /* 堵塞判斷串口是否發送完成 */
    while((USART1->ISR & 0X40) == 0);

    /* 串口發送完成,將該字符發送 */
    USART1->TDR = (uint8_t) ch;

    return ch;
}
#endif

/* USER CODE END 1 */

測試printf

在main函數中測試一下printf函數是否可以正常使用:

    /* USER CODE BEGIN 2 */
    printf("Hello, i am %s\n""mculover666");
    printf("Test int: i = %d"100);
    printf("Test float: i = %f"1.234);
    printf("Test hex: i = 0x%2x",100);
    /* USER CODE END 2 */

結果如下:

3.在MDK中使用標準庫重定向printf

printf 函數使用了半主機模式,所以直接使用標準庫會導致程序無法運行,因此必須提前告知編譯器不使用半主機模式:

  • 不使用半主機模式

/* 告知連接器不從C庫鏈接使用半主機的函數 */
#pragma import(__use_no_semihosting)

/* 定義 _sys_exit() 以避免使用半主機模式 */
void _sys_exit(int x)
{
    x = x;
}

所以,重定向fputc()函數完整的代碼如下:

#if 1
#include <stdio.h>

/* 告知連接器不從C庫鏈接使用半主機的函數 */
#pragma import(__use_no_semihosting)

/* 定義 _sys_exit() 以避免使用半主機模式 */
void _sys_exit(int x)
{
    x = x;
}

/* 標準庫需要的支持類型 */
struct __FILE
{

    int handle;
};

FILE __stdout;

/*  */
int fputc(int ch, FILE *stream)
{
    /* 堵塞判斷串口是否發送完成 */
    while((USART1->ISR & 0X40) == 0);

    /* 串口發送完成,將該字符發送 */
    USART1->TDR = (uint8_t) ch;

    return ch;
}

#endif

測試printf

測試printf函數的代碼不變,在MDK設置中取消勾選USE MICROLIB,然後重新編譯,下載代碼後試驗現象如下:

4.在GCC中使用標準庫重定向printf

不同的編譯器對於C庫的底層實現機制是不同的,所以上面兩種在MDK中的實現方法,在使用Gcc編譯器的時候是不可行的。

在Gcc中重定向printf函數時注意兩個關鍵點:

  • 與重定義fputs()函數一樣,在使用Gcc編譯器的時候,需要重新定義_write函數;

  • Gcc中沒有MicroLib,只能使用標準庫;

所以重定向printf函數的代碼如下:

/* USER CODE BEGIN 1 */
#if 1
#include <stdio.h>

int _write(int fd, char *ptr, int len)  
{  
  HAL_UART_Transmit(&huart1, (uint8_t*)ptr, len, 0xFFFF);
  return len;
}
#endif
/* USER CODE END 1 */

使用STM32CubeMX生成makefile,然後使用arm-none-eabi-gcc編譯沒有問題,再使用STM32 ST-LINK utility 下載後實驗現象如下:

至此,我們已經學會實現printf()函數的多種方法,使用DAC輸出不同電壓。

上面我介紹了三種重定向printf函數的方法,你用的是哪一種呢?歡迎文末留言哈哈哈~

 


本文分享自微信公衆號 - 小熊派開源社區(BearPi-Club)。
如有侵權,請聯繫 [email protected] 刪除。
本文參與“OSC源創計劃”,歡迎正在閱讀的你也加入,一起分享。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章