【STM32 系列】多路USART串口Printf重定向詳解——通用
引言
通常情況下,標準的printf函數只能將輸出重定向到一個串口。然而,當我們需要在多個串口上進行輸出,而又不希望為每個串口單獨封裝發送函數時,就可以考慮將printf的輸出重定向到多個串口。接下來,本文將詳細介紹如何實現這一目標。
注:
- 本篇文章的代碼根據正點原子所提供代碼改寫。
- 正點原子所提供代碼能在不使用半主機模式下能夠正確編譯和運行代碼,這包括聲明一些特殊的匯編指令和函數定義,以避免編譯器默認使用半主機模式(不需要勾選 “Use MicroLIB”)。
- 本篇文章代碼是基于CubeMX編寫的HAL庫,但是所涉及HAL庫的內容不多,稍微修改,即可兼容標準庫。
單個串口的printf重定向
單個串口的重定向,可以參考正點原子所提供代碼,直接cope進自己工程中即可。
//usart.c/*****************************************************************************************/
/ 加入以下代碼, 支持printf函數, 而不需要選擇use MicroLIB */if 1
if (__ARMCC_VERSION >= 6010050) /* 使用AC6編譯器時 */
__asm(".global __use_no_semihosting\n\t"); /* 聲明不使用半主機模式 /
__asm(".global __ARM_use_no_argv \n\t"); / AC6下需要聲明main函數為無參數格式,否則部分例程可能出現半主機模式 */else
/* 使用AC5編譯器時, 要在這里定義__FILE 和 不使用半主機模式 */
pragma import(__use_no_semihosting)
struct __FILE
{
int handle;
/* Whatever you require here. If the only file you are using is /
/ standard output using printf() for debugging, no file handling /
/ is required. */
};endif
/* 不使用半主機模式,至少需要重定義_ttywrch_sys_exit_sys_command_string函數,以同時兼容AC6和AC5模式 */
int _ttywrch(int ch)
{
ch = ch;
return ch;
}/* 定義_sys_exit()以避免使用半主機模式 */
void _sys_exit(int x)
{
x = x;
}char *_sys_command_string(char *cmd, int len)
{
return NULL;
}/* FILE 在 stdio.h里面定義 */
FILE __stdout;/* 重定義fputc函數, printf函數最終會通過調用fputc輸出字符串到串口 */
int fputc(int ch, FILE f)
{
while ((USART_UX->ISR & 0X40) == 0); / 等待上一個字符發送完成 */USART_UX->TDR = (uint8_t)ch; /* 將要發送的字符 ch 寫入到DR寄存器 */ return ch;}
endif
/******************************************************************************************/
多串口的printf重定向
usart.h文件
對正點原子的源碼進行了一點小的改動,首先在usart.h文件中添加了一個枚舉類型,方便用來索引某個串口,用于printf定向:

