Keil MDK 使用方式与典型场景
四、使用方式与典型场景
示例一:把 printf 重定向到串口(调试阶段最实用的改造)
/* 放在 main.c 的 user code 区域,或单独的 retarget.c 里 */
#include <stdio.h>
#include "main.h"
extern UART_HandleTypeDef huart2; /* 由 CubeMX 生成的串口句柄 */
/* 方法一:用 MicroLIB 时的经典重定向 */
int fputc(int ch, FILE *f)
{
HAL_UART_Transmit(&huart2, (uint8_t *)&ch, 1, HAL_MAX_DELAY);
return ch;
}
/* 方法二:不勾 MicroLIB(用标准库)时,需要补下面这些 */
#if !defined(__MICROLIB)
int _write(int fd, char *ptr, int len)
{
HAL_UART_Transmit(&huart2, (uint8_t *)ptr, len, HAL_MAX_DELAY);
return len;
}
/* 标准库不带半主机模式,否则未接线时会卡死 */
void _sys_exit(int x) { (void)x; }
void _ttywrch(int ch) { (void)ch; }
FILE __stdout;
int _isatty(int fd) { (void)fd; return 1; }
#endif
/* 使用:注意浮点格式化会显著增大体积,调试时可只打整数 */
// printf("tick=%lu\r\n", (unsigned long)HAL_GetTick());
这里最重要的一条经验是:使用标准库而不勾 MicroLIB 时,必须禁用半主机模式,否则程序会在第一次 printf 时停在 BKPT 指令上,表现为「下载成功但不运行」。这是 STM32 + MDK 新手最经典的坑之一。
示例二:用分散加载文件把变量放进指定内存段
; 在工程的 Options for Target → Linker 里选择 Use Memory Layout from Target Dialog 取消
; 改用自定义 .sct 文件,示意结构如下
LR_IROM1 0x08000000 0x00100000 { ; 1MB Flash
ER_IROM1 0x08000000 0x00100000 {
*.o (RESET, +First) ; 中断向量表必须放最前
*(InRoot$$Sections)
.ANY (+RO) ; 其余只读内容
}
RW_IRAM1 0x20000000 0x00020000 { ; 128KB 主 RAM
.ANY (+RW +ZI)
}
RW_IRAM2 0x20020000 0x00010000 { ; 另一块 RAM,放不想被初始化的缓冲
*(.dma_buffer)
}
}
; 代码侧这样把数组放进去:
// __attribute__((section(".dma_buffer"))) uint8_t dma_buf[4096];
分散加载在两类场景里不可替代:一是有 Bootloader 时要把应用挪到 Flash 的某个偏移(同时要改向量表偏移);二是DMA 需要特定内存区域(某些芯片的 DMA 只能访问特定 RAM 块)。写错 .sct 的典型症状是「编译通过但一跑就 HardFault」。
适合与不适合的场景
适合:Cortex-M 系列芯片的裸机与 RTOS 开发;需要成熟调试体验(外设寄存器视图、实时变量观察)的场景;团队已有 MDK 授权与流程;配合 CubeMX 做 STM32 项目。
不适合:需要命令行构建与 CI 自动化的团队(MDK 的自动化能力弱于 GCC/CMake);代码体积超过免费版限制又不想购买授权的项目;跨平台(Linux/macOS 主力)开发团队;非 Cortex-M 架构的芯片。

