STM32---MDK工程创建

本节我们带领大家学习如何新建一个寄存器库版本MDK的详细步骤;

由于51单片机的学习时,所涉及的寄存器很少,所以往往几个头文件、驱动文件就可以完成相关的功能,但是对于STM32来讲,涉及的寄存器、头文件等都很多,这也是为什么32的功能强大的原因,内容复杂才能功能庞大;所以在建立一个完整的工程时,也相对来说比较麻烦;

目录

一、新建寄存器版本MDK工程

1、新建工程文件夹

1.1 新建文件夹

1.2 复制相关文件 

2、新建工程框架 

3、添加文件

3.1设置工程名与分组名 

3.2添加启动文件

 3.3添加SYSTEM文件

4、魔术棒的设置 

5、添加 main.c,并编写代码

二、下载验证

1、实物连接

2、实验结果

一、新建寄存器版本MDK工程

        新建一个寄存器版本MDK工程,分为以下5个步骤,我们会分别详细介绍:

1、新建工程文件夹;2、新建工程框架、工程命名以及选择芯片;3、添加文件;4、魔术棒的设置;5、添加main.c文件;

1、新建工程文件夹

1.1 新建文件夹

        新建工程文件夹的作用是,由于32的一个工程涉及的.c、.h等功能文件很多如果把它们全部放在一个文件夹中,那很容易混乱,寻找哪个文件也很不方便;所以我们对于文件进行分类,把它按照不同为文件夹进行划分;就好比:51是我们小学时候,班级的同学比较少,不需要进行分班就能直接找到每一个同学;而32是等我们上到初中了,一个年级的同学就有很多,这时我们就需要对这些同学进行分班,按照班级寻找同学。建立文件夹的作用是一样的; 

我们对于32工程共分5个大的文件夹(如同5各班级),分别为:

文件夹名称作用
Drivers存放与硬件相关的驱动层文件
Middlewares存放正点原子提供的中间层组件文件和第三方中间层文件
Output存放工程编译输出文件
Projects存放 MDK 工程文件
User存放用户编写的代码,如 main.c

        建立完这5个文件夹后,现在里面都是空的,对于新手来说,这5个文件夹是用来干啥的?不知道!为什么要分为5个文件夹?不知道!当然,为什么这么划分,我确实也不知道,但是,官方能给出这样的划分,就说明有一定的原因,我们先按照这个来进行;

1.2 复制相关文件 

        工程根目录及其相关文件夹新建好以后,我们需要拷贝一些工程相关文件过来(主要是在 Drivers 文件夹里面),以便等下的新建工程需要。

        我们进入Drivers 文件夹,现在里面没有文件夹,我们先建立一个BSP 文件夹,用于存放正点原子提供的板级支持包驱动代码,如:LED、蜂鸣器、按键等。 本章我们暂时用不到该文件夹,不过可以先建好备用。

        然后是CMSIS文件夹,,用于存放 CMSIS 底层代码(ARM 和 ST 提供),如:启动文件(.s 文件)、 stm32f1xx.h 等各种头文件。该文件夹我们可以直接从 STM32CubeF1 固件包;因此我们根据实际情况,对其进行了大幅精简,精简后的 CMSIS 文件夹大小为 1MB 左右;

         最后是SYSTEM 文件夹,用于存放正点原子提供的系统级核心驱动代码,如:sys.c、delay.c 和 usart.c 等,方便大家快速搭建自己的工程。这里我给大家提供这个文件夹,大家对应着CV就行;

链接:https://pan.baidu.com/s/1_XOo_-zhc5Kwsu8Oo7uuIQ 
提取码:1022

        这样文件拷贝就完成了,总共在 Drivers 文件夹建立三个文件夹BSP 文件夹、CMSIS文件夹、SYSTEM 文件夹,其中BSP 文件夹为空文件夹,其余两个是复制粘贴得来的;

文件夹名称
BSP存放开发板板级支持包驱动代码,如各种外设驱动
CMSIS存放 CMSIS 底层代码,如启动文件(.s 文件)、stm32f1xx.h 等
SYSTEM存放正点原子系统级核心驱动代码,如 sys.c、delay.c 和 usart.c 等

        最后,我们还需要再Project文件中,新建一个MDK-ARM 文件夹,用于存放 MDK 的工程文件;

 

2、新建工程框架 

        新建工程框架就是新建一个工程,在这个工程中进行操作,也可以实现一个项目,一个功能等等;

        首先,打开 MDK 软件。然后点击 Project→New uVision Project;

         然后,设置工程的路径与名称;

        工程路径,就选择我们上节新建在Project中的文件MDK-ARM 文件夹;

        工程名字,自行选择,好记就行;

        保存后,下一步就是对应的芯片选型,此处对应自己的芯片选型即可;

在选好芯片后,界面会弹出这样的页面,我们不需要管,关闭即可;

然后就会在主界面显示这个内容,表明新建工程框架完成;

 

 最后,我们看一下,新建完工程后,在第一节中新建的文件夹中有什么变化呢?

         在开 MDK-ARM 文件夹,会看到 MDK 在该文件夹下自动创建了 2 个文件夹 (Listings 和 Objects)以及生成.uvprojx文件,这个文件就是新建的工程文件;而Listings文件夹,用于存放编译过程产生的链接列表等文件;Objects文件夹,用于存放编译过程产生的调试信息、.hex、预览、.lib 文件等;我们后面回把输出在 Listings 和 Objects 文件夹的内容,统一改为输出到 Output 文件夹,不按照他这个进行输出,此处需要注意一下下;

3、添加文件

         添加文件的作用是什么呢?我们在建立完一个工程后,需要用到这个工程,32的功能比较复杂,我们需要添加一些已经封装好的函数文件.c、.h等等;

         同样,由于涉及的文件较多,我们需要在工程内部,对所添加的文件进行分类;

注意:第一节是对文件在外部文件夹中分类,这节是对文件在工程内部进行分类

分为三个步骤:1、设置工程名与分组名(也就是分班);2、添加启动文件;3、添加SYSTEM源码;

3.1设置工程名与分组名 

        在 Project→Target 上右键,选择 Manage Project Items…(方法一)或在菜单栏点击品字形 红绿白图标(方法二)进入工程管理界面。

         在工程管理界面,我们可以执行设置工程名字(Project Targets)、分组名字(Groups)以 及添加每个分组的文件(Files)等操作。

        我们设置工程名字为:Template,并设置四个分组: Startup(存放启动文件)、User(存放 main.c 等用户代码)、Drivers/SYSTEM(存放系统级驱 动代码)、Readme(存放工程说明文件);

        这里我们只是新建了一个简单的工程,并没有添加 BSP、Middlewares 等分组,后面随着工 程复杂程度的增加,我们需要一步步添加对应的分组,使内外分组进行一致;

3.2添加启动文件

        启动文件(.s 文件)包含 STM32 的启动代码,其主要作用包括:1、堆栈(SP)的初始化; 2、初始化程序计数器(PC);3、设置向量表异常事件的入口地址;4、调用 main 函数等,是每 个工程必不可少的一个文件。当然这些话使看不懂的,不过没事,这会看不懂是很正常,后面学习到对应的章节,就能够看的懂了;

启动文件的位置就在,我们第一节赋值的CMSIS简介版文件夹中Drivers→CMSIS→Device→ST→STM32F1xx→ Source→Templates→arm 文件夹下,我们开发板使用的是 STM32F103ZET6,对应的启动文 件为:startup_stm32f103xe.s。

如何添加启动文件呢?

如上图所示,按照上面步骤进行添加,双击Startup文件夹,选择对应的外部文件夹,选择文件类型为ALL,选中文件,添加;

添加完后,为了更好的匹配寄存器版本代码,我们对 startup_stm32f103xe.s 做了 2 处修改:

1,我们用不到编译器的内存管理函数,为节省内存,将 Heap_Size 改成 0,源码如下: ;未用到编译器自带的内存管理(malloc,free 等),设置 Heap_Szie 为 0

Heap_Size EQU 0x00000000 

 2、寄存器代码不需要调用 SystemInit 函数,因此修改 Reset_Handler 函数,去掉 SystemInit 调用,源码如下:

 Reset_Handler PROC

EXPORT Reset_Handler     [WEAK]

IMPORT __main ;

寄存器版本代码,因为没有用到 SystemInit 函数,所以注释掉以下代码为防止报错!

;HAL 库版本代码,建议加上这里(提供 SystemInit 函数),以初始化 stm32 时钟等。 ;IMPORT SystemInit

;LDR R0, =SystemInit

;BLX R0 LDR R0, =__main

BX R0

