本章介绍如何创建寄存器版本的MDK工程。
10.1 新建本地工程文件夹
图10.1-1 新建文件夹
我们首先建立一个文件夹(取名:Led_RegVersion)用于后续建立MDK工程。正常项目我们这个时候是需要再建立一些文件夹,用来分别存放启动文件,外设以及内核等支持文件的,这章我们就省掉这一步,后续正式建工程的时候再正规讲解,也是为了有一个对比,效果会更好。
10.2 新建工程
打开MDK,新建一个工程,工程名根据个人喜好,最好取一个一眼就能知道含义的名字,我这里取LED_RegVersionTest,直接保存在上一步骤建立的Led_RegVersion文件中。
图10.2-1 新建工程
10.2.1 选择MCU 型号
新建工程后会弹出MCU型号选择对话框,这个根据我们自己所用的型号选择即可,我们用的F103C8T6,所以我们选择STM32F103C8。注意如果这里没有你找到的型号,说明前面安装的时候没有安装对应的器件支持包,安装一下即可。
图10.2-2 选择MCU型号
10.2.2 在线添加库文件
点击 OK后MDK 会弹出 Manage Run-Time Environment 对话框,如图 10.2-3 所示.在这个界面,我们可以添加自己需要的组件,从而方便构建开发环境,这里我们暂时用不到就直接跳过,我们直接点击 Cancel,即可。
图10.2-3 Manage Run-Time Environment 界面
10.2.3 生成工程文件结构介绍
此时我们打开我们创建的文件夹,会看到 MDK 在该文件夹下自动创建了 3 个文件夹:
DebugConfig,Listings 和 Objects),如图 10.2-4所示. 这三个文件夹是随着.uvprojx文件创建时自动生成的。
图10.2-4 MDK新建工程时自动创建的文件夹
这三个文件夹的作用如下图10.2-5所示:
图10.2-5 MDK自动生成的文件夹的作用
10.2.4 添加分组及文件
在Project→Target 上右键,选择 Manage Project Items…或在菜单栏点击品字形红绿白图标进入工程管理界面,如图10.2-6所示:
图10.2-6 进入工程管理界面
在工程管理界面,我们可以执行设置工程名字(Project Targets)、分组名字(Groups)以
及添加每个分组的文件(Files)等操作。我们设置工程名字为:Target 1,并设置2个分组:
Startup(存放启动文件)、User(存放 main.c 等用户代码)如图 10.2-7所示:
图10.2-7 工程管理配置
在实际使用过程中,我们也经常会在新建的组里右键添加文件,如图10.2-8。
图10.2-8 组新建文件
添加启动文件
启动文件就是,系统上电后第一个运行的程序,由汇编编写。这个文件从标准固件库里面复制过来即可,由官方提供。这个文件我们添加到前面建立的Startup组里去。文件在下面目录,如图10.2-6。 STM32F10x_StdPeriph_Lib_V3.5.0\Libraries\CMSIS \ CM3\DeviceSupport\ST\STM32F10x\startup\arm
图10.2-6 启动文件
从上图我们可以看到,启动文件有很多,哪个是我们需要的呢?我们可以根据下表10.2-1查找。我们所用的STM32F103C8T6是64K的ROM因此,对应的启动文件是后缀为MD的,即启动文件为:startup_stm32f10x_md.s。我们把这个文件copy到我们新建的文件夹Led_RegVersion之下。
表10.2-1 不同型号STM32缩写
添加Main.c文件
用户手动新建,用于存放main 函数,暂时为空。建成后文件夹如图10.2-7所示。这个我们添加到User组里。
图10.2-7 添加文件后的文件夹
10.3 硬件连接
如图10.3-1,我们把LED 灯的阳极连接到3.3V 电源,阴极各经过1 个限流电阻接到我们使用的STM32最小系统板的PB0口,我们只要控制这PB0引脚输出高/低电平,即可控制所连接LED 灯的亮灭。我们的目标是把GPIO-PB0口的引脚设置成推挽输出模式并且默认下拉,输出低电平,这样就能让LED灯亮起来了。(关于GPIO的模式等问题后面再讲,这里不用管)
图10.3-1 硬件连接图
10.4 代码编写
10.4.1 启动文件解释
前面名为“startup_stm32f10x_md.s”的文件,它里边使用汇编语言写好了基本程序,当STM32芯片上电启动的时候,首先会执行这里的汇编程序,从而建立起C语言的运行环境,所以我们把这个文件称为启动文件。该文件使用的汇编指令是Cortex-M3内核支持的指令。startup_stm32f10x_md.s文件由官方提供,一般不需要修改。启动文件这部分的主要功能如下:
•初始化堆栈指针SP;
•初始化程序计数器指针PC;
•设置堆、栈的大小;
•初始化中断向量表;
•调用SystemIni()函数配置STM32的系统时钟。
•设置C库的分支入口“__main”(最终用来调用main函数);
这里面对我们来说最重要的是,在启动文件中有一段复位后立即执行的程序,搜索Reset_Handler 即可找到,代码如下:
图10.4-1 启动文件中Reset_Handler代码
下面对这段代码进行解析:
代码含义:程序注释,在汇编里面注释用的是“;”,相当于C 语言的“//”注释符。
|
定义了一个子程序:Reset_Handler。PROC 是子程序定义伪指令。这里就相当于C 语言 里定义了一个函数,函数名为Reset_Handler。
|
EXPORT Reset_Handler [WEAK] EXPORT 表示Reset_Handler 这个子程序可供其他模块调用。相当于C 语言的函数声明。 关键字[WEAK] 表示弱定义,如果编译器发现在别处定义了同名的函数,则在链接时用别处的地 址进行链接,如果其它地方没有定义,编译器也不报错,以此处地址进行链接。
|
IMPORT 说明SystemInit 和__main 这两个标号在其他文件,在链接的时候需要到 其他文件去寻找。相当于C 语言中,从其它文件引入函数声明。以便下面对外部函数进行调
|
__main其实不是我们定义的(不要与C语言中的main函数混淆),这是一个C库函数,当编译器编译时,只要遇到这个标号就会定义这个函数,该函数的主要功能是:负责初始化栈、堆,配置系统环境,并在函数的最后调用用户编写的main函数,从此来到C的世界。SystemInit是用来配置系统时钟的,这个是官方写好的。
程序跳转到R0 中的地址执行程序,即执行SystemInit 函数的内容。 程序跳转到R0 中的地址执行程序,即执行__main 函数,执行完毕之后就去到我们熟知的
|
因为我们目前体验的是寄存器版本的MDK工程,我们是假定没有官方支持的,所以我们的函数里是没有SystemInit这个函数的,为了程序不报错,我们有2种处理方法:
1.修改一下启动代码,把SystemInit相关的注释掉。修改后如下:
EXPORT Reset_Handler [WEAK]
|
2.在我们的C文件函数里编写一个 SystemInit的空函数。
因为启动文件是只读的,改起来麻烦,我们就直接采用写个SystemInit的空函数的方式。
10.4.2 main.c编写
现在我们开始编写我们的程序。除了前面提到的SystemInit的空函数, 我们主要是要配置GPIO-PB0端口,使其输出低电平。这里主要需要配置3个寄存器,分别是:
①开启APB2时钟控制寄存器
②CRL输入输出方式寄存器
③ODR端口输出数据寄存器
这里不太懂没关系,后面我们还会针对GPIO开专题讲解。下面是配置方式:
①开启外设时钟:
由于STM32 的外设很多,为了降低功耗,每个外设都对应着一个时钟,在芯片刚上电的时候这些时钟都是被关闭的,如果想要外设工作,必须把相应的时钟打开。STM32 的所有外设的时钟由一个专门的外设来管理,叫RCC(Reset and ClockControl),RCC 在《STM32 中文参考手册》的第六章。
通过前面章节,我们知道所有的GPIO 都挂载到APB2 总线上,具体的时钟由APB2外设时钟使能寄存器(RCC_ APB2ENR)来控制。
首先我们需要配置RCC_ APB2ENR的地址。关于地址的配置,可以参考本专栏的第5章。
在《STM32 中文参考手册》中RCC寄存器的地址范围,如图10.4-2:
10.4-2 参考手册中RCC寄存器地址范围
由此我们看到,RCC时钟控制寄存器的基地址是0x4002 1000。APB2外设时钟使能寄存器(RCC_ APB2ENR)的描述如下图,我们只用把该寄存器的bit3置1即可开启GPIO-B的时钟。
图10.4-3 APB2外设时钟使能寄存器
根据以上信息,相关代码如下,代码中“|=”的目的是不改变其他bit的设置。
#define RCC_BASE ((unsigned int) 0x40021000) #define RCC_APB2ENR *(unsigned int*)(RCC_BASE+0x18) RCC_APB2ENR |= 0x00000008;
|
②配置输入输出方式:
首先我们把连接到LED灯的GPIO引脚PB0配置成输出模式,即配置GPIO的端口配置低寄存器CRL,见图GPIO端口控制低寄存器CRL。CRL中包含0-7号引脚,每个引脚占用4个寄存器位。MODE位用来配置输出的速度,CNF位用来配置各种输入输出模式。在这里我们把PB0配置为通用推挽输出,输出的速度为10M。
图10.4-4 CRL寄存器配置图
对应代码如下:
#define GPIOB_BASE ((unsigned int) 0X40010C00) #define GPIOB_CRL *(unsigned int*)(GPIOB_BASE+0x00) GPIOB_CRL &= ~((unsigned int)0x0000000F); GPIOB_CRL |= (unsigned int)0x00000001;
|
③配置端口输出寄存器:
我们在这里直接操作ODR 寄存器来控制GPIO的电平,如图10.4-5.ODR寄存器的配置图。
图10.4-6 ODR寄存器配置图
对应代码如下:
定义了一个子程序:Reset_Handler。PROC 是子程序定义伪指令。这里就相当于C 语言 里定义了一个函数,函数名为Reset_Handler。
|
至此代码全部写完,完整的代码如下:
代码含义:程序注释,在汇编里面注释用的是“;”,相当于C 语言的“//”注释符。
|
10.5 魔术棒配置
10.5.1 Output 选项卡
图10.5-1 Output选项卡配置
Output 选项卡主要是选择把输出文件放到哪个文件夹,如果不选择就是系统默认的前面建立工程时自动生成的“Objects”文件夹,也可以自己选择指定文件夹存放。如果想在编译的过程中生成hex 文件,那么那Create HEX File 选项勾上。
10.5.2 Listing选项卡配置
在Listing 选项卡和前面Output类似,如果不选择就是系统默认的前面建立工程时自动生成的“Listings”文件夹,也可以选择自己习惯的文件夹。
10.5.2 下载器配置
在debug选项卡中,我们选择我们使用的下载器STLINK。右边蓝色框点开Debug Settings 选项配置,弹出对话框的“Flash Download”选项卡,把“Reset and Run 也勾选上,这样程序下载完之后就会自动运行,否则需要手动复位”。
图10.5-2 下载配置
10.6 下载程序
前面步骤都成功后,接下来就是编译,连接最小系统板到电脑,然后下载程序到最小系统板上运行。下载程序不需要其他额外的软件,直接点击KEIL中的LOAD 按钮即可,如图10.6-1。消息栏,出现“Application running…”,则表示代码下载成功,且开始运行 就可以观察实验现象了。
图10.6-1 编译下载
如图10.6-2我们终于点亮了我们第一个LED灯。。。
图10.6-2 实验现象
|