如何在Keil5中在没有硬件支持的情况下使用Keil的模拟器(Simulator) + 调试窗口输出进行调试

如何在Keil5中在没有硬件支持的情况下使用Keil的模拟器(Simulator) + 调试窗口输出进行调试

  • 一、核心配置步骤
    • [1. 启用仿真器并选择软件仿真](#1. 启用仿真器并选择软件仿真)
    • [2. 问题分析与解决方案](#2. 问题分析与解决方案)
      • [2.1 问题原因](#2.1 问题原因)
      • [2.2 解决方案](#2.2 解决方案)
        • [2.2.1 创建初始化脚本文件](#2.2.1 创建初始化脚本文件)
      • [2.3 验证仿真环境](#2.3 验证仿真环境)
      • [2.4 如果问题仍然存在](#2.4 如果问题仍然存在)
  • 二、善用仿真组件与调试窗口
    • [1. 配置逻辑分析仪](#1. 配置逻辑分析仪)
      • [1.1 进入调试模式:](#1.1 进入调试模式:)
      • [1.2 打开逻辑分析仪:](#1.2 打开逻辑分析仪:)
      • [1.3 添加GPIOD12信号:](#1.3 添加GPIOD12信号:)
    • [2. 可能的报错](#2. 可能的报错)
    • [3. 解决办法:](#3. 解决办法:)
      • [3.1 尝试直接地址访问法](#3.1 尝试直接地址访问法)
      • [3.2 通过全局变量间接观察(万能方法,推荐用)](#3.2 通过全局变量间接观察(万能方法,推荐用))
  • 三、软件仿真的局限性
  • 四、实用技巧与问题排查

在STM32F407项目开发中,使用Keil MDK的 Use Simulator 进行纯软件仿真,可以在没有硬件的情况下测试和调试代码的基本逻辑,我结合工程经验为你梳理了以下设置要点和技巧。

一、核心配置步骤

1. 启用仿真器并选择软件仿真

  • 在Keil MDK中,进入 Options for TargetDebug 选项卡。

  • 选择 Use Simulator。这表明你将使用MDK内置的仿真环境,而不是物理调试器。

  • Driver DLL 中,确保选择了适用于Cortex-M内核的仿真驱动,例如 SARMCM3.DLL。这一步对正确模拟Cortex-M4内核至关重要。

  • Parameter:此处需指定你的具体芯片型号,例如STM32F407ZG芯片则填写 -pSTM32F407ZG。请确保这与你在"Target"选项卡中选择的型号一致。

  • 正确设置外部晶振频率和系统时钟。即使是在仿真中,这些时钟设置也会影响延时函数等的执行。

bash 复制代码
Dialog DLL:DARMSTM.DLL

Parameter:-pXXXXXXXXXX(必须与 Device 选项卡中选定的芯片完全一致(如STM32F407ZG → -pSTM32F407ZG))。


2. 问题分析与解决方案

bash 复制代码
*** error 65: access violation at 0x40023830 : no 'read' permission

这个错误表示在线仿真时尝试读取一个没有读取权限的内存地址 0x40023830。在STM32F407中,这个地址对应的是 RCC (复位和时钟控制) 寄存器

2.1 问题原因

  • 地址分析 : 0x40023830 是 RCC_APB2ENR 寄存器的地址
  • 权限问题: 仿真器没有正确配置对该外设地址空间的访问权限
  • 代码触发: 可能是代码中访问了RCC寄存器,但仿真环境没有正确映射

2.2 解决方案

2.2.1 创建初始化脚本文件

创建一个名为 simulation.ini 的文件,内容如下:

ini 复制代码
// STM32F407 完整仿真配置
FUNC void Setup(void) {
    // 清除现有的内存映射
    MAP 0x40000000, 0x400FFFFF UNMAP
    MAP 0x50000000, 0x50060BFF UNMAP
    
    // 重新映射所有必要的外设区域
    MAP 0x40000000, 0x400FFFFF READ WRITE EXEC  // AHB1/APB1/APB2 外设
    MAP 0x50000000, 0x50060BFF READ WRITE EXEC  // AHB2 外设 (GPIO)
    MAP 0x60000000, 0xA0000FFF READ WRITE EXEC  // AHB3 外设 (FSMC)
    
    // 特别映射关键外设区块
    MAP 0x40020000, 0x400203FF READ WRITE EXEC  // GPIOA
    MAP 0x40020400, 0x400207FF READ WRITE EXEC  // GPIOB  
    MAP 0x40020800, 0x40020BFF READ WRITE EXEC  // GPIOC
    MAP 0x40020C00, 0x40020FFF READ WRITE EXEC  // GPIOD
    MAP 0x40021000, 0x400213FF READ WRITE EXEC  // GPIOE
    MAP 0x40021400, 0x400217FF READ WRITE EXEC  // GPIOF
    MAP 0x40021800, 0x40021BFF READ WRITE EXEC  // GPIOG
    MAP 0x40021C00, 0x40021FFF READ WRITE EXEC  // GPIOH
    MAP 0x40022000, 0x400223FF READ WRITE EXEC  // GPIOI
    
    // RCC 相关寄存器
    MAP 0x40023800, 0x40023BFF READ WRITE EXEC  // RCC
    
    // 系统配置
    MAP 0x40013000, 0x400133FF READ WRITE EXEC  // SYSCFG
    MAP 0x40013800, 0x40013BFF READ WRITE EXEC  // EXTI
    
    // 调试输出
    printf("=========================================\n");
    printf("STM32F407 软件仿真环境初始化完成\n");
    printf("外设内存映射已配置\n");
    printf("=========================================\n");
}

// 执行初始化
Setup();

然后在MDK中配置使用此脚本:

  • Options for TargetDebugInitialization File
  • 选择你创建的 simulation.ini 文件

2.3 验证仿真环境

创建一个简单的测试程序来验证仿真环境:

c 复制代码
// 在main函数或其他需要的地方更新状态
int main(void)
{
    // 初始化代码...
    
    while(1)
    {
        // 翻转PD12之前或之后更新状态变量
        GPIO_ToggleBits(GPIOD, GPIO_Pin_12);                
        // 延时或其他代码
        Delay_ms(100);
    }
}

2.4 如果问题仍然存在

如果上述方法都不能解决问题,可以尝试:

  1. 重置仿真环境

    • 关闭MDK
    • 删除项目目录中的 *.uvoptx*.uvguix.* 文件
    • 重新打开项目
  2. 使用不同的仿真驱动

    • DebugSettings 中尝试不同的仿真驱动
  3. 检查MDK版本兼容性

    • 确保使用支持STM32F4系列的MDK版本

按照这些步骤操作,应该能解决 access violation 错误并成功进行STM32F407的在线仿真。

二、善用仿真组件与调试窗口

1. 配置逻辑分析仪

1.1 进入调试模式:

  • 点击 Start/Stop Debug Session 或按 Ctrl+F5

1.2 打开逻辑分析仪:

  • 菜单:View → Analysis Windows → Logic Analyzer

  • 或使用工具栏按钮

1.3 添加GPIOD12信号:

  • 在逻辑分析仪窗口中点击 Setup 按钮

  • 点击 Add New 按钮添加新信号

  • 在 Expression 栏中输入:(GPIOD->ODR & 0x1000) >> 12

2. 可能的报错

在添加引脚的时候就会出现报错:Unknown Signal !

Virtual Registers缺失:在软件仿真时,逻辑分析仪依赖Virtual Registers来识别外设寄存器。如果其中没有PORTF,输入PORTF.9等表达式就会报"Unknown Signal"。

芯片内核与仿真配置不匹配:您的工程可能使用的是Cortex-M3内核的配置(如SARMCM3.DLL),但如果实际芯片是其他内核(如M0或M4),或者仿真参数(Parameter)与芯片型号不精确匹配,就会导致外设寄存器无法访问。

Debug.ini文件未正确配置:如果没有正确配置初始化文件来映射外设寄存器的地址空间并设置读写权限,仿真时可能无法访问这些寄存器。

3. 解决办法:

直接测试下面的值尝试:

bash 复制代码
((((GPIO_TypeDef *) ((((uint32_t)0x40000000) + 0x00020000) + 0x0C00))->ODR & 0x1000) >> 12 & 0x1000) >> 12

3.1 尝试直接地址访问法

bash 复制代码
如果 PORTF 仍无法识别,直接操作对应的寄存器地址是更底层通用的方法。

查找 GPIOF 的基地址:在芯片数据手册或参考手册的存储器映射中查找 GPIOF 的基地址(例如STM32F103系列中 GPIOF 的ODR寄存器地址可能为 0x4002140C)。

在逻辑分析仪中添加信号:

添加方法:直接输入地址表达式。

逻辑分析仪中输入内容示例:*((unsigned long*)0x4002140C) & 0x0200) >> 9 (假设 0x4002140C 是 GPIOF_ODR 地址,0x0200 掩码针对Bit 9)。

说明:此法直接访问内存地址,不依赖Virtual Registers,更可靠。

添加后,同样需要设置该信号的 Display Type 为 Bit。

3.2 通过全局变量间接观察(万能方法,推荐用)

如果上述方法均无效,可以在代码中将GPIOF9的状态映射到一个全局变量,然后在逻辑分析仪中观察该变量。

在代码中定义并更新全局变量:

bash 复制代码
// 在全局区域定义变量
volatile uint8_t PD12_State = 0;

// 在main函数或其他需要的地方更新状态
int main(void)
{
    // 初始化代码...
    
    while(1)
    {
        // 翻转PD12之前或之后更新状态变量
        GPIO_ToggleBits(GPIOD, GPIO_Pin_12);
        
        // 读取PD12当前状态并更新全局变量
        PD12_State = (GPIOD->ODR & GPIO_Pin_12) ? 1 : 0;
        
        // 或者使用IDR读取实际引脚状态(如果是输入模式)
        // PD12_State = (GPIOD->IDR & GPIO_Pin_12) ? 1 : 0;
        
        // 延时或其他代码
        Delay_ms(100);
    }
}

在逻辑分析仪中直接添加全局变量 GPIOF9_State,并将其 Display Type 设置为 Bit。

三、软件仿真的局限性

非常重要的一点是,MDK的Use Simulator是一个软件仿真环境,它主要通过模拟ARM内核指令和部分核心外设来工作。

  • 外设模拟不完全 :对于STM32F407复杂的片内外设(如USB、以太网、特定型号的ADC/DMA等),仿真可能无法完全或精确地模拟其行为。你可能无法像在真实硬件上那样依赖所有这些外设的仿真结果。
  • 时序差异:仿真执行速度与真实硬件速度不同,不能用于精确的性能测量。

四、实用技巧与问题排查

  • 添加软件仿真配置脚本 :对于一些复杂的初始化(例如内存映射、时钟树),你可以编写或使用.INI格式的脚本文件,在仿真启动时通过 DebugInitialization File 指定并自动加载执行。
  • 检查初始化代码 :确保你的 SystemInit() 和时钟配置(SetSysClock() 等)函数与软件仿真环境兼容。有时过于复杂的低功耗或时钟配置可能会在仿真中引发意外行为。
  • 理解仿真日志 :关注MDK的 Command 窗口,仿真器可能会在那里输出重要的状态或错误信息。
相关推荐
烛衔溟2 小时前
C语言多级指针与函数指针:指针的高级用法
c语言·算法
GilgameshJSS2 小时前
STM32H743-ARM例程39-SD_IAP
arm开发·stm32·嵌入式硬件
hollq3 小时前
STM32F103RCT6+STM32CubeMX+keil5(MDK-ARM)+Flymcu完成轮询方式检测按键
arm开发·stm32·嵌入式硬件
d111111111d4 小时前
STM32外设学习--DMA直接存储器读取(AD扫描程序,DMA搬运)--学习笔记。
笔记·stm32·单片机·嵌入式硬件·学习
树在风中摇曳5 小时前
C语言动态内存管理:从基础到进阶的完整解析
c语言·开发语言·算法
biter down6 小时前
C 语言17:位操作符 & | ^:从二进制编码到大小端
c语言·开发语言
永远都不秃头的程序员(互关)6 小时前
C 语言文件读写初探:打开数据之门 [特殊字符]
c语言
楼田莉子10 小时前
Linux学习:进程的控制
linux·运维·服务器·c语言·后端·学习
xiaohai@Linux11 小时前
STM32上使用HAL库完美实现驱动MAX98357声卡模块(I2S+DMA+音频环形缓冲区)
stm32·单片机·嵌入式硬件·音视频