ENDP

 3.3添加SYSTEM文件

         在我们工程里面,由Drivers/SYSTEM的文件夹,注意这个不是或的意思,这是Drivers文件夹下的文件夹SYSTEM,我们前面对SYSTEM进行了复制(就是百度网盘中的文件),这会需要添加进来。如果之前没拷贝,是找不到这些源码的。

4、魔术棒的设置 

        为避免编写代码和编译报错,我们需要通过魔术棒对 MDK 工程进行相关设置。在 MDK 主界面,(魔术棒图标,即 Options for Target 按钮);

Target 选项卡设置使用编译器的版本,外部晶振
Output 选项卡设置输出文件夹、生成.hex文件、输出浏览信息
Listing 选项卡设置Listing输出文件
C/C++选项卡设置全局宏定义,优化等级
Debug 选项卡设置使用的仿真器,下载接口
Utilities 选项卡添加分散加载实验

1、Target 选项卡

外部晶振频率为 8Mhz。

2、Output 选项卡

勾选:Browse Information,用于输出浏览信息,这样就可以使用 go to definition 查看函数/变量的定义,对我们后续调试代码比较有帮助,如果不需要调试代码,则可以去掉这 个勾选,以提高编译速度。 

Select Folder Objects,为选择输出的文件夹,我们在第一节说过,建立的5个文件夹中Output就是用来存放输出的文件,这里我们需要对应的选择;而且在这里我们还需要选择创建.hex文件,即十六进制文件,因为往单片机里传输的文件就是.hex文件;

3、Listing 选项卡

同样我们将Listing 的输出文件也放在我们设置的Output文件夹中去;

4、C/C++选项卡 

        全局宏定义:STM32F103xE,用于定义所用 STM32 型号,在 stm32f1xx.h 里 面会用到该宏定义。

        设置了优化等级为-O0,可以得到最好的调试效果,当然为了提高优化效果提升性能 并降低代码量,可以设置-O1~-O3,数字越大效果越明显,不过也越容易出问题。注意:当使用 AC6 编译器的时候,这里推荐默认使用-O1 优化。

        C99 模式,即使用 C99 C 语言标准。

        进行头文件包含路径设置。

        头文件的包含路径共有以上四个,此处用到的是相对路径,并非绝对路径;

绝对路径:是指目录下的绝对位置,直接到达目标位置,通常是从盘符(即C\D盘等)开始的路径。 

相对路径:是指由某个文件所在路径引起的跟其他文件(或文件夹)的路径关系;

        直接路径简单来说,就是由明确的路径,根据其能直接找到对应的文件;例如:

F:\51单片机\【正点原子】精英STM32F103开发板 V2-资料盘(A盘)\8,STM32参考资料\8,STM32参考资料\1,STM32CubeF1固件包;

        相对路径就是,在一个文件(初始路径)的基础上,进行上一层目录。或者下一层,进行的一种关系,“../”表示当前目录的上一层目录;“./”表示当前目录;而我们默认的初始路径为MDK 工程所在的路径,即.uvprojx 文件所在路径。所以上面的路径前面都有../。

5、设置Debug 选项卡

        设置对应的仿真器,我使用的是ST-LINK仿真器,大家选择合适对应的即可;

6、Utilities 选项卡

 按照上面,设置为对应的选项;

5、添加 main.c,并编写代码

         在 MDK 主界面,新建一个 main.c 文件,并保存在 User 文件夹下。然后双击 User 分组,弹出添加文件的对话框,将 User 文件夹下的 main.c 文件添加到 User 分组下。并在main.c文件中加入下面的代码:

#include "./SYSTEM/sys/sys.h"
#include "./SYSTEM/usart/usart.h"
#include "./SYSTEM/delay/delay.h"
int main(void)
{
 uint8_t t = 0;
 sys_stm32_clock_init(9); /* 设置时钟, 72Mhz */
 delay_init(72); /* 延时初始化 */
usart_init(72, 115200); /* 串口初始化 */
 while (1)
 {
 printf("t:%d\r\n", t);
 delay_ms(500);
 t++;
 }
}

 编写完 main.c 以后,我们点击:(Rebuild)按钮,编译整个工程,编译结果如图 所示:

如果有一个警告,在最后一行敲一行空行,即不要让代码成为最后一行; 

并且,在Output中生成了.hex文件,可以直接传给单片机;

二、下载验证

        此处,我们采用的是ST-LINK进行下载;

1、实物连接

        点击下载,底部会显示下载成功的标志;

2、实验结果

         在用ST-LINK进行下载后,将串口同单片机以及电脑连接,这是一个有关串口的实验现象,通过串口向单片机发送数据;

        连接方式如下:

        

新建MDK文件

总结:写到此处,第一个MDK工程已经创建完毕,当然这是一个漫长而又繁琐的过程,多少人都很难坚持下来。而且往往看第一遍的时候总是耐不下心,觉得很难、很烦,很啰嗦,这是每个人都会遇到的,但是只要你坚持看完第一遍,再去第二遍看的时候,就会熟悉很多,再到第三遍,这或许就是学习的过程吧。

        本文大多数内容参考的是正点原子:STM32F103 开发指南,特此说明!!!

创作不易,还请大家多多点赞支持!!!

 

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

CleanMyMac X4.16.2最新2024注册许可证

都说苹果的闪存是金子做的,这句话并非空穴来风,普遍都是256G起步,闪存没升级一个等级,价格都要增加上千元。昂贵的价格让多数消费者都只能选择低容量版本的mac。而低容量的mac是很难满足用户的需求的,伴随着时间的推移…

005、简单页面-容器组件

之——布局 目录 之——布局 杂谈 正文 1.布局基础知识 2.Column 3.Row 4.实践 杂谈 布局容器组件。 一个丰富的页面需要很多组件组成,那么,我们如何才能让这些组件有条不紊地在页面上布局呢?这就需要借助容器组件来实现。 容器组件是…

elment Loading 加载组件动态变更 text 值bug记录

先上效果图: 倒计时4分钟组件方法 // 倒计时 4分钟getSencond() {this.countDown 4分00秒this.interval setInterval(() > {this.maxTime--;let minutes Math.floor(this.maxTime / 60);let seconds Math.floor(this.maxTime % 60);minutes minutes < 10 ? 0 minu…

AI 绘画 | Stable Diffusion 电商模特

前言 上一篇文章讲到如何给人物更换背景和服装。今天主要讲电商模特,就是服装电商们的固定服装产品图片如何变成真人模特穿上的固定服装产品效果图。如果你掌握了 《AI 绘画 | Stable Diffusion 人物 换背景|换服装》,这篇文章对你来说,上手会更轻松。 教程 提取服装蒙版…

解决Wireshark分析RTMP抓包时Unknown问题

使用Wireshark抓包时&#xff0c;经常出现很多Unknown包&#xff0c;但实际上的字节流实际是正常的。 其实&#xff0c;RTMPT设置里有一个最大包大小的设置&#xff0c;默认是32768&#xff0c;而且默认RTMPT协议配置了从多个TCP流中重组RTMPT的功能(应当是考虑基于HTTP的传输…

SpringBoot+SSM项目实战 苍穹外卖(2)

继续上一节的内容&#xff0c;本节完成新增员工、员工分页查询、启用禁用员工账号、编辑员工、导入分类模块功能代码。 目录 新增员工(完整流程分为以下五个部分)需求分析和设计代码开发功能测试代码完善 (ThreadLocal 线程局部变量)代码提交 员工分页查询代码完善 扩展Spring …

css中元素水平居中的方式

文章目录 前言水平居中&#xff1a;垂直居中方法一: text-align: centerdisplay: table-cell方法二:父元素静态定位子元素通过相对定位来实现方法三:通过静态和相对定位方法四 css图片居中用text-align:center无效怎么回事&#xff1f;如何让图片在DIV中水平和垂直两个方向都居…

prometheus|云原生|kubernetes内部安装prometheus

架构说明&#xff1a; prometheus是云原生系统内的事实上的监控标准&#xff0c;而kubernetes集群内部自然还是需要就地取材的部署prometheus服务了 那么&#xff0c;prometheus-server部署的方式其实是非常多的&#xff0c;比如&#xff0c;kubesphere集成方式&#xff0c;h…

前端面试JS— JS数据类型及相关内容

目录 JS数据类型 基本数据类型&#xff1a; 引用数据类型&#xff1a; 两种数据存储方式&#xff1a; 两种数据类型的区别&#xff1a; 数据类型的检测方式 null和undefined区别 JS数据类型 基本数据类型&#xff1a; Number&#xff0c;String&#xff0c;Boolean&#xff0c;…

Safe and Practical GPU Computation in TrustZone论文阅读笔记

Safe and Practical GPU Computation in TrustZone 背景知识&#xff1a; youtube GR视频讲解链接&#xff1a;ASPLOS’22 - Session 2A - GPUReplay: A 50-KB GPU Stack for Client ML - YouTube GPU软件栈&#xff1a; 概念&#xff1a;"GPU软件栈"指的是与GPU硬件…

以下哪项不是在内蒙古举办的?()

需要查看更多试题和答案&#xff0c;可以前往题海舟&#xff08;题海舟&#xff09;进行搜题查看。可以搜“题干关键词”。 以下哪项不是在内蒙古举办的&#xff1f;&#xff08;&#xff09; A.中蒙博览会 B.东北亚区域合作高峰论坛 C.中蒙俄合作高层论坛 D.中日经济合作会 …

C语言之位段(详解)

C语言之位段 文章目录 C语言之位段1. 位段的介绍2. 位段的内存分配3. 位段跨平台问题4. 位段的应用5. 位段使用注意 1. 位段的介绍 位段&#xff08;bit-field&#xff09;是C语言中的一种特殊数据类型&#xff0c;它允许将一个字节分成几个部分&#xff0c;并为每个部分指定特…

Sql Server数据库跨机器完整恢复(源文件恢复)

问题描述 在操作系统异常的情况下&#xff0c;SQL Server 和相关的业务系统遭受了不可用的情况。由于操作系统问题&#xff0c;导致旧服务器无法正常运行。为了恢复业务功能并确保数据完整性&#xff0c;采取了以下步骤来在新机器上进行 SQL Server 的重新安装和数据恢复。 面…

C#网络编程(System.Net命名空间和System.Net.Sockets命名空间)

目录 一、System.Net命名空间 1.Dns类 &#xff08;1&#xff09;示例源码 &#xff08;2&#xff09;生成效果 2.IPAddress类 &#xff08;1&#xff09;示例源码 &#xff08;2&#xff09;生成效果 3.IPEndPoint类 &#xff08;1&#xff09; 示例源码 &#xff0…

如何用Java实现扑克牌(附源码)

目录 一.扑克牌的数据结构 二.买牌(扑克牌的初始化) 三.洗牌 四.发牌 五.完整代码 Card.java CardList.java 六.测试 输出结果 一.扑克牌的数据结构 首先&#xff0c;扑克牌是一幅一幅的&#xff0c;除去大小王以外一共有52张&#xff0c;我们可以考虑用数组来存储…

开源 LLM 安全扫描器

Vigil 是一款开源安全扫描程序&#xff0c;可检测即时注入、越狱以及对大型语言模型(LLM) 的其他潜在威胁。 当攻击者使用专门设计的输入成功影响 LLM 时&#xff0c;就会出现即时注入。这导致 LLM 无意中实现了攻击者设定的目标。 ​ 我对 LLM 的可能性感到非常兴奋&#xff…

隐形内嵌!触想智能发布全新B款内嵌式工控一体机及内嵌式工业显示器

近日&#xff0c;触想智能发布全新B款内嵌式工控系列TPC-19.该系列可支持显示器和一体机等多种品类、多级配置的灵活选购。标志性的2.5mm矮阶窄边面板设计&#xff0c;适配隐形内嵌式安装&#xff0c;专为机柜类设备应用打造&#xff0c;以高契合的物理结构&#xff0c;带动稳定…

YUVRGB

一、直观感受 根据上面的图片&#xff0c;不难看出&#xff1a; RGB的每个分量&#xff0c;是对当前颜色的一个亮度值Y分量对呈现出清晰的图像有着很大的贡献Cb、Cr分量的内容不太容易识别清楚YUV将亮度信息&#xff08;Y&#xff09;与色度信息&#xff08;UV&#xff09;分离…

进程的创建:fork()

引入 创建进程的方式我们已经学习了一个&#xff01;在我们运行指令(或者运行我们自己写的可执行程序)的时候不就是创建了一个进程嘛&#xff1f;那个创建进程的方式称为指令级别的创建子进程&#xff01; 那如果我们想要在代码中创建进程该怎么办呢&#xff1f; fork() for…

Python基础学习快速入门

文章目录 Number变量String字符串Def函数Class类List列表Tuple元组Dictionary字典Set集合值与引用类型if条件控制Loop循环 Number变量 python直接赋值&#xff0c;不需要定义变量类型。不需要**,逗号结尾符 使用print**直接进行输出 #赋值 a 1.0 print(a)a 7 print(a)p…