在 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) 的详细配置代码,请提供具体型号!