💡UART#1 窗口 是 Keil 调试模式下的"虚拟串口终端",可实时查看 MCU 串口输出、模拟输入数据。无论是否拥有硬件,都能高效调试串口通信代码。
🔧 1. 核心概念
🧪 软件仿真模式
无需真实开发板,Keil 模拟 MCU 外设。UART#1 窗口直接显示 printf 或串口发送的数据,并可模拟串口输入。适合算法验证、初期调试。
无硬件 · 快速迭代
🔌 硬件调试模式
连接 J-Link/ST-Link 等调试器,真实运行目标板。通过 ASSIGN 命令将物理串口映射到 UART#1 窗口,实现输出捕获。
真实外设 · 映射显示
💻 2. 软件仿真模式 ------ 让 UART#1 显示 printf
📌 步骤 1:开启软件仿真
- 点击菜单
Project → Options for Target(或工具栏"魔术棒"图标)。 - 切换到
Debug标签页 → 选中Use Simulator→ 确定。 - 按
Ctrl+F5进入调试模式。 - 通过菜单
View → Serial Windows → UART #1打开串口窗口。
📌 步骤 2:重定向 printf → 串口1 (关键代码)
在工程中添加以下代码,将标准输出映射到 USART1,否则 printf 无法显示在 UART#1 窗口。
/* 禁用半主机模式 (必须) */
#pragma import(__use_no_semihosting_swi)
struct __FILE { int handle; };
FILE __stdout;
void _sys_exit(int x) {
x = x;
}
/* 重定向 fputc 函数 ------ 将 printf 内容发往 USART1 */
int fputc(int ch, FILE *f) {
// 等待发送缓冲区为空 (根据芯片库函数调整)
while (!(USART1->SR & (1<<7))); // 示例: 检查 TXE 标志
USART1->DR = (uint8_t)ch;
return ch;
}
📢 提示: 如果使用标准外设库,常用写法为 while(USART_GetFlagStatus(USART1, USART_FLAG_TXE)==RESET);
HAL 库用户可使用 while(!(USART1->ISR & USART_ISR_TXE_TXFIFOE)); 或者直接调用 HAL_UART_Transmit 实现重定向。
📌 步骤 3:编写测试代码 & 运行
// 初始化 USART1 (波特率 115200, 8N1 等)
void UART1_Init(void) {
// 使能时钟,配置 GPIO, 波特率等 (略)
// 以 STM32 为例: RCC_APB2PeriphClockCmd...
USART_Init(USART1, &USART_InitStruct);
USART_Cmd(USART1, ENABLE);
}
int main(void) {
UART1_Init();
printf("Hello from Keil UART#1!\r\n");
printf("当前仿真时间: 正常输出\r\n");
while(1);
}
全速运行 (F5) 后,UART#1 窗口 会显示打印的字符串,效果与真实串口助手完全一致。
🌍 3. 硬件调试模式 ------ 映射物理串口到 UART#1 窗口
当连接真实开发板调试时,MCU 实际通过 TX/RX 引脚输出数据,Keil 默认不会自动抓取。使用 ASSIGN 命令建立映射关系。
⚙️ 操作流程
- 进入硬件调试模式(Options for Target → Debug → 选择硬件调试器如 ST-Link, J-Link)。
- 打开
View → Serial Windows → UART #1窗口。 - 同时打开
Command窗口 (View → Command Window)。 - 在
Command窗口输入映射命令后回车:
ASSIGN WIN1 < S0IN > S0OUT
命令解析:
WIN1→ 对应 UART #1 窗口。S0IN/S0OUT→ Keil 仿真器虚拟串口通道,S0一般映射到 MCU 的 USART1。对于 USART2 则使用S1IN / S1OUT。- 执行后,真实硬件通过 USART1 发出的所有数据会实时显示在 UART#1 窗口。
✅ 注意事项: 某些较新的 Cortex-M 系列需要确保调试接口 SWO 或者串口引脚未被占用。若命令无效,检查芯片型号是否支持 ASSIGN 映射。另外也可以使用 ITM 方式,但 ASSIGN 是最直观的串口映射方法。
📡 示例:多串口映射
| UART 外设 | 目标窗口 | ASSIGN 命令 |
|---|---|---|
| USART1 | UART #1 | ASSIGN WIN1 < S0IN > S0OUT |
| USART2 | UART #2 | ASSIGN WIN2 < S1IN > S1OUT |
| USART3 | UART #3 | ASSIGN WIN3 < S2IN > S2OUT |
⌨️ 4. 进阶技巧:向 MCU 发送模拟数据(软件仿真)
在软件仿真下,你可以通过 Command 窗口模拟串口接收数据,用于测试协议解析、命令响应。
- 确保处于软件仿真模式,并已打开
UART #1窗口和Command窗口。 - 在 Command 窗口输入模拟发送命令:
S0IN = 'A' // 发送字符 A
S0IN = 0x31 // 发送十六进制 0x31 (ASCII '1')
S0IN = "Hello" // 发送字符串
此时 MCU 的串口接收中断或查询方式会收到上述数据,程序可进行相应处理,便于调试 AT 指令等逻辑。
💡**提示:**硬件调试模式下也可以通过类似方式注入数据,但需要确保硬件上无电气冲突;推荐纯软件仿真时使用此功能。
⚡ 5. 性能优化:加速串口输出(仿真模式)
软件仿真时串口数据传输会按位时间模拟,速度较慢。可以修改 S0TIME 参数强制让数据"瞬间"传输完毕。
S0TIME = 0
S0TIME 代表发送每一位数据需要的时间(单位:秒),默认值为 0.000104(约 9600 波特率每一位时长)。设为 0 后,输出不再延迟,极大提高调试效率。若恢复模拟真实波特率可重新赋正值。
🚀 小贴士:S0TIME = 0 对于大量 printf 调试信息尤其有用,避免长时间等待仿真输出。
❓ 6. 常见问题与排查
| 现象 | 可能原因 & 解决方案 |
|---|---|
| UART#1 窗口无任何输出 | 1. 未重定向 fputc 或者重定向函数内未发送数据。 2. 软件仿真下未正确配置系统时钟,导致外设未使能。 3. 硬件调试下未执行 ASSIGN 映射命令。 4. 检查串口初始化代码是否真正使能了 USART。 |
| printf 卡死/程序跑飞 | 多半是半主机模式问题,确保添加了 #pragma import(__use_no_semihosting_swi) 及空实现 _sys_exit。 |
| 硬件映射后无输出但硬件真实串口有数据 | 确认调试器连接稳定,部分 MCU 需要正确配置 DBGMCU 寄存器以允许调试时串口继续工作;也可以使用逻辑分析仪对比。 |
| Command窗口无法识别ASSIGN | 请检查是否在硬件调试会话中且芯片支持。部分较老的ARM7/9支持,Cortex-M系列通常支持。也可以尝试将串口映射到 ITM 控制台。 |
| 输出乱码 | 波特率/时钟配置与实际仿真设置不符?仿真默认按目标时钟计算,若时钟初始化错误则乱码。检查 SystemInit 及波特率寄存器。 |
📁 7. 完整示例流程(软件仿真+硬件通用)
📌 快捷检查表:
-
✔ 工程中包含 UART 初始化代码(波特率配置正确)。
-
✔ 重定向代码已添加(对于 printf 方式)或直接使用
USART_SendData发送,但显示到 UART#1 仍需重定向或映射。 -
✔ 进入调试模式 → 打开 UART #1 窗口 → 如果是硬件模式则执行 ASSIGN 命令。
-
✔ 运行程序,查看窗口实时打印。
-
✔ 想模拟接收,软件仿真下使用
S0IN = "指令"。// 典型初始化 + 重定向整体示例 (适用于STM32F103 标准库)
#include "stm32f10x.h"
#include <stdio.h>#pragma import(__use_no_semihosting_swi)
struct __FILE { int handle; };
FILE __stdout;
void _sys_exit(int x) { x = x; }int fputc(int ch, FILE *f) {
while(USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);
USART_SendData(USART1, (uint8_t)ch);
return ch;
}void USART1_Config(void) {
GPIO_InitTypeDef GPIO_InitStructure;
USART_InitTypeDef USART_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1 | RCC_APB2Periph_GPIOA, ENABLE);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_Init(GPIOA, &GPIO_InitStructure);
USART_InitStructure.USART_BaudRate = 115200;
USART_InitStructure.USART_WordLength = USART_WordLength_8b;
USART_InitStructure.USART_StopBits = USART_StopBits_1;
USART_InitStructure.USART_Parity = USART_Parity_No;
USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
USART_Init(USART1, &USART_InitStructure);
USART_Cmd(USART1, ENABLE);
}int main(void) {
USART1_Config();
printf("✅ Keil UART#1 窗口调试成功!\r\n");
while(1);
}
🎯结论: 掌握 UART#1 窗口相当于拥有一个集成在调试器里的"虚拟串口终端"。无论是代码初期仿真还是后期硬件联调,都能大幅提升串口相关功能的调试效率。灵活使用printf重定向、ASSIGN映射和S0TIME加速,让调试更加丝滑。
Keil µVision 调试技巧 · UART#1 完整指南 | 适用于 MDK v5 / v6 及主流 Cortex-M 系列