在 ARM 編譯器(如 Keil MDK) 中禁用半主機(Semihosting)并實現標準庫的基本功能,需要以下步驟:
1. 禁用半主機
#pragma import(__use_no_semihosting) // 禁用半主機模式
- 作用:防止標準庫函數(如
printf
、scanf
)依賴調試器進行 I/O 操作。 - 后果:必須手動實現底層函數(如
_sys_exit
、fputc
),否則會鏈接失敗。
2. 定義簡化 FILE
結構體
struct __FILE {int handle; // 占位符,無實際用途(可簡化)
};
FILE __stdout, __stdin; // 標準輸入/輸出流
- 說明:
- 標準庫需要
FILE
結構體,但禁用半主機后無需復雜實現,僅需滿足編譯要求。 - 如果不需要文件操作,可直接定義為空結構體:
struct __FILE { int dummy; };
- 標準庫需要
3. 必須實現的系統函數
(1) 程序退出處理 _sys_exit
#include "stm32f10x.h" // 假設使用 STM32void _sys_exit(int x) {// NVIC_SystemReset(); // 硬件復位(推薦)// 或 while(1); // 簡單死循環
}
- 作用:覆蓋庫的默認退出函數,避免鏈接錯誤。
- 注意:
- 如果調用
exit()
或程序結束,會執行此函數。
- 如果調用
(2) 輸出重定向 fputc
(支持 printf
)
int fputc(int ch, FILE *f) {USART_SendData(USART1, (uint8_t)ch); // 發送到 USART1while (!USART_GetFlagStatus(USART1, USART_FLAG_TXE)); // 等待發送完成return ch;
}
- 關鍵點:
printf
依賴此函數輸出字符。- 需提前初始化 USART(波特率、引腳等)。
(3) 輸入重定向 fgetc
(支持 scanf
)
int fgetc(FILE *f) {while (!USART_GetFlagStatus(USART1, USART_FLAG_RXNE)); // 等待接收數據return (int)USART_ReceiveData(USART1);
}
- 關鍵點:
scanf
依賴此函數讀取字符。- 檢查
USART_FLAG_RXNE
(接收標志),而非TC
(發送完成)。
4. 完整示例代碼
#pragma import(__use_no_semihosting)#include <stdio.h>
#include "stm32f10x.h" // STM32 頭文件// 簡化 FILE 結構體
struct __FILE { int dummy; };
FILE __stdout, __stdin;// 系統函數
void _sys_exit(int x) { NVIC_SystemReset(); //也可以為空 }// 輸出重定向(printf)
int fputc(int ch, FILE *f) {USART_SendData(USART1, (uint8_t)ch);while (!USART_GetFlagStatus(USART1, USART_FLAG_TXE));return ch;
}// 輸入重定向(scanf)
int fgetc(FILE *f) {while (!USART_GetFlagStatus(USART1, USART_FLAG_RXNE));return (int)USART_ReceiveData(USART1);
}int main() {// 初始化 USART1(需自行實現)USART_InitTypeDef USART_InitStruct = { ... };USART_Init(USART1, &USART_InitStruct);USART_Cmd(USART1, ENABLE);printf("Hello, No-Semihosting!\n"); // 通過 USART1 輸出int num;scanf("%d", &num); // 從 USART1 讀取輸入return 0;
}
5. 關鍵注意事項
功能 | 實現要求 |
---|---|
禁用半主機 | #pragma import(__use_no_semihosting) |
FILE 結構體 | 定義 struct __FILE 和 __stdout /__stdin (可簡化) |
_sys_exit | 必須實現,建議硬件復位(NVIC_SystemReset() )或死循環(while(1) ) |
fputc | 重定向 printf 到硬件(如 UART) |
fgetc | 重定向 scanf 從硬件讀取(需檢查 USART_FLAG_RXNE ) |
硬件初始化 | 確保 USART/UART 已正確配置(波特率、引腳模式等) |
6. 常見問題解決
- 問題1:
printf
無輸出- 檢查
fputc
是否實現,并確認 USART 初始化正確。
- 檢查
- 問題2:鏈接錯誤
undefined _sys_exit
- 確保所有必需函數(
_sys_exit
、fputc
等)均已實現。
- 確保所有必需函數(
- 問題3:
scanf
無法接收數據- 檢查
fgetc
是否使用USART_FLAG_RXNE
,而非TC
標志。
- 檢查
7. 擴展適配其他硬件
- USB-CDC 重定向:替換
fputc
/fgetc
為 USB 通信函數。 - LCD 輸出:修改
fputc
將字符顯示到 LCD。 - GCC 編譯器:需實現
_write
和_read
而非fputc
/fgetc
。
如果需要針對 特定硬件平臺(如 STM32、ESP32、NXP) 的詳細配置代碼,請提供具體型號!