其次增加了一個新的串口句柄,存儲當前使用的USART句柄(標準庫注意修改此處):
![]()
最后添加了一個當前串口的索引,用于存儲當前使用的USART索引:
![]()
以下就是上面所說的在usart.h中所需要增加的代碼:
//usart.h/******************** 以下是多路USART串口printf重定向 ********************/
/* 定義USART索引枚舉 /
typedef enum {
USART_NONE, / 無USART /
USART1_IDX, / USART1索引 /
USART2_IDX, / USART2索引 /
USART3_IDX, / USART3索引 */
}Current_USART_Indx;extern UART_HandleTypeDef* Current_USART_Handle; /* 當前某個USART的句柄 /
extern Current_USART_Indx Current_USART_Printf_Indx; / 當前某個USART的索引 */
void Set_Current_USART(Current_USART_Indx indx); /* 函數聲明,用于設置當前使用的USART */
usart.c文件
Set_Current_USART函數用于設置當前使用的USART。它接受一個Current_USART_Indx類型的參數,并根據該參數更新Current_USART_Handle句柄和Current_USART_Printf_Indx索引:
/*
* 簡介:設置當前使用的USART
* 參數:indx - 要設置的USART索引
* 這個參數可以是:USARTx_IDX,其中x可以從1~3
* 使用舉例:(必須要將其放在printf函數前面,指定其中一個串口)
* Set_Current_USART(USART1_IDX);
* printf("我是串口1\r\n");
*/
void Set_Current_USART(Current_USART_Indx indx)
{
switch(indx)
{
case USART1_IDX:
Current_USART_Handle = &huart1;
Current_USART_Printf_Indx = USART1_IDX;
break;
case USART2_IDX:
Current_USART_Handle = &huart2;
Current_USART_Printf_Indx = USART2_IDX;
break;
case USART3_IDX:
Current_USART_Handle = &huart3;
Current_USART_Printf_Indx = USART3_IDX;
break;
default:
Current_USART_Handle = NULL;
Current_USART_Printf_Indx = USART_NONE;
break;
}
}
fputc函數是printf函數輸出字符時調用的底層函數,進行了一些改變,使其可以隨時自定義重定向到不同串口:
/*
* 簡介:重定義fputc函數,用于將字符輸出到當前設置的USART
* 參數:
* ch - 要發送的字符
* f - 文件指針(在此實現中未使用)
* 返回值:發送的字符(或EOF如果出錯)
*/
int fputc(int ch, FILE *f)
{
if(Current_USART_Handle == NULL){ /* 如果當前沒有設置USART句柄,則返回EOF表示錯誤 */
return EOF;
}
/* 根據當前設置的USART句柄,選擇對應的USART外設發送字符 */
if(Current_USART_Handle == &huart1){
while ((USART1->ISR & 0X40) == 0); /* 等待USART1發送完成,然后發送字符 */
USART1->TDR = (uint8_t)ch; /* 將要發送的字符 ch 寫入到DR寄存器 */
}
else if(Current_USART_Handle == &huart2){
while ((USART2->ISR & 0X40) == 0); /* 等待USART2發送完成,然后發送字符 */
USART2->TDR = (uint8_t)ch; /* 將要發送的字符 ch 寫入到DR寄存器 */
}
else if(Current_USART_Handle == &huart3){
while ((USART3->ISR & 0X40) == 0); /* 等待USART3發送完成,然后發送字符 */
USART3->TDR = (uint8_t)ch; /* 將要發送的字符 ch 寫入到DR寄存器 */
}
return ch; /* 返回發送的字符 */
}
此文件中全部的重定向代碼:
#if 1 #if (__ARMCC_VERSION >= 6010050) /* 使用AC6編譯器時 */ __asm(".global __use_no_semihosting\n\t"); /* 聲明不使用半主機模式 */ __asm(".global __ARM_use_no_argv \n\t"); /* AC6下需要聲明main函數為無參數格式,否則部分例程可能出現半主機模式 */else
/* 使用AC5編譯器時, 要在這里定義__FILE 和 不使用半主機模式 */
pragma import(__use_no_semihosting)
struct __FILE
{
int handle;
/* Whatever you require here. If the only file you are using is /
/ standard output using printf() for debugging, no file handling /
/ is required. */
};endif
/* 不使用半主機模式,至少需要重定義_ttywrch_sys_exit_sys_command_string函數,以同時兼容AC6和AC5模式 */
int _ttywrch(int ch)
{
ch = ch;
return ch;
}/* 定義_sys_exit()以避免使用半主機模式 */
void _sys_exit(int x)
{
x = x;
}char *_sys_command_string(char *cmd, int len)
{
return NULL;
}/************************** 以下是多串口printf重定向函數 *************************/
/ FILE 在 stdio.h里面定義 */
FILE __stdout;UART_HandleTypeDef* Current_USART_Handle = NULL;
Current_USART_Indx Current_USART_Printf_Indx = USART_NONE;/*
- 簡介:重定義fputc函數,用于將字符輸出到當前設置的USART
- 參數:
- ch - 要發送的字符
- f - 文件指針(在此實現中未使用)
- 返回值:發送的字符(或EOF如果出錯)
*/
int fputc(int ch, FILE f)
{
if(Current_USART_Handle == NULL){ / 如果當前沒有設置USART句柄,則返回EOF表示錯誤 /
return EOF;
}
/ 根據當前設置的USART句柄,選擇對應的USART外設發送字符 /
if(Current_USART_Handle == &huart1){
while ((USART1->ISR & 0X40) == 0); / 等待USART1發送完成,然后發送字符 /
USART1->TDR = (uint8_t)ch; / 將要發送的字符 ch 寫入到DR寄存器 /
}
else if(Current_USART_Handle == &huart2){
while ((USART2->ISR & 0X40) == 0); / 等待USART2發送完成,然后發送字符 /
USART2->TDR = (uint8_t)ch; / 將要發送的字符 ch 寫入到DR寄存器 /
}
else if(Current_USART_Handle == &huart3){
while ((USART3->ISR & 0X40) == 0); / 等待USART3發送完成,然后發送字符 /
USART3->TDR = (uint8_t)ch; / 將要發送的字符 ch 寫入到DR寄存器 /
}
return ch; / 返回發送的字符 /
}
/- 簡介:設置當前使用的USART
- 參數:indx - 要設置的USART索引
- 這個參數可以是:USARTx_IDX,其中x可以從1~3
- 使用舉例:(必須要將其放在printf函數前面,指定其中一個串口)
Set_Current_USART(USART1_IDX);printf("我是串口1\r\n");/
void Set_Current_USART(Current_USART_Indx indx)
{
switch(indx)
{
case USART1_IDX:
Current_USART_Handle = &huart1;
Current_USART_Printf_Indx = USART1_IDX;
break;
case USART2_IDX:
Current_USART_Handle = &huart2;
Current_USART_Printf_Indx = USART2_IDX;
break;
case USART3_IDX:
Current_USART_Handle = &huart3;
Current_USART_Printf_Indx = USART3_IDX;
break;
default:
Current_USART_Handle = NULL;
Current_USART_Printf_Indx = USART_NONE;
break;
}
}
/************************* 以下是單串口printf重定向函數 ************************/
/
- 簡介:單個串口printf重定向fputc函數
- 重定義fputc函數, printf函數最終會通過調用fputc輸出字符串到串口
- int fputc(int ch, FILE *f)
- {
- while ((USART2->ISR & 0X40) == 0); 等待上一個字符發送完成
- USART2->TDR = (uint8_t)ch; 將要發送的字符 ch 寫入到DR寄存器
- return ch;
- }
*/
endif
main.c中引用
在使用printf函數之前,需要先調用Set_Current_USART函數設置當前使用的USART。然后,就可以像平常一樣使用printf函數了,輸出的字符串將會通過指定的USART發送到串口。
Set_Current_USART(USART1_IDX); /* 想要指定不同串口必須在printf前加上此函數 */
printf("我是串口1\r\n");
Set_Current_USART(USART2_IDX); /* 想要指定不同串口必須在printf前加上此函數 */
printf("我是串口2\r\n");
Set_Current_USART(USART3_IDX); /* 想要指定不同串口必須在printf前加上此函數 */
printf("我是串口3\r\n");
通過這種方式,我們可以非常方便地在STM32項目中實現多路USART串口printf重定向,從而大大提高調試的效率和便利性。
博客導航
本文來自博客園,作者:膝蓋中箭衛兵,轉載請注明原文鏈接:http://www.rzrgm.cn/Skyrim-sssuuu/p/18774946

浙公網安備 33010602011771號
https://orcid.org/0000-0001-5102-772X