本文所使用的方法與代碼參考自正點原子,如果想要詳細(xì)了解這方面的知識,請閱讀正點原子官方提供的文檔。
一、背景
在開發(fā)STM32應(yīng)用時,將一些信息通過串口打印到電腦上是常用的調(diào)試手段。C語言標(biāo)準(zhǔn)庫中的printf函數(shù)是我們常用的打印函數(shù)。但是在STM32應(yīng)用下一般無法直接使用這個函數(shù),正點原子給出的解釋如下,有興趣可以詳細(xì)了解一下。
標(biāo)準(zhǔn)庫下的 printf 為調(diào)試屬性的函數(shù),如果直接使用,會使單片機(jī)進(jìn)入半主機(jī)模式(semihosting),這是一種調(diào)試模式,直接下載代碼后出現(xiàn)程序無法運(yùn)行,但是在連接調(diào)試器進(jìn)行 Debug 時程序反而能正常工作的情況。半主機(jī)是 ARM 目標(biāo)的一種機(jī)制,用于將輸入/輸出請求從應(yīng)用程序代碼通信到運(yùn)行調(diào)試器的主機(jī)。例如,此機(jī)制可用于允許 C 庫中的函數(shù)(如 printf()和 scanf())使用主機(jī)的屏幕和鍵盤,而不是在目標(biāo)系統(tǒng)上設(shè)置屏幕和鍵盤。這很有用,因為開發(fā)硬件通常不具有最終系統(tǒng)的所有輸入和輸出設(shè)備,如屏幕、鍵盤等。半主機(jī)是通過一組定義好的軟件指令(如 SVC)SVC 指令(以前稱為 SWI 指令)來實現(xiàn)的,這些指令通過程序控制生成異常。應(yīng)用程序調(diào)用相應(yīng)的半主機(jī)調(diào)用,然后調(diào)試代理處理該異常。調(diào)試代理(這里的調(diào)試代理是仿真器)提供與主機(jī)之間的必需通信。也就是說使用半主機(jī)模式必須使用仿真器調(diào)試。
如果想在獨(dú)立環(huán)境下運(yùn)行調(diào)試功能的函數(shù),我們這里是 printf,printf 對字符 ch 處理后寫入文件 f,最后使用 fputc 將文件 f 輸出到顯示設(shè)備。對于 PC 端的設(shè)備,fputc 通過復(fù)雜的源碼,最終把字符顯示到屏幕上。那我們需要做的,就是把 printf 調(diào)用的 fputc 函數(shù)重新實現(xiàn),重定向fputc 的輸出,同時避免進(jìn)入半主模式。
目前想要在SMT32上使用printf有兩種方法:
- 通過代碼取消ARM的半主機(jī)工作模式,并重定向printf函數(shù)
- 使用微庫MicroLib,并重定向printf函數(shù)。
由于微庫裁剪了許多標(biāo)準(zhǔn)庫的功能,如果注重功能完整性建議使用第一種方法。
二、取消ARM的半主機(jī)工作模式
添加stdio.h頭文件,并在程序中加入以下代碼段即可(代碼引自正點原子)
/******************************************************************************************/
/* 在合適的位置引用下面頭文件 */
#include <stdio.h>
/* 加入以下代碼, 支持printf函數(shù), 而不需要選擇use MicroLIB */
#if 1
#if (__ARMCC_VERSION >= 6010050) /* 使用AC6編譯器時 */
__asm(".global __use_no_semihosting\n\t"); /* 聲明不使用半主機(jī)模式 */
__asm(".global __ARM_use_no_argv \n\t"); /* AC6下需要聲明main函數(shù)為無參數(shù)格式,否則部分例程可能出現(xiàn)半主機(jī)模式 */
#else
/* 使用AC5編譯器時, 要在這里定義__FILE 和 不使用半主機(jī)模式 */
#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
/* 不使用半主機(jī)模式,至少需要重定義_ttywrch\_sys_exit\_sys_command_string函數(shù),以同時兼容AC6和AC5模式 */
int _ttywrch(int ch)
{
ch = ch;
return ch;
}
/* 定義_sys_exit()以避免使用半主機(jī)模式 */
void _sys_exit(int x)
{
x = x;
}
char *_sys_command_string(char *cmd, int len)
{
return NULL;
}
/* FILE 在 stdio.h里面定義. */
FILE __stdout;
/* 重定義fputc函數(shù), printf函數(shù)最終會通過調(diào)用fputc輸出字符串到串口
其中串口可根據(jù)實際使用情況調(diào)整 */
int fputc(int ch, FILE *f)
{
while ((USART1->SR & 0X40) == 0); /* 等待上一個字符發(fā)送完成 */
USART1->DR = (uint8_t)ch; /* 將要發(fā)送的字符 ch 寫入到DR寄存器 */
return ch;
}
#endif
/***********************************************END*******************************************/
上面代碼段使用的是串口1(USART1),可根據(jù)實際使用情況調(diào)整。
三、使用微庫MicroLib
直接在Keil中的如下界面勾選使用微庫
并添加如下代碼段重定向fputc
/* 在合適的位置引用下面頭文件 */
#include <stdio.h>
/* 重定義 fputc 函數(shù), printf 函數(shù)最終會通過調(diào)用 fputc 輸出字符串到串口 */
/* 串口可根據(jù)實際使用情況調(diào)整 */
int fputc(int ch, FILE *f)
{
while ((USART1->ISR & 0X40) == 0); /* 等待上一個字符發(fā)送完成 */
USART1->TDR = (uint8_t)ch; /* 將要發(fā)送的字符 ch 寫入到 DR 寄存器 */
return ch;
}
微庫由于裁剪掉了一些功能,有著如下特點:
- 微庫會優(yōu)化代碼空間,但會降低某些程序的執(zhí)行效率(比如: memcpy()),效率換空間
- 微庫不支持浮點運(yùn)算,所以在有FPU單元的MCU上,使用MicroLIB并開啟FPU會讓程序死機(jī)或跑飛
- 微庫不支持C++,在使用C++開發(fā)MCU時不能使用MicroLib
- 微庫不支持操作系統(tǒng)函數(shù)
更詳細(xì)的講解可參見博文STM32程序不運(yùn)行與MicroLIB講解
四、應(yīng)用
采用了上面任意一種方法設(shè)置后,我們便可在程序中使用printf,并通過串口打印在電腦端的串口助手上。文章來源:http://www.zghlxwxcb.cn/news/detail-782748.html
printf("123\r\n");
HAL_Delay(500);
文章來源地址http://www.zghlxwxcb.cn/news/detail-782748.html
到了這里,關(guān)于【STM32學(xué)習(xí)5】STM32使用printf函數(shù) 打印到電腦串口助手的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!