重定向printf函數到串口輸出


轉自:https://mculover666.blog.csdn.net/article/details/99842909

 

本文詳細的介紹了如何重定向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的代碼更少,資源占用更少:

mark

重定義fputc到串口

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

  • 檢測串口當前狀態

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

mark

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

mark

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

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 */ 

 

結果如下:

mark

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

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

  • 不使用半主機模式
/* 告知連接器不從C庫鏈接使用半主機的函數 */ #pragma import(__use_no_semihosting) /* 定義 _sys_exit() 以避免使用半主機模式 */ void _sys_exit(int x) { x = x; } 

 

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

#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,然后重新編譯,下載代碼后試驗現象如下:

mark

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 下載后實驗現象如下:

mark

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM