手把手教你使用MDK仿真调试

当今的嵌入式系统开发领域中,高效的调试工具对于工程师来说至关重要。它们能够极大地减少开发周期中的错误追踪时间,并加速产品的上市时间。MDK作为业界领先的嵌入式开发工具之一,其内置的调试功能被广大开发者所赞誉。这些功能不仅提供了对代码执行的深入洞察,还允许开发者在实时环境中监控和修改系统行为。通过本文,我们将一起探索 MDK 的调试功能如何成为嵌入式开发者的得力助手,以及它如何助力我们构建更加稳定、高效的嵌入式应用。

1. 源码下载及前置阅读

  • STM32F103C8T6模板工程

链接:https://pan.baidu.com/s/1n7XHCaMYtASWdJH2uA5yDA?pwd=lw59 提取码:lw59

  • MDK5安装包

链接:https://pan.baidu.com/s/1j7USS7-rsmr7-GZ_BIIEYQ?pwd=5q3s 提取码:5q3s

  • 芯片固件包

链接:https://pan.baidu.com/s/1Td21MOEshL7qE4afCBSjtw?pwd=86uh 提取码:86uh

如果你是个零基础的小白,连 STM32 都没见过,我也给你准备了一个保姆级教程,手把手教你搭建好 STM32 开发环境,并教你如何下载程序,简直业界良心!

零基础快速上手STM32开发(手把手保姆级教程):https://www.lxlinux.net/e/stm32/stm32-quick-start-for-beginner.html

如果你连代码都不知道怎么烧录到 STM32 的,可以参考下文,提供了 5 种代码烧录方式:

STM32下载程序的五种方法:https://www.lxlinux.net/e/stm32/five-ways-to-flash-program-to-stm32.html

新手小白如果连 MDK 的使用都不熟悉,那么可以通过下文先熟悉一下 MDK 的使用:

一文教你使用MDK开发工具:https://www.lxlinux.net/e/stm32/mdk-development-tool-tutorial.html

2. MDK仿真调试配置

在 MDK 仿真前,我们需要对其进行环境配置。

  • 打开 Keil5 软件,打开或者新建一个工程。

  • 点击 Options for Target,也就是我们俗称的魔法棒。

  • 选择 C/C++ 选项卡,在进行仿真时,我们需要将 MDK 的优化调成 Level 0 等级。

  • 点击 Debug 选项卡,把「Load Application at Startup」和「Run to main」勾上,

Load Application at Startup 是在启动调试时是否加载应用程序,如果此选项去掉则不会自动将程序下载到单片机,直接调试。如果此选项打勾则每次进入调试前先下载应用程序,然后进入调试。

Run to main() 可以使程序执行到 main() 函数。进入调试模式后,程序自动运行到 main 函数处。

  • 打开 Settings ,可以看到关于仿真器的设置,可以在这里配置仿真器。默认情况下,大部分都是自动配置好的,无需额外修改。

  • 我这里使用的是 ST-Link ,所以选的是 ST-Link 。如果你的 ST-Link 正常且插在电脑上了,右边 SW Device 会正常显示,表示仿真器与开发板连接成功了。

    Debug Adapter 是你下载器的型号,这里要选择你使用的下载器型号。

    Serial 是指仿真器的序列号,当你选择完仿真器之后,Serial 才会显示你下载器的序列号。

    在下面的 Version:FW 是下载器的版本号,本文使用的下载器是 ST-Link V2 所以显示的 HW 是 V2 。

    Port 是选择仿真器的调试模式,ARM 芯片有两种调试模式,分别是 SW 和 JTAG 。

    相对于 JTAG 模式,SW 占用更少的信号线,而且功能一样支持代码下载与调试,所以强烈推荐使用 SW 调试模式。

如果 ST-Link 没有插上或设备异常,则会提示 No ST-Link detected。

  • 最后打开 Utilities 选项卡将 Use Debug Driver 打勾,再点击 OK 确定一下,MDK 仿真的调试配置就完成了。

3. 各个调试按钮的作用

接着我们编译一下代码。再点击这个像放大镜一样的按钮就可以开始仿真了(这个按钮同时也可以退出仿真)。

进入仿真之后的界面如下图示。

界面左边显示寄存器的地址和程序运行时间,上方是汇编语言窗口,需要一定的汇编基础才能看得懂。界面左下方是命令窗口,这个窗口会显示一些打印信息,也可以在这个窗口输入一些命令。右下角的窗口是关于函数及变量在内存中的地址信息。

在左上方有一排关于调试的小按钮:

它们的功能从左到右分别是:复位、全速运行、停止、进入函数、执行过此函数、跳出函数、执行到光标处、显示下一个将运行的代码

  • 复位:重新执行程序;
  • 全速运行:开始执行程序;(快捷键为 F5 )
  • 停止:停止执行程序;
  • 进入函数:进入当前行代码中;(快捷键为 F11 )
  • 执行过此函数:执行当行代码;(快捷键为 F10 )
  • 跳出函数:跳出当前程序代码;(快捷键为 Ctrl+F11 )
  • 执行到光标处:自动执行代码至蓝色光标处;(快捷键为 Ctrl+F10 )
  • Show Next Statement:显示下一行即将要执行的程序。

这里我以流水灯作为案例来详细的说明一下各个按钮的使用方法,使用的单片机型号为 STM32F103C8T6 最小系统板。连接 ST-Link 上电,接线图如下:

ST-Link V2STM32
SWCLKSWCLK
SWDIOSWDIO
GNDGND
3.3V3V3

在 STM32F103C8T6 最小系统板上有个自带的小灯,这颗 LED 灯连接在 PC13 管脚上。

首先我们需要写一个小灯的初始化程序,这里可以不用封装,直接写在 main.c 里面。

#define LED_CLK()       __HAL_RCC_GPIOC_CLK_ENABLE()
#define LED_GPIO        GPIOC
#define LED_PIN         GPIO_PIN_13void led_init(void)
{GPIO_InitTypeDef gpio_initstruct;LED_CLK();                                              /* IO口时钟使能 */gpio_initstruct.Pin = LED_PIN;                          /* LED0引脚 */gpio_initstruct.Mode = GPIO_MODE_OUTPUT_PP;             /* 推挽输出 */gpio_initstruct.Pull = GPIO_PULLUP;                     /* 上拉 */gpio_initstruct.Speed = GPIO_SPEED_FREQ_HIGH;           /* 高速 */HAL_GPIO_Init(LED_GPIO, &gpio_initstruct);              /* 初始化LED0引脚 */
}

主函数代码如下:

int main(void)
{HAL_Init();                         /* 初始化HAL库 */sys_stm32_clock_init(RCC_PLL_MUL9); /* 设置时钟, 72Mhz */delay_init(72);                     /* 延时初始化 */led_init();                         /* LED初始化 */while(1){ HAL_GPIO_WritePin(LED_GPIO,LED_PIN,GPIO_PIN_SET);  /*使LED灭掉*/delay_ms(500);HAL_GPIO_WritePin(LED_GPIO,LED_PIN,GPIO_PIN_RESET);/*使LED亮起*/delay_ms(500); }
}

点击放大镜进入仿真界面,然后再点击左上角的开始按钮(或者使用快捷键 F5 )开始执行代码。

可以看到 STM32F103C8T6 最小系统板上的 PC13 小灯在不断闪烁。这时候我们在 19 行打上一个断点,再点击全速运行按钮:

可以看到当前程序运行到了第 19 行然后暂停了,STM32F103C8T6 最小系统板上的 PC13 小灯则会保持长亮。

(需要注意的是,调试停在中断处的时候,内核会停止,但是外设将会继续运行)

接着我们在 21 行再打上一个断点,然后再点击全速运行按钮。

会发现代码执行到了 21 行就停下了,这时 STM32F103C8T6 最小系统板上的 PC13 小灯会灭掉。

我们点击右上角复位的按钮,然后选中 led_init(); 这行代码,点击执行到光标处按钮(或者快捷键 Ctrl+F10 ),将代码执行至这一行。

(需要注意必须要退出调试模式再重新进入调试模式,否则代码会一直在死循环当中执行,导致代码无法执行到你想要执行的那行)

这时候我们点击进入函数按钮(或者快捷键 F11 ),则进入到 LED 的初始化函数当中。

我们可以看到当前程序执行到了 LED 的初始化函数中。这时,点击「执行过此函数按钮」(或者快捷键 F10 ),如下图示。

点击一下上图按钮,程序将执行一行代码。而且,如果当前行是函数,将不进入此函数内部代码,直接执行完函数代码并进入到下一行。

如果你不想查看 led_init() 函数里的代码了,你可以点击「跳出函数按钮」(或者快捷键 Ctrl+F11 ),可以按钮可以直接跳出当前代码程序,准备执行下一行。

(跳出函数的话会默认执行完当前程序代码,然后跳到下一行)

4. 查看程序段/函数执行的时间

在代码中,我们经常会使用延时函数。那么,我们如何确定延时函数真的延时了我们所设置的时间呢(比如 500ms)?

我们可以通过代码调试功能来确定延时时间的准确性。

首先打开魔法棒,在 Debug 中打开 Settings 选项。

再点击 Trace 选项卡,可以找到时钟频率。

默认情况下的时钟频率是 8MHz ,这里我们改成 72MHz 。

我们选中 20 行,然后使用「执行到光标处按钮」(快捷键为 Ctrl+F10 )运行至 20 行。如下:

在左下方的 Sec 表示程序运行了多久,单位是秒( s ),我们使用「执行过此函数」(快捷键为 F10 )执行一下延时函数,看看延时函数是否准确。结果如下:

使用此结果减去初始的时间,可以看到大约延时了 0.5s 左右,也就是 500ms ,可以认为延时函数的时间是非常准确的。

5. 工具栏常用窗口按钮介绍

在调试按钮的左侧有一排窗口按钮,用于打开各种调试窗口(也可以通过菜单栏的 View 打开)。

  • 第一个 Command Window 按钮,可以打开 Command 窗口,这个窗口可以显示一些打印的信息,还能输入命令。

  • 第二个按钮是 Disassembly Window ,这个窗口显示了汇编语言。你可能需要一些汇编语言的基础才能看得懂,这个窗口显示的光标表示下一条即将运行的汇编语言是什么。

  • 第三个按钮是 Symbols Window ,这是符号窗口,用于查看一些符号类型。本文暂时不对这个窗口进行深入学习。

  • 第四个按钮是 Registers Window ,这是一个寄存器窗口。这个窗口可以直观的查看一些内核的寄存器,还有程序运行时间等等。

  • 第五个按钮是 Call Stack Window ,它是用于查看函数调用关系 & 局部变量。本文学习的是仿真工具一些基础的使用,所以不对这个窗口进行深入学习。

  • 第六个按钮是 Watch Windows ,用于查看函数首地址以及变量的值。

Watch 窗口还可以设置变量在被读或写后自动停止运行,对于代码调试非常有帮助。可以定义一个函数,将该函数的名称输入到窗口中,然后向函数传入一个数,在这个窗口你可以看到该函数的寄存器首地址,传完数之后你可以看到该数的类型是什么。

点击该按钮,弹出的界面如下:

举个栗子来演示一下这个过程。例如,在程序中定义一个全局变量 int temp; ,在 while 循环中执行 temp++ 。右击 temp ,选择 Add "temp" to... 添加到 Wacth 1 。

在左下方的 Watch 1 窗口就可以看到 temp 的首地址和定义类型了。如下图:

这里我们使用「执行过此函数按钮」(快捷键为 F10 ),执行一遍程序,会发现 temp 的值发生了改变,执行了一次 temp++ 。如下图:

Wacth 窗口不止这些功能,Wacth 窗口还可以设置变量在被读或写后会自动停止运行。这个功能在调试行数比较多的项目非常有用,因为在很多场景下,有个变量有可能在几十上百处被修改,如果没有这个功能,排查变量被修改将变得非常困难。

那么如何设置这个功能呢?

首先右击 temp ,会出现一个列表,点击 Set Access Breakpoint at "temp" 。如下图:

这时候会弹出一个窗口,在这个窗口你可以设置函数是否要在被读或者被写的自动停止运行。设置完之后点击 Define 就行了。

Wacth 窗口对于我们代码调试还是有非常大的作用,大家可以好好掌握。

  • 第七个按钮是 Memory Windows ,可用于查看内存的地址与值。界面如下图示:

同样的,我们举一个例子。

首先我们在程序中定义一个 uint8_t 类型的数值 temp[10] ,然后给数组的前五个数赋值 {1,2,3,4,15} ,其他元素默认值为 0 。在循环中我们进行 temp[0]++ ,只对第一个元素自增。

编译一下,打开仿真工具,再打开Memory 窗口,在窗口中输入 temp ,这样我们就可以看到 temp 数组在内存中的地址与值。前面我们给数组赋值 {1,2,3,4,15} ,在 Memory 窗口可以看到 temp 数组被十六进制表示出来 “01 02 03 04 0F” 。如下图:

(注意,M3/M4/M7内核是小端模式,内存的值要倒着读)

Memory 窗口可以帮助我们快速的查看变量在内存中的地址和值,便于我们调试程序。

  • 第八个按钮是 Serial Windows ,这是用于查看串口的窗口。

  • 下面这个按钮是 Peripheral 窗口,于查看寄存器的值。Core Peripherals 是内核寄存器,其他的都是外设寄存器。如下图:

    打开之后是这个样子的(我这里打开的是 GPIOC )。如下图:

    在这里你可以看到一些寄存器的值,如果你想看某个值,你可以点击左边的 + 号,在 + 号中有更详细的信息。

    通过这个窗口,可以查看到自己配置寄存器是否正确,确保外设能按照你的预期运行。

6. 小结

总的来说,想要将单片机学的更好,MDK 仿真工具就必不可少。MDK 仿真工具不仅功能强大,而且操作起来的难度也不高,对新手也十分的友好,最重要的是它能够帮助我们更好地理解和调试程序代码,学好使用 MDK 仿真工具,可以让你的技术更上一层楼。


另外,想进大厂的同学,一定要好好学算法,这是面试必备的。这里准备了一份 BAT 大佬总结的 LeetCode 刷题宝典,很多人靠它们进了大厂。

刷题 | LeetCode算法刷题神器,看完 BAT 随你挑!

有收获?希望老铁们来个三连击,给更多的人看到这篇文章

推荐阅读:

  • 程序员必备编程资料大全
  • 程序员必备软件资源

欢迎关注我的博客:良许嵌入式教程网,满满都是干货!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/644762.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

学术研究的策略与方法——从自身经验谈起

人生短短几十载,别让难过占一半 让导师喜欢和抓狂的学生 让导师喜欢的学生 积极主动、乐观向上 常跟导师汇报进展、讨论问题,主动push导师主动承担实验室的项目、课题、任务等 思维敏捷、有自己的想法见解,充满好奇 遇到问题主动寻求解决办法、提出解决…

分享一个剧本(改编自我)

不知道是不是错过了一个喜欢我的女孩,一个很不错的女孩,当初没勇气表白。去年表白过但女孩表示仅想是永远的朋友,今天翻他的朋友圈发现2021年我生日时,她分享了这首歌曲,还评论Best wishes!!!,高中有一次我…

学单片机前先学什么?

学单片机前先学什么? 在开始前我有一些资料,是我根据网友给的问题精心整理了一份「单片机的资料从专业入门到高级教程」, 点个关注在评论区回复“888”之后私信回复“888”,全部无偿共享给大家!!&#xff…

微信小程序从入门到进阶(二)

数据请求 wx.request发起网络请求&#xff0c;请求的方式主要分为两种&#xff1a; get 请求 post 请求 // get请求 // html <view><button type"primary" bindtap"onGetClick">发起 get 请求</button> </view> // js // inde…

一个简单的ETCD GUI工具

使用ETCD没有好用的GUI工具&#xff0c;随手用c#写了一个&#xff0c; 做得好玩的一个ETCD GUI工具&#xff0c;后面加上CLI 工具&#xff0c;类似于 redis Cli工具一样&#xff0c;简化在 Linux下面的操作&#xff0c;不知道有没有必要&#xff0c; git 地址如下&#xff0c;…

【制作100个unity游戏之23】实现类似七日杀、森林一样的生存游戏1(附项目源码)

本篇最终效果演示 文章目录 本篇最终效果演示系列目录前言环境素材绘制地形 实现人物移动指示显示物品名称源码完结 系列目录 【制作100个unity游戏之23】实现类似七日杀、森林一样的生存游戏1&#xff08;附项目源码&#xff09; 【制作100个unity游戏之23】实现类似七日杀、森…

面向对象、封装、继承、多态、JavaBean

二、面向对象 什么是对象 什么是对象&#xff1f;之前我们讲过&#xff0c;对象就是计算机中的虚拟物体。例如 System.out&#xff0c;System.in 等等。然而&#xff0c;要开发自己的应用程序&#xff0c;只有这些现成的对象还远远不够。需要我们自己来创建新的对象。 1. 抽…

鸿蒙开发 状态管理

最近学习鸿蒙开发。 状态管理&#xff1a; State -> Prop 单向传递&#xff1b; stateprop: State -> Prop 单向传递 State -> Link 双向传递&#xff1b;

数据的存储结构

1.类别 顺序存储、链式存储、散列存储、索引存储 2.顺序存储与链式存储的区别 顺序存储链式存储优点 可以实现随机存取每个元素占用最少的空间 充分利用所有存储单元&#xff0c;不会出现碎片现象。缺点 只能使用整块的存储单元&#xff0c;会产出较多的碎片。 需要额外的存…

面试题-【消息队列】

消息队列 问题1 如何进行消息队列的技术选型优点解耦 &#xff08;pub/sub模型&#xff09;异步&#xff08;异步接口性能优化&#xff09;削峰 使用消息队列的缺点几种消息队列的特性 问题2 引入消息队列之后该如何保证其高可用性RabbitMQ的高可用kafka高可用 问题3 在消息队列…

HCIP-BGP实验4

搭建实验拓扑图 要求 1.全网可达 2.isp只能配置IP地址 实验开始 配置IP地址及环回 r1,r2,r9,r10配ipv4地址(以r1为例) [Huawei]sysname r1 [r1]interface g0/0/0 [r1-GigabitEthernet0/0/0]ip address 12.1.1.1 24 [r1-GigabitEthernet0/0/0]q [r1]interface LoopBack 0…

Java实现考研专业课程管理系统 JAVA+Vue+SpringBoot+MySQL

目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块2.1 数据中心模块2.2 考研高校模块2.3 高校教师管理模块2.4 考研专业模块2.5 考研政策模块 三、系统设计3.1 用例设计3.2 数据库设计3.2.1 考研高校表3.2.2 高校教师表3.2.3 考研专业表3.2.4 考研政策表 四、系统展示五、核…

Cesium实现动态水面效果

目录 1.使用Primitive实现动态水面效果 2.版本问题 1.使用Primitive实现动态水面效果 let points [[87.07131373100303, 29.40857655725876],[87.33503858397042, 29.41843499494008],[87.33072496578943, 29.193059292424955],[87.05098771260403, 29.20286249623694],];l…

kali安装LAMP和DVWA

LANMP简介 LANMP是指一组通常用来搭建动态网站或者服务器的开源软件&#xff0c;本身都是各自独立的程序&#xff0c;但是因为常被放在一起使用&#xff0c;拥有了越来越高的兼容度&#xff0c;共同组成了一个强大的Web应用程序平台。 L:指Linux&#xff0c;一类Unix计算机操作…

【代码随想录13】102. 二叉树的层序遍历 226. 翻转二叉树 101. 对称二叉树

目录 102. 二叉树的层序遍历题目描述做题思路参考代码 226. 翻转二叉树题目描述做题思路参考代码 101. 对称二叉树题目描述做题思路参考代码 102. 二叉树的层序遍历 题目描述 给你二叉树的根节点 root &#xff0c;返回其节点值的 层序遍历 。 &#xff08;即逐层地&#xff…

【前端可视化】postcss-px-to-viewport 适配怎么限制最大宽度?使用 postcss-mobile-forever

需求原因 自己用 nuxt3 写官网发现用 postcss-px-to-viewport 这个插件虽然能够实现基于 vw 的响应式&#xff0c;但是无法做到限制宽度&#xff0c;比如设计稿 1920p&#xff0c;我只想让最大缩放比例为 1920p&#xff0c;不能超过&#xff0c;就无法实现了。 方案参考 纯 c…

webassembly003 whisper.cpp的python绑定实现+Cython+Setuptools

python绑定项目 官方未提供python的封装绑定&#xff0c;直接调用执行文件 https://github.com/stlukey/whispercpp.py提供了源码和Cpython结合的绑定 https://github.com/zhujun1980/whispercpp_py提供了ctype方式的绑定&#xff0c;需要先make libwhisper.so Pybind11 bi…

spring Cloud Stream 实战应用深度讲解

springCloudStream 简介 Spring Cloud Stream是一个框架&#xff0c;用于构建与共享消息传递系统连接的高度可扩展的事件驱动微服务。 该框架提供了一个灵活的编程模型&#xff0c;该模型建立在已经建立和熟悉的 Spring 习惯用语和最佳实践之上&#xff0c;包括对持久发布/订…

算法基础学习|双指针算法

双指针算法 代码模板 for (int i 0, j 0; i < n; i ){while (j < i && check(i, j)) j ;// 具体问题的逻辑 } 常见问题分类&#xff1a;(1) 对于一个序列&#xff0c;用两个指针维护一段区间(2) 对于两个序列&#xff0c;维护某种次序&#xff0c;比如归并…

Transformer and Pretrain Language Models3-1

content transformer attention mechanism transformer structure​​​​​​​ pretrained language models language modeling pre-trained langue models(PLMs&#xff09; fine-tuning approaches PLMs after BERT applications of masked LM frontiers of PLMs …