微机原理与接口技术

微机原理与接口技术

基本概念

微型计算机简称微机,是微机原理这门课程的主要研究范围。

首先我们先对微型计算机有一个初步的认识。

电脑——微型计算机系统

电脑是我们熟悉的事物,我们家用的台式电脑和笔记本电脑都是属于个人电脑(Personal Computer),简称为PC。个人电脑PC是一个通用微型计算机系统()。其中,“通用”暗示了“专用”计算机系统的存在,而“微型”暗示了“大型”计算机系统的存在。其中还有值得注意的是计算机系统中的“系统”二字,因为这暗示了在计算机之上还存在一个更加宏观的概念。

计算机根据规格和能力区分的话可以粗略分作超级计算机,大型计算机,小型计算机和微型计算机。其中超算和微机可能是我们日常生活中常常能听到的词语,前者出现在新闻里面,比如天河二号;后者出现在我们们的日常生活中,也就是我们的电脑(主机)。

经典的电脑硬件配置包括鼠标,键盘,显示器和电脑主机,虽然笔记本发展后大多数笔记本都配备有触控板作为鼠标的代替,但是鼠标依旧是更受欢迎的计算机外设(Peripherals)。除了鼠标,键盘和显示器也是最经典的外设,但是对于完全不了解电脑构造却经常使用电脑的人来说,或许会认为显示器是计算机,尤其是一体机的存在使得这个错误更容易犯下。但是,计算机,顾名思义是计算的机器,它的功能并不会包括“显示”。根据输入,计算机会得到输出,但是这个输出不是我们可以直接看到的“显示”,“显示”的功能来自一个密布发光单元的显示器,显示器可以将计算机的输出变成屏幕上的文字或者图片为我们“显示”出来。

外设不是计算机,真正的计算机是我们通常称为“电脑主机”的的部分,在台式机中,它是显示器旁边的长方体的箱子;在笔记本中,它一般在你的键盘的下面。

计算机(个人计算机)和外设,配合上电源,便组成了微型计算机系统的硬件部分。完整的微型计算机系统不仅包括硬件,还包括软件。后者不是我们在微机原理课程中重点的关注的内容,但是理解软件部分是微型计算机系统不可缺少的部分却很重要。

微型计算机系统的软件部分包括系统软件应用软件,系统软件中我们最熟悉的就是操作系统,如Windows和MacOS;而像Office或者浏览器等软件则是应用程序即应用软件。

==总结:家用电脑整体称作微型计算机系统(),这个系统分为硬件和软件两大部分,其中软件部分包括系统软件和应用软件;硬件部分包括电源、外设和最重要的微型计算机(电脑主机)。接下来我们重点介绍微型计算机的内部结构。==

微型计算机

微型计算机()内部结构可概括为四部分:

  • CPU(这个肯定耳熟了,稍后会进一步介绍)
  • 存储器(比较复杂,稍后重点介绍)
  • 总线(bus):总线就是连接计算机内部部件的导线
  • 输入输出接口电路:与外设连接的导线

相信即便是最不了解电脑的小白也听说过CPU的大名。CPU中的字母C表示Center,是中心的意思,表明CPU是计算机的中心。计算机中的一切一切都是围绕着CPU转圈的。这样重要且复杂的部件,我们肯定要单独拿出来讲:下一节微处理器,就是对CPU的详细介绍。

计算机的本质是计算,无论是使用计算机显示文本或是图形,播放音乐或是视频,计算机本身都只是在快速做着最简单的运算。我们从很小的时候便学习开始做运算了,我们知道,做运算首先我们需要纸笔记录下数字,然后可能还需要将结果记录在另一张纸上(比如你的试卷)。计算机中也存在大量的数字,我们称作数据(data),它们存在计算机的存储器中。除数据外,计算中我们需要知道算数的方法,比如老师对我们说,将两个数字相乘,我们就做乘法。计算机也存在类似的东西,称作指令(code)。众所周知,计算机使用二进制,也就是计算机中不存在文字,甚至不存在数字2,我们在计算机中只有0和1。因此计算机中指令和数据都是由0和1进行表示的。在计算机经典结构——冯·诺伊曼结构中,指令和数字不加区分的放在存储器中;而在另一种改进过的计算机结构——哈佛结构中,指令放在指令存储器中而数据放在数据存储器中。这种分开存放的优势在于可以使数据和指令的存取同时进行,我们将在后面进一步看到这样做的重要意义。

目前我们已经知道了,存储器存储着数据和指令。处理数据是计算机无时无刻都在做的事情,而指令则告诉计算机该怎样处理数据。那么接下来的问题是:我们需要一个什么样的存储器呢?

简单来说,我们需要一个好的存储器。一般来说,我们关注存储器的如下3个特点

  1. 存储器是一个存储结构,既要存,还要取
    • 存——要求能存住,机器断电(掉电)后能不能一直保存
    • 取——主要看速度,取出来自然是越快越好
  2. 最后就是作为商品,我们还是要考虑成本的,成本太高买不起就不行了。

综上,我们需要一个价格便宜,能掉电存储且读取速度快的存储器。

可惜的是,现实中没有那么完美的存储器(至少目前),虽然根据不同的原理人们制作出的低成本且掉电存储的快闪存储器Flash(就是很多人U盘用的那个),但是它传输速度慢;另一方面人们制作出了静态随机存储器SRAM,虽然便宜还传输快,但是掉电不保存。虽然除了上述两种还有一些类型的存储器,但是也都有其缺点。为了协调速度、容量和成本三个方面,人们设计出了三级存储结构:高速缓存存储器内存储器(主存)外存储器(辅存)

高速缓存存储器存在于主存与CPU之间的一级存储器,容量小但是速度很快,接近于CPU的速度。

内存储器是CPU读取数据和指令的地方,如果数据在外存储器中,CPU将先把数据从外存储器移至内存储器中然后再处理数据。

外存储器就是我们平时说的电脑的固态硬盘,手里拿的U盘和已经淘汰了的光盘。

三级存储结构帮助我们再速度、容量和成本三个方面达到一个平衡。但是未来如果有更好的存储手段,能同时满足上述三点,或许我们就将创造全新的存储器结构,主存和辅存也将会像光盘一样被淘汰掉。


微型计算机的分类——按微型计算机的结构形式分类。

  • 单片机
  • 单板机
  • 多板机

对于数码爱好者,尤其是那些对于台式机组装机器充满兴趣的人都不会对多板机这个概念感到困惑。在台式机中存在一个叫主板或是叫母板的东西。典型的主板能提供一系列接合点,供我们非常熟悉名字的例如处理器、显卡、硬盘驱动器、存储器、对外设备等设备的接合。

多板机是在一块主板上提供了多个扩展插槽,并使用总线将各个部分进行连接,最终将一切封装在一个机箱里面。在组装的开始,我们有一堆板子,在组装完成后,我们有一台电脑。

单板机的思路与多板机不同,单板机将一台电脑用到的所用功能集成在一块电路板上,在购买单板机时我们直接买到的就是一台电脑,而不需要再进行组装。当然,鉴于单板机性能弱一般用于实验室和简单的控制场所,所以单板机所在的电路板可能会进一步连接在其他的电路板上。但是,无论外部还会连接什么,重点是,在一小块电路板上我们有了一台基础功能完备的电脑。这里值得一提的是,对于电子设计爱好者,很可能听说过Arduino和树莓派的大名,这里要说明,Arduino和树莓派都属于单板机。

单片机也被称作微控制器,这个名字基本对单片机定了性质。单片机通常是用来做控制的,常常用于工业,在实验室也经常见到它的身影。单片机将CPU和存储器……其实就是上面提到的微型计算机中该有的那些东西,集成在了一个芯片上面。通常来讲,你对商店老板说来来些单片机,那你将收到的是一些黑色塑料外壳包着的下方有许多金属引脚的小东西。与Arduino和树莓派,一个芯片既不能直接连接一个LED控制它的亮灭,也不能直接烧录程序。单片机正常执行控制需要建立一个最小工作系统,然后才能进行控制。如果想了解更具体些,这里会有你想知道的。

==总结一下:微型计算机由CPU和存储器组成,内部有总线连接,向外有输入输出电路进行扩展。计算机的本职工作是做计算,计算需要指令和数据,根据指令数据的存储方式的不同可以将微型计算机分为冯·诺伊曼结构和哈佛结构。计算机可以分为单片机、单板机和多板机。将计算机放在一个芯片上是单片机,放在一个电路板上是单板机,多个板子各司其职组装在一起形成电脑叫多板机。==

微处理器

微处理器(microprocessor)也称中央处理单元(central processing unit),是微型计算机的核心部件。

前面我们已将提过,计算机的本职工作是做计算。计算需要两方面内容——数据和指令。前者就相当于给你数字2和3,后者就相当于告诉你对2和3做乘法。对于CPU来说不止但肯定有下述三个功能:

  • 进行基本的算数和逻辑运算(如果想要知道电路是如何做运算的,这里会找到答案的)
  • 暂存少量数据(这是好理解的,毕竟我们计算式也要先把数字写在草稿纸上)
  • 执行指令(值得注意的是指令是需要译码的)

嵌入式系统

上文我们以通用计算机系统为例,沿着“微型计算机系统->微型计算机->微(型)处理器”三个层次描述了微机的基本概念。最后我们注意微机的另一大类,即平行于通用计算机系统的,名为嵌入式系统的专用计算机系统。

嵌入式系统也是微型计算机系统,上面描述的通用计算机系统包含的内容,在嵌入式系统中也同样有所体现。但是嵌入式系统的关键词在于“专用”。专用意味着嵌入式系统为某一具体的应用问题而生,不需要具备通用计算机系统的全部内容。例如,嵌入式系统中可能不会连接鼠标键盘等外设,而且不会具备完整的系统软件。

ARM微处理器

ARM微处理器最重要的特性是其英文名称

ARM 微处理器的工作状态

  • ARM 状态:处理器执行32位的 ARM 指令集,即执行字方式的 ARM 指令
  • Thumb 状态:处理器执行16位的 Thumb 指令集,即执行半字方式的 ARM 指令

ARM 微处理器在两种工作模式下都有切换处理器工作状态的指令,但是在 ARM 处理器开始执行代码时处理器应处于 ARM 状态。

ARM 微处理器的工作模式

  • 用户模式
  • 特权模式
    • 系统模式
    • 异常模式
      • 管理模式
      • 快速中断模式
      • 外部中断模式
      • 数据访问中止模式
      • 未定义指令中止模式

工作模式可以通过软件改变,也可以通过外部中断和处理器异常来改变。并且每一种模式下可以使用的寄存器是不同的。

ARM 处理器支持的数据类型和存储格式

数据类型

在处理器中数据类型是这样分类的,分成下面三种类型:

  • 字节类型
  • 半字类型
  • 字类型

数据操作都是以字为单位的(这里课本写:ARM 指令是一个字长而 Thumb 指令是半字长;但不是说操作都是以字为单位并且都是一样长吗???),而从存储器读写的数据则可以按上述三种不同的数据类型进行读写。

对存储器按字或者半字类型进行读取时,需要进行对齐操作;对数据进行字节传输时不需要对齐。

一条指令(指的是机器码)由操作码(opcode)和操作数(operand)构成。操作数可以是1个,也可以是多个,甚至可以没有。操作码则是每一条指令都必须有。操作码表示该指令要做什么动作,例如跳转,加减等等。操作数则表示操作对象,操作数可能还会分为目的操作数和源操作数。操作数当然是一个数字,该操作数可以由多种来源,例如寄存器,存储器或者立即数。本节介绍的处理器的寻址方式就是讨论指令中操作数的来源问题。

存储格式

指令编码中,用来说明操作数来源和操作数构成存储器地址的方法,叫做寻址方式,英文为Addressing Mode。寻址方式是由处理器的指令编码直接决定的,是处理器体系架构的一部分,所以我们一般叫做xxx处理器寻址方式,例如8086寻址方式,MIPS寻址方式。寻址方式是学习任何一种汇编语言的起点。

ARM 存储器的最大寻址空间为4 GB。

存储器可以看作是一个从零开始线性递增的一个容器,容器的基本单位为字节。

数据以字为单位存储在存储器中,这意味着一个(字)数据将存放在存储器的四个基本单位中。

数据在存储器中的存储方式有两种,区分两种存储方式的关键在于一个字的四个字节在存储器中哪一个存在高位哪一个存在低位:

  • 大端格式:数据的高字节位存储在存储器的低地址中
  • 小端格式:数据的低字节位存储在存储器的低地址中

大小端的存储方式可以通过外部引脚和内部寄存器的配置来进行选择,选择要在使用前进行配置,开始使用后只能选择一种存储器数据存储方式。

ARM 处理器的寄存器

ARM 寄存器:

  • 定义:寄存器是 CPU 内部用来暂时存放参与运算的数据和运算结果的小型存储区域
  • 特征:传输数据的速度非常快
  • 分类:
    • 通用寄存器:保存数据和地址
    • 状态寄存器:标识 CPU 的工作状态及程序的运行状态
  • 经典 ARM 寄存器在物理上共有37个32位寄存器组成
    • 31个通用寄存器
    • 6个状态寄存器

ARM 处理器的I/O组织

I/O 是 CPU 与外部设备之间通信的桥梁

对 I/O 端口物理地址进行编址的方式有两种:

  • 独立编址方式:I/O映射方式,x86采用

    I/O 端口地址与内存单元地址分开独立编址,I/O 端口地址不占用内存空间的地址范围,需要专门的I/O 指令和控制逻辑

  • 统一编址方式:内存映射方式,ARM采用

    I/O 端口地址与内存单元地址混在一起:将内存的一部分划分出来作为I/O 地址空间。

Cortex-M4 微处理器

Cortex-M4 微处理器的工作状态

Cortex-M4 微处理器的工作模式

Cortex-M4 微处理器的寄存器组织

  • 存在于:寄存器存在于处理器的内核之中
  • 作用:执行数据处理和控制
  • 分类:16个32位寄存器
    • 通用寄存器组
    • 特殊功能寄存器
  • 与经典的 ARM 微处理器对比:Cortex-M4 微处理器的寄存器较少
  • 通用寄存器详解:
    1. \(R0-R7\)
      • 统称:低组寄存器
      • 访问指令:32位指令和大多数16位指令
      • 初始化:复位后初始化为未定义
    2. \(R8-R12\)
      • 统称:高组寄存器
      • 访问指令:32位指令和少量16位指令
      • 初始化:复位后初始化为未定义
    3. \(R13\)
      • 统称:堆栈指针寄存器
      • 系统可以同时支持两个堆栈,

Cortex-M4 微处理器的存储组织

摘抄:

RISC使用的是load-store结构。load-store结构的本质,在于RISC技术的CPU只处理(指逻辑,算术运算处理)寄存器中的数据。相反,X86却能够直接处理存储器中的数据。

index (nieyong.github.io)

STM32启动程序分析

1.程序的内存分配

一个由C/C 编译的程序占用的内存分为以下几个部分:

栈区(stack):由编译器自动分配释放,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。

堆区(heap):一般由程序员分配释放,若程序员不释放,程序结束时可能由OS回收。注意它与数据结构中的堆是两回事,分配方式类似于链表。

全局区(静态区)(static):全局变量和静态变量的存储是放在一块的,初始化的全局变量和静态变量在一块区域,未初始化的全局变量和未初始化的静态变量在相邻的另一块区域。程序结束后由系统释放。

文字常量区:常量字符串就是放在这里的,程序结束后由系统释放。

程序代码区:存放函数体的二进制代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
int a = 0; //全局初始化区
char *p1; //全局未初始化区
void main()
{
int b; //栈
char s[] = "abc"; //栈
char *p2; //栈
char *p3 = "123456"; //123456\0在常量区,p3在栈上。
static int c = 0;//全局(静态)初始化区
p1 = (char *)malloc(10);
p2 = (char *)malloc(20); //分配得来得10和20字节的区域就在堆区。
strcpy(p1, "123456"); //123456\0放在常量区,编译器可能会将它与p3所指向的"123456"优化成一个地方。
}
//这里要强调一点:传递指针只占4字节,如果传递的是结构体,就会占用结构大小空间。
//提示:在函数嵌套,递归时,系统仍会占用栈空间。
//虽然堆上的数据只要程序员不释放空间就可以一直访问,但是,如果忘记了释放堆内存,那么将会造成内存泄漏,甚至致命的潜在错误。

Q&A

Q:为什么驱动文件是汇编语言写而不是C语言? A:汇编语言处理一些更加底层的工作,如设置堆栈空间等,这一点C这个"高级语言"无法做到。

Q:启动程序阶段,CPU对启动文件是从上向下足以读取吗? A:编译之前的是源程序,编译之后的指令的机器码以地址进行排列。实际上按地址逐一进行读取,要看源码的组织结构。

Q:用户通过Keil软件配置堆栈空间和自己定义堆栈有什么区别? A:

时钟系统就是CPU的脉搏,像人的心跳一样,重要性不言而喻。由于STM32本身十分复杂,外设非常多,但并不是所有的外设都需要系统时钟那么高的频率,比如看门狗以及RTC只需要几十k的时钟即可。并且,同一个电路,时钟越快功耗越快,同时抗电磁干扰能力也就越弱,所以较为复杂的MCU都是采用多时钟源的方法来解决这些问题。

startup_stm32f10x_cl.s(启动文件) → SystemInit() → SetSysClock () → SetSysClockTo72()

查看MDK的文档,会发现有这么一句说明:It is automatically created by the linker when it sees a definition of main()。简单点来说,当编译器发现定义了main函数,那么就会自动创建__main。

程序经过汇编启动代码,执行到__main(),之后会执行两个大的函数:

__scatterload():负责把RW/RO输出段从装载域地址复制到运行域地址,并完成了ZI运行域的初始化工作。

__rt_entry():负责初始化堆栈,完成库函数的初始化,最后自动跳转向main()函数

注意:有些应用中会要求在进入main函数之前先初始化一些外设或者变量区,如:初始化时钟、初始化SDRAM。在初始化他们的时候一定不要使用全局变量,部分库函数,HAL库。因为在__main之前,全局变量还没有初始化,使用会异常,甚至发生内存错误。因此建议使用寄存器来初始化。

https://blog.csdn.net/shenghuaDay/article/details/71643767

r0-r3 用作传入函数参数,传出函数返回值。在子程序调用之间,可以将 r0-r3 用于任何用途。被调用函数在返回之前不必恢复 r0-r3。如果调用函数需要再次使用 r0-r3 的内容,则它必须保留这些内容。

r4-r11 被用来存放函数的局部变量。如果被调用函数使用了这些寄存器,它在返回之前必须恢复这些寄存器的值。

r12 是内部调用暂时寄存器 ip。它在过程链接胶合代码(例如,交互操作胶合代码)中用于此角色.在过程调用之间,可以将它用于任何用途。被调用函数在返回之前不必恢复 r12。

r13 是栈指针 sp。它不能用于任何其它用途。sp 中存放的值在退出被调用函数时必须与进入时的值相同。

r14 是链接寄存器 lr。如果您保存了返回地址,则可以在调用之间将 r14 用于其它用途,程序返回时要恢复

r15 是程序计数器 PC。它不能用于任何其它用途。

注意:在中断程序中,所有的寄存器都必须保护,编译器会自动保护R4~R11

所谓启动,一般来说就是指我们下好程序后,重启芯片时,SYSCLK的第4个上升沿,BOOT引脚的值将被锁存。用户可以通过设置BOOT1和BOOT0引脚的状态,来选择在复位后的启动模式。

STM32上电或者复位后,代码区始终从0x00000000开始,三种启动模式其实就是将各自存储空间的地址映射到0x00000000中。

(1)从Flash启动,将主Flash地址0x08000000映射到0x00000000,这样代码启动之后就相当于从0x08000000开始。

(2)从RAM启动,将RAM地址0x20000000映射到0x00000000,这样代码启动之后就相当于从0x20000000开始。

(3)从系统存储器启动。首先控制BOOT0 BOOT1管脚,复位后,STM32与上述两种方式类似,从系统存储器地址0x1FFF F000开始执行代码。系统存储器存储的其实就是STM32自带的bootloader代码,在bootloader中提供了UART1的接口,通过此接口可以将用户所需的程序代码下载到主Flash中,下载完毕后,此时程序代码已经存储在主Flash当中,这时切换启动模式(从主Flash启动),复位后所执行的就是刚刚下载到Flash中的代码了。

在STM32中,一般都会有一个片上的Flash和SRAM。Flash用于烧录我们编译后生成的目标代码,SRAM则用于栈空间和保存全局变量, 它们分别对应图1中地址空间的Code段和SRAM段。此外STM32中的Flash一般都映射在0x0800 0000的地址上的, 因此为了保证向量表写在0x0800 0000的位置上,我们必须保证生成的目标代码中一开始就是向量表的内容

image-20220502134455259
image-20220502134540053
1
2
3
4
5
6
--cpu Cortex-M4.fp.sp *.o 
--strict --scatter ".\Objects\project.sct"
--summary_stderr --info summarysizes --map --xref --callgraph --symbols
--info sizes --info totals --info unused --info veneers
--list ".\Listings\project.map"
-o .\Objects\project.axf

ARM寻址方式:

寄存器间接寻址:

把寄存器当作存储器的指针。

image-20220507083936948

体系结构——架构

架构由指令集和操作空间(寄存器和存储器)组成

计算机中使用的单词叫指令,计算机使用的词汇表叫指令集

指令包含操作码和操作数,操作数来自于存储器,寄存器和指令本身

ARM架构将每条指令表示为一个32位的字,微处理器是一个可以读入并执行机器语言的指令的数字电路系统

ARM设计的四个准则:

  • 规范性支持简单设计
  • 加快常见功能
  • 越小设计越快
  • 好的设计需要折中方法

微机原理复习

微型计算机系统

微处理器(MP)也称为中央处理单元(CPU),由运算器和控制器两部分组成。

CPU 功能及其组成

功能 对应 组成
算数运算、逻辑运算 <---> 算数逻辑单元(ALU)
暂存少量数据 <---> 寄存器组
1. 指令译码、执行指令
2. 提供定时和控制信号
3. 响应中断
<---> 控制单元(CU)
与存储器和外设通信 <---> 内部总线

细节介绍

  • 程序计数器(PC):存储下一条要被执行的指令的地址

  • 累加器(AC):保存参与计算的数据和运算的中间结果

  • 堆栈指针(SP):存放栈顶地址

  • 标志寄存器:存放指令执行结果和处理器状态

  • 指令译码器(ID):对指令译码

  • 指令寄存器(IR)

  • 地址寄存器(MAR)

CPU 执行指令的过程

前提:程序已经存放在内存中,上电后PC中存放第一条指令地址

  1. 读PC: 控制器将PC中的地址送至MAR,并且发出读命令
  2. 取指令: 存储器依据PC中的地址取指令,经数据总线送入IR
  3. 译码指令: ID对IR中的指令译码
  4. 执行指令: 译码后的 “指令“ 使得 ”控制逻辑阵列“ 向 “存储器或运算器” 发出操作命令
  5. 改PC: 修改PC,准备读取下一条指令

计算机运行过程是不断取指令然后执行指令的过程,早期计算机采用 ”取指令“ 后才 ”执行指令“ 的串行执行方式

但是现代微处理器普遍采用指令流水线技术,即指令执行中步骤并行进行:

  • 两级流水线:8086:取指令、执行

  • 三级流水线:ARM7:取指令、译码、执行

  • 六级流水线:???:取指令、译码、计算操作数地址、取操作数、执行指令、写操作数

微型计算机

微型计算机组成:CPU、存储器、输入输出接口和总线

硬件结构分类:冯诺依曼结构 & 哈佛结构

冯诺依曼结构 哈佛结构
1. 运算器
2. 存储器
3. 控制器
4. 输入输出设备
1. 运算器
2. 控制器
3. 输入输出接口
4. 指令存储器
5. 数据存储器
数据和指令存在一个存储器中不区分 数据和指令分别存在两个存储器中
一套总线 两套总线:分数据和地址

按CPU的指令集分类:精简指令集计算机(RISC)和复杂指令集计算机(CISC)

RISC:

  • 优点
    • 简化指令功能
    • 大量使用寄存器
  • 缺点
    • 增加开发难度
    • 性能依赖编译器

CISC:

  • 优点:
    • 程序运行速度块
    • 降低软件成本
  • 缺点:
    • 增加硬件设计难度
    • 常用指令集少

总线

总线的组成:总线控制器、总线发送器、总线接收器和一组导线

将微型计算机中的总线按位置和使用场景分为三种:

  • 片内总线
  • 片外总线(局部总线)
  • 内总线(系统总线)
    • 数据总线(DB):
      • 双向总线
      • 数据总线位数与微机的位数对应,是微机的一个重要指标
    • 地址总线(AB):
      • CPU送出的单向总线
      • 决定了CPU可以直接寻址的内存空间:\(N\)位地址线管理\(2^N\)字节地址空间,如32位AB可以访问\(4GB\)物理地址空间
    • 控制总线(CB)
      • 双向传输控制信号
  • 外总线(通信总线)

微型计算机的存储器分为内存储器和外存储器,目前采用了存储器三级存储结构

CPU与外设的通信依托于IO接口

微型计算机分类:

  • 单片机:CPU+存储器+IO口+时钟
  • 单板机:一片PCB,包括了显示器和键盘等外设,实验室用
  • 多板机:将多个模块组装在一个主板上,就是我们的电脑主机

微型计算机系统

包括硬件和软件两部分

衡量指标:

  1. 字长:字长指计算机能直接处理的二进制数据的位数,32位计算机就是指字长为32的计算机。字长取决于数据总线的位数
  2. 主存:主存储器能存储的最大信息为主存容量
    • 可以用 字节数 表示主存大小
    • 可以用 单元数×字长 表示主存大小
  3. 主频:计算机中主时钟信号的频率
  4. 运算速度、性价比

嵌入式系统

嵌入式系统是指嵌入到应用对象的计算机系统

ARM体系结构分类:

ARM有7个版本,各个版本之间主要的区分在于体系结构(架构)的变化

ARMv4版本:增加了Thumb指令集,可以在16位/32位指令集之间切换

ARMv6版本:增加了STMD指令集和Thumb2指令集

ARMv7版本:

  • ARMv7-A:高运算性能
  • ARMv7-R:实时处理
  • ARMv7-M:廉价,不支持STMD

增强型体系结构:如T增强版本指增加了Thumb指令集

ARM微处理器分类

ARM处理器包括经典的ARM处理器和Cortex处理器:

  • ARM11之前叫ARM处理器,如ARM7、ARM9、ARM10E
  • ARM11之后改用Cortex命名并分为A、R、M三类

计算机中的数字

数制转化

十进制转二进制:整数部分做除法,小数部分做乘法

二进制转十进制:按权展开求和

二进制、八进制、十六进制转化直接转,八进制三个三个划分、十六进制四个四个划分

计算机中数的表示:

计算机中只存在0/1,没有负号或者小数点。

  • 为了解决使用计算机中的0/1解决符号的问题,我们引入了:

    • 机器数:-10b -> 110b

    • 真值:101b -> -2d

    • 原码:就是最基本的机器数:+用0表示,-用1表示

      • 反码:反码使用原码得到:
        • 正数反码与原码相同
        • 负数反码符号位不变,数值位是原码依次取反
    • 补码:

      • 正数补码与原码相同
      • 负数X:\([X]_补 = [X]_反 + 1\)
  • 为了解决使用计算机中小数的表示问题,我们引入了:

    • 定点数:小数点在数中的位置是不变的
    • 浮点数:

计算机中的编码:

  • BCD码
  • ASCII码
  • 汉字编码:GB2312-80(国标码)
    • 汉字输入码(外码):某一汉字的一组键盘符号
    • 内码:计算机存储用的编码,内码是将国标码的最高两位置1得到的
    • 汉字输出码

ARM处理器

特点及功能

ARM是32位的RISC微处理器架构,ARM微处理器是ARM架构下的微处理器

ARM微处理器特点:

  1. ARM7采用冯诺依曼结构、ARM9-ARM11包括Cortex系列采用的是哈佛结构
  2. 属于RISC处理器结构
  3. 有两种工作状态,并且定义了多种工作模式
  4. 大量使用寄存器

ARM指令集特点:

  1. 采用固定长度指令集
  2. 内存的访问使用LOAD/STORE模式(LOAD/STORE伪指令可以操作内存,其他都是操作寄存器)

Cortex-M3/4的特点:

  1. 哈佛结构
  2. 三级流水线
  3. 32位寻址,4GB存储器空间
  4. 采用Thumb2指令集

Cortex-M4中的总线:

  • I-CODE:获取指令
  • D-CODE:读取立即数
  • 系统总线:访问外设
  • 私有外设总线(PPB):也是访问外设

ARM微处理器编程模型

工作状态:

  • ARM状态:执行32位的ARM指令集,字方式的ARM指令
  • Thumb状态:执行16位的Thumb指令集,执行半字方式的ARM指令

ARM处理器在运行中可以在两种方式间切换,但是开始时只能从ARM状态开始;

ARM指令集效率高,支持所有功能;Thumb指令集具有良好的代码密度

工作模式:

  • 用户模式
  • 特权模式
    • 系统模式
    • 异常模式
      • 管理模式
      • 快速中断模式
      • 外部中断模式
      • 数据访问中止模式
      • 未定义指令中止模式

模式可以通过编程来改变,每种模式能够使用的寄存器不同

ARM微处理器支持的数据类型和存储格式

ARM汇编的数据类型:

3种数据类型:字、半字、字节

4字节对齐(字对齐):对存储器按字读写时,要求数据地址的低两位是00

2字节对齐(半字对齐):对存储器按字读写时,要求数据地址的最低为是0

使用字节进行读写时,对数据地址没有要求

数据:

数据可以是有符号数:\(0到2^n-1\),也可以是无符号数:\(-2^{n-1}到2^{n-1}-1\)

ARM指令是一个字长,Thumb指令是半个字长;

数据操作指令是以字为单位的,对存储器进行读写的指令,有字节、半字和字为单位

数据存储方式:
  • 大端格式:字数据的高字节存放在存储器的低地址
  • 小端格式:字数据的低字节存放在存储器的高地址
ARM的寄存器:

CPU内,暂时存放,参与运算的数据,和运算结果的,小型存储区域

  • 通用寄存器:保存数据和地址
  • 状态寄存器:标识CPU工作状态和程序运行状态

经典ARM处理器有37个寄存器,31个通用寄存器,6个状态寄存器

  • 31个通用寄存器:
    • 未分组寄存器 R0 ~ R7,共8个
    • 分组寄存器 R8 ~ R12,R13 ~ R14
      • R8 ~ R12:其中FIQ模式下有单独的一组 R8 ~ R12,共5个;另外6中模式共用一组R8 ~ R12,共5个;总共10个
      • R13 ~ R14:其中USR和SYS模式(表格的第一列)共用一组R13 ~ R14共2个,另外5中模式下各有独自的一组R13 ~ R14共10个;总共12个
    • 程序计数器(PC) 即R15,共1个
  • 6个状态寄存器
    • 状态寄存器 CPSR,和5个备份状态寄存器SPSR,共6个
ARM处理器的I/O组织:

对I/O端口进行编址的方式有两种:独立和统一,这里的独立和统一针对的都是内存单元

  • 独立编址方式(IO映射方式)
    • x86体系结构
    • IO端口的地址和内存的地址是分开,IO有自己的端口,对应相应的寄存器和指令
  • 统一编址方式
    • ARM体系结构
    • IO端口和内存是同等地位的

Cortex-M4微处理器

Cortex-M4微处理器工作状态:
  • 调试状态:进入调试状态意味着处理器被暂停,原因可能是调试时间或者触发断点
    • 用于调试操作
    • 进入方法:调试器发起暂停请求;处理器产生调试事件
    • 可以修改寄存器
  • Thumb状态:处理器正常执行代码时就是处于Thumb状态
    • 在Thumb状态下有两个工作模式
      • 线程模式:复位时执行代码就是处于线程模式;”异常“ 返回时也进入线程模式;特权级或者非特权级
      • 处理模式:处理模式就是执行”异常“,例如中断;特权级
Cortex-M4微处理器寄存器组织:
通用寄存器 功能 特殊功能寄存器 功能
R0-R7 低寄存器
32位指令和大多数16位指令可以访问
xPSR APSR:应用程序状态寄存器,保存当前指令运算结果的状态
IPSR:中断状态寄存器,保存当前中断的向量号
EPSR:执行状态寄存器,进入异常时保护现场
R8-R12 高寄存器
32位指令和少量16位指令可以访问
中断屏蔽寄存器 PROMASK:片上外设中断总开关,仅一位
FAULTMASK:异常屏蔽寄存器,异常的总开关
BASEPRI:定义屏蔽优先级的阈值
R13(堆栈指针寄存器) 一个堆栈指针对应两个物理寄存器
主堆栈指针(MSP):内核及系统异常中断
进程堆栈指针(PSP):用户任务
控制寄存器(CONTROL) 定义线程模式的访问级别
选择堆栈指针
R14(连接寄存器 LR) 子程序或函数调用时保存返回地址
异常返回时保存返回后的状态
异常返回的地址由硬件电路保存在PC
R15(程序计数器 PC) 指向 ”正在取指“ 的操作
读操作:返回当前指令地址加4
写操作:引起程序跳转操作
Cortex-M4微处理器的存储组织

存储系统特征:

  • 32位寻址访问4GB的存储器空间
  • 预定义的存储器映射
  • 可以自己配置大端或者小端模式
  • 可选位段访问
  • 具有存储器保护单元(MPU)
  • 支持非对其传输

存储器映射:

存储区划分:

  • 程序代码访问(code区)
    • 存放指令或者数据
    • ICode总线读写指令;DCode总线读写数据
    • 用于存代码,用于异常向量表
  • 数据访问(SRAM区)
    • 片上SRAM
      • 存放指令或者数据
      • 通过系统总线读写数据
      • 用于数据存储
    • 片外SRAM
      • 通过系统总线读写数据
      • 用于扩展外部存储器
    • 该区域可以位寻址
  • 外设(外设区)
    • 片上外设
      • 通过系统总线访问
      • 用于作为片上外设的输入输出接口
    • 片外外设
      • 通过系统总线访问
      • 用于扩展片外外设的输入输出接口
  • 处理器的内部控制和调试组件(私有外设总线)
    • 分配给
      • 中断控制器(NVIC)
      • 调试部件
  • 系统区域是给芯片厂家使用的

堆栈内存操作:

LIFO(Last In First Out)后入先出

处理器中采用堆栈机制来临时存放数据和变量

Cortex-M4将系统主存储器用于堆栈空间,用PUSH指令存数据,POP指令取数据,执行指令时堆栈指针会自动调整

Cortex-M4使用的是 ”满递减“ 栈,PUSH指令执行时,指针地址数值减小;有于寄存器是32位的,每一次执行PUSH或POP指令时至少都会传输一个字。而且地址总是对齐到4字节的边界上,SP的最低两位也总为0

Cortex-M4 微处理器的异常和中断

异常和中断的异同:

  • 异:
    • 中断信号来自内核外
    • 异常是内核产生的
  • 同:都需要保存当前状态,跳到处理程序,处理完成后再回到原来状态

嵌套向量中断控制器(NVIC)及向量表:

Cortex-M4提供了一个用于处理异常的嵌套向量中断控制器,其中包括了中断

NVIC

  • 处理异常
  • 中断配置、优先级和中断屏蔽

在异常发生时,内核要跳转到异常处理程序;实现跳转需要知道异常处理程序的地址。为了解决这个问题,我们在内存中为每个异常类型设置一个固定的地址,将这个地址存在系统的RAM或者ROM中,这样统一存放的地址被称作异常向量表。

我们在stm32的启动代码中可以看到这个异常向量表:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
__Vectors       DCD     __initial_sp               ; Top of Stack
DCD Reset_Handler ; Reset Handler
DCD NMI_Handler ; NMI Handler
DCD HardFault_Handler ; Hard Fault Handler
DCD MemManage_Handler ; MPU Fault Handler
DCD BusFault_Handler ; Bus Fault Handler
DCD UsageFault_Handler ; Usage Fault Handler
DCD 0 ; Reserved
DCD 0 ; Reserved
DCD 0 ; Reserved
DCD 0 ; Reserved
DCD SVC_Handler ; SVCall Handler
DCD DebugMon_Handler ; Debug Monitor Handler
DCD 0 ; Reserved
DCD PendSV_Handler ; PendSV Handler
DCD SysTick_Handler ; SysTick Handler

上面的汇编代码与下图中的向量是一一对应的:

image-20220801094303542

复位和复位流程

对于Cortex-M4来说,复位有三种方式:

  • 上电复位:复位所有部分——处理器、外设、调试支持部件
  • 系统复位:复位——处理器、外设
  • 处理器复位:复位——处理器

复位过程:

复位后,程序开始执行之前,处理器会从存储器中读出头两个字,第一个字表示主栈指针的初始值(下方代码的第七行)第二个字表示复位处理起始地址的复位向量(下方代码的第八行),如下方代码的第15行所示,程序进入main函数就是先通过复位向量Reset_Handler跳转到15行来实现的。处理器读出前两个字后就会将这些数值赋给MSP和PC,来获得第一条指令。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
; Vector Table Mapped to Address 0 at Reset
AREA RESET, DATA, READONLY
EXPORT __Vectors
EXPORT __Vectors_End
EXPORT __Vectors_Size

__Vectors DCD __initial_sp ; Top of Stack
DCD Reset_Handler ; Reset Handler
DCD NMI_Handler ; NMI Handler
DCD HardFault_Handler ; Hard Fault Handler

........

; Reset handler
Reset_Handler PROC
EXPORT Reset_Handler [WEAK]
IMPORT SystemInit
IMPORT __main

LDR R0, =SystemInit
BLX R0
LDR R0, =__main
BX R0
ENDP

结合启动代码和 ”图2-3-8 Cortex-M4微处理器异常类型“ 可以理解下图:

image-20220801095422185

ARM指令系统

ARM指令系统简介

体系架构与指令系统

ARMv4 ARMv4T ARMv5TE ARMv6 ARMv7 ARMv8
ARM8 ARM7TDMI ARM7EJ ARM1156T2 Cortex-M4 Cortex-A8
ARM指令集 引入Thumb指令集 增加了增强型DSP指令 开始引入Thumb2 增强SIMD 引入A64指令集

统一汇编语言:

不同指令集语法不同,因此ARM开发工具支持了统一汇编语言(UAL)

UAL统一了ARM指令集和Thumb指令集的语法,16位指令和32位指令可以无缝出现在代码中

Thumb2指令不需要分析这条指令是32位还是16位,编译器会按最简原则完成汇编;但是在UAL中可以用后缀 “.N

” 和 “.W” 来指定指令长度

1
2
3
4
ADD R1,R2			;自动汇编成16位代码
ADD R1,R2,0x8000 ;有大于8位数范围立即数的指令汇编成32位代码
ADD R1,R1,R2 ;自动汇编成16位代码
ADD.W R1,R1,R2 ;原来是16位代码,指定汇编为32位代码

指令格式

指令一般由操作码和操作数两部分组成

指令基本格式:

1
<opcode>{<cond>}{S} <Rd>,<Rn>{,<shifter_operand>}

指令的二进制编码基本格式:

image-20220801105302596

其中:

  • opcode是指操作码
  • cond位于二进制编码中的最高四位,只有APSR中的条件码对应标志位满足指定条件时,带条件码的指令才被执行;cond指令内容见课本P53(PDF-P69)
  • APSR标志位的值可以被带有{S}的指令改变
  • Rn为第一源操作数,Rd为目的操作数
  • shifter_operand为第二源操作数

ARM指令中没有条件转移指令,该效果的实现如下:

1
2
3
4
ADDEQ	R1,R2,R3	;ADD加法指令+cond:“EQ”
;若之前的操作是相等状态,则执行该ADD指令
BGE L1 ;B跳转指令+GE后缀
;若之前的比较结果是有符号数大于或等于,即(N==V),则跳转至L1

shifter_operand 第二源操作数

  • 立即数(#immed)

    • 立即数是一个32位无符号数,但不是所有32位无符号数都是立即数;因为我们在指令中只有12位来表示一个32位的立即数
    • 立即数组成:8位立即数(immed_8)和4位循环移位(rotate_imm)
      • 32位立即数 = immed_8 循环右移(2 × rotate_imm)位
  • 寄存器方式(Rm)

    • 第二源操作数 shifter_operand 直接用ARM寄存器

    • 例如

      1
      ADD	R2,R3,R4
  • 寄存器移位方式

    • 第一源操作数是寄存器Rm后,第二个操作数可以指定第一源操作数的移位方式和移位的位数

    • 移位的位数可以是立即数#shift_imm或者寄存器Rs的数值,移位方式有九种

    • 例如:

      1
      MOV	R0,R2,LSL#3	;R2逻辑左移3位(相当于乘8)后,赋值给R0寄存器

ARM指令的寻址方式

ARM指令有7种寻址方式

  • 数据处理类指令:立即寻址、寄存器直接寻址、基址变址寻址
  • 内存访问指令:寄存器间接寻址、基址寻址、多寄存器直接寻址
  • 执行堆栈操作:堆栈寻址方式
  1. 立即寻址:

    1
    2
    MOV	R0,	#0	;P0<-9
    ;32位指令,immed_12格式立即数0传送到寄存器R0中
  2. 寄存器直接寻址:寻址用的操作数位于CPU内部的寄存器中

    1
    MOV	R1, R2	;R1<-R2,寄存器R2的内容传到寄存器R1中
  3. 寄存器移位寻址:寻址用的操作数由寄存器中的数值经过移位得到

    1
    MOV	R0,	R1	ASR R2	;R0<-(R1算数右移R2规定的位数)
  4. 寄存器间接寻址:寄存器中存放的内容为操作数的内存地址而不是操作数

    1
    2
    3
    4
    STR	R0, [R1]	;R0中的值传送到以R1的值作为地址的存储器中
    ;指令执行完成后R1中的值不变
    LDR R0, [R1] ;R1中的值作为地址,将内存中该地址单元的数据传送到R0
    ;指令执行完成后R1的值不变
  5. 基址变址寻址:寄存器提供基准地址,这个基准地址与指令称为 “地址偏移量” 的数据相加形成操作数的有效地址

    1
    2
    3
    4
    LDR	R0,[R1,#4]		;R0<-[R1+4],立即数前索引变址寻址
    LDR R0,[R1,#4]! ;R0<-[R1+4],R1<-R1+4,立即数前索引变址寻址,带更新
    LDR R0,[R1].#4 ;R0<-[R1],R1<-R1+4,立即数后索引变址寻址,带更新
    LDR R0,[R1,R2] ;R0<-[R1+R2],寄存器前索引变址寻址
  6. 多寄存器直接寻址:ARM架构中LDM(加载多个寄存器)和STM(存储多个寄存器)指令可以读写存储器中的多个连续数据传送到处理器的多个寄存器。

    1
    LDMIA R0!,{R1,R3-R5}	;R1<-[R0],R3<-[R0+4],R4<-[R0+8],R5<-[R0+12],

    IA:每次读写后地址加4 DB:每次读写前地址减4

总线技术

总线概述

早期总线是并行的多分枝共享式的结构,现代计算机采用的多是串行、点对点、交换式的结构

总线上的设备可以分为主设备和从设备:

  • 具有发起总线操作能力的设备成为总线的主设备(CPU、DMA)
  • 不能发起总线操作,只能服从总线命令的称为从设备(存储器、IO设备)

CPU操作总线进行读写操作,其流程为

  1. CPU向地址线发送 ”从设备“ 的地址
  2. CPU向控制线发送 ”读/写“ 的命令
    • 读操作:CPU等待,然后从数据线上获取数据
    • 写操作:CPU将数据放在数据线上,等待,从设备判断地址,然后根据控制线从数据线接收数据

主动发起总线操作的设备称为发起者,接收总线命令的设备称为操作对象

总线的性能指标

  1. 总线单位时间内最大的数据传输能力,称为总线的带宽(bandwidth)或者吞吐量(throughput)
    • 总线吞吐量计算:总线数据宽度为\(\omega(B)\),时钟周期为\(T(s)\),则吞吐量最大的可能值为\(\omega/T(B/s)\)
  2. 总线的灵活性和扩展性:越宽的总线越快的时钟越受限,扩展性越差;为了连接更多设备应使用桥接(bridge)

总线的分类

分类依据 类型 类型 类型 类型
通信介质的共享方式 共享式总线 交换式总线
信息传递方式 并行总线 串行总线
操作时序 同步总线 半同步总线 异步总线
应用的目的和范围 系统总线 局部总线 扩展总线 片内总线

总线时序和仲裁

总线时序:

根据操作时序可以将总线分为:同步总线 半同步总线 异步总线

  1. 同步总线:
    • 总线系统使用一个统一的时钟和固定的操作周期进行总线操作
    • 优点:简单
    • 缺点:高速设备可以跟上的时钟,低速设备可能跟不上
  2. 半同步总线:
    • 总线按照统一的时钟工作,但是总线周期并不完全固定,可以插入一个或多个称为等待周期的时钟周期
    • 优点:高性能设别按照最快总线周期工作,低性能设别可以适当延长总线周期
    • 实际使用中并行总线如ISA、PCI、AHB等都是半同步总线
  3. 异步总线:
  • 异步总线中没有统一的时钟,需要(如READY或ACK)等信号来控制总线操作

总线可以分为共享式总线和交换式总线,其中共享式总线在使用中需要保证一个时刻只有一个主设备操作总线。

当冲突出现时,我们需要使用总线仲裁器:

  • 集中仲裁器:
    • 总线主设备在操作总线前,需要先向总线仲裁器提出申请(request),并在收到许可(grant)后使用总线。
    • 集中仲裁器收到多个申请时,按照某种优先级规则,选择一个申请者并向其发送许可
  • 分布式仲裁:
    • 分布式仲裁要求每个主设备附带一个实现仲裁功能的组件
    • 需要使用总线是由各仲裁组件交互信息,决定总线使用权

共享式总线4个阶段:申请、裁决、传送和结束

串行总线

计算机性能的提升要求总线带宽或者说吞吐量的提升,这要求增加数据线的宽度OR总线的时钟频率。但是,当宽度提升到64位,时钟频率达到几百MHz时,提升空间就比较有限。

串行总线使用差分信号线传送信号,将各种信息逐个bit的在一对差分线上传输。

为了保证信号完整性,串行通信只能 “点对点” 的连接设备,当多个设备进行串口通信时,需要使用集线器(hub)或交换器(switch)芯片。

总线拓扑呈现以多个集线器为中心的星型结构的互联状态

串行通信通过并行使用多对差分线进行扩展,提高其带宽(吞吐量),总线信息的各字节被分配到不同的差分线,每对线之间不需要bit级别的时间对齐,因此扩展难度比并行总线低。

一种高速串行总线标准:PCIe

总线标准化

  • IBM公司制定了免费的ISA总线标准
  • IBM制定的付费的MCA标准和Campaq制定的开放的EISA标准
  • Intel制定的PCI
  • 后续就是USB、PCIe

扩展总线

当越来越多的设备接入总线时,单一的总线开始难以满足计算机内部高性能和多样性的要求。

因此出现了总线的层次结构,靠近CPU的总线性能更强,性能要求较低的外围设备采用低性能总线接入设备

不同技术的总线之间采用桥接(bridge)芯片连接并转发总线操作

外设可以通过外部总线(external bus)或扩展总线(extended bus)进而通过总线桥路接入到高性能的系统总线

嵌入式系统中的扩展总线,以stm32f407为例

  • FSMC是并行总线,类似于XT总线,面向一些SRAM或者Flash使用
  • 串行总线包括SPI、I2C和CAN

总线中的地址的概念

  • 扩展总线的 “地址”:设备编号
  • 系统总线的 ”地址“:识别存储单元或者IO设备中的寄存器

SPI/I2C、USB、SCSI、CAN等总线协议的介绍

ARM总线

SoC上的总线:

微机原理实验报告

课程实验1

——STM32仿真开发环境的构建与简单汇编程序的编写

推荐阅读:https://blog.csdn.net/qq_46359697/article/details/115255840

实验报告内容

  1. 实验目标与任务
    1. 掌握MDK-ARM开发平台的使用,包括程序编写/调试/编译等。
    2. 掌握ARM的基本汇编指令,能够理解并编写简单的汇编程序实现某些功能。
  2. 实验内容
    1. STM32F407启动代码实现与分析。
    2. 编写简单汇编程序:
      1. 找到3个数字中最大的数字并将结果存储在R0中
      2. 将N,N-1,…… 2,1共N个数字相加,将结果存储在R1,当N = 0时,输出的结果为0.
      3. 调用子例程实现R3低4位中十六进制转换位对应的ASCII码,并将十六进制数和对应的结果存储在内存中,存储在从20000000开始的单元中
  3. 实验步骤

实验报告

MDK-ARM开发平台的使用

安装软件

使用到的软件:

  • MDK-ARM(微控制器开发工具)

uVision是由keil公司开发的集成开发环境(IDE),可以进行代码编辑,文件管理,程序的编译调试等。

目前uVision的版本有uVision2、uVision3、uVision4、uVision5。

我们常说的keil4指的是uVision4,keil5指的是uVision5。

每一个uVision版本下都有4个独立的软件:C51、C251、C166、ARM。

uVision ARM就是MDK,或者可以称为MDK-ARM。这一款软件主要支持ARM7,ARM9,Cortex等ARM内核。

MDK-ARM中中包含PACK工具

image-20220501135651588

PACK工具可以打开.pack文件,双击下载后文件夹中的 “Keil.STM32F4xx_DFP.2.14.0.pack” 文件,选择默认安装路径,即可完成导入。

新建工程

打开 MDK-ARM,选择 Project -> New uVision Project

image-20220501140158387

选择工程存储路径,并给工程起一个名字。

image-20220501140436221

在弹出的选项卡的搜索框中搜索 “STM32F407”,点击加号,选择具体型号 “STM32F407ZET”

image-20220501140958609

STM32 微控制器命名规则如下:

image-20220501141115297

为了导入启动程序,在新弹出的选项卡中做如下操作:

image-20220501141406536

即可得到启动程序

image-20220501141447804

编译程序

直接编译程序,会报错,如下:

image-20220501141621369

错误为,没有找到 main() 函数

因此,需要创建一个.c文件来补充 main() 函数:

首先创建一个新文件

image-20220501141803519

保存文件为.c文件

image-20220501142026434

将.c文件加入到工程文件:在 Source Group 1 文件夹上右键,选择 Add Existing Fiels to Group 'Source Group 1' ...

image-20220501142117084

选项卡中选择文件并添加,然后点击 close 关闭

image-20220501142207067

编译成功:

image-20220501142435430

程序Debug

在没有器件的情况下无法直接对程序进行Debug,因此需要使用模拟器来仿真,需要进行一定的配置

image-20220501142555008

配置方式在课本P91页:

image-20220501142851518

具体操作:

1
2
DARMSTM.DLL
-pSTM32F407ZETx

image-20220501143156502

再次点击Debug即可进入Debug模式:

image-20220501143314055

STM32F407启动代码分析

整体分析:

对于STM32F407启动代码整体的一个描述在 startup_stm32fxxx_xx.s 文件的顶部注释中有着解释,内容如下:

;* Description: STM32F407xx devices vector table for MDK-ARM toolchain. ;* This module performs: ;* - Set the initial SP ;* - Set the initial PC == Reset_Handler ;* - Set the vector table entries with the exceptions ISR address ;* - Branches to __main in the C library (which eventually calls main()). ;* After Reset the CortexM4 processor is in Thread mode, priority is Privileged, and the Stack is set to Main.

含义为:

  • 设置堆栈指针 SP = _initial_sp
  • 设置程序寄存器 PC = Reset_Handler
  • 使用例外ISR设置向量表项
  • 配置系统时钟
  • 配置外部 SRAM 用亍程序变量等数据存储(可选)
  • 调用C库中的 __main() 函数,最终调用 main() 函数
源代码解释:
  1. 开辟栈 (STACK) 空间,用于局部变量,函数调用,函数形参等的开销,栈的大小不能超过内部SRAM的大小。如果编写的程序比较大,定义的局部变量很多,那么就需要修改栈的大小。 \[ \sf{通知编译器链接\rightarrow分配一片栈空间\rightarrow记录栈顶地址} \]

    指令 作用
    EQU EQU是一个伪指令,起作用是定义一个符号,这类似于C语言中的 #defineEQU可以使用“*”来替代。
    AREA 用于定义一个新的段(代码段或数据段),并说明段的相关属性。
    SPACE 用于分配一定数量的内存空间并初始化为0;SPACE跟在AREA后面,用于给新定义的段分配合适大小的内存;SPACE后要跟随数字,指明初始化内存空间的大小;可以使用“%”来替代。
    ALIGN 编译器指令,对指令或数据存放地址进行对齐(一般跟一个立即数,缺省为4字节)
    1
    2
    3
    4
    5
    6
    7
    8
    Stack_Size      EQU     0x00000400
    ;定义一个符号,其中0x400为1KB;此处含义为设置栈空间大小为1KB
    AREA STACK, NOINIT, READWRITE, ALIGN=3
    ;新定义一个段名为STACK的段,不进行初始化,属性为可读,2^3=8字节对齐
    Stack_Mem SPACE Stack_Size
    ;栈本体,这里指令是分配栈大小
    __initial_sp
    ;标号,表示该地址(这里是末尾,即栈顶地址)
  2. 开辟堆(HEAP)空间,主要用于动态内存分配,也就是说用 malloc(), calloc()等函数分配的变量空间在堆上: \[ \sf{通知编译器链接\rightarrow记录堆头地址\rightarrow分配一片堆空间\rightarrow记录堆尾地址} \]

    1
    2
    3
    4
    5
    6
    Heap_Size       EQU     0x00000200

    AREA HEAP, NOINIT, READWRITE, ALIGN=3
    __heap_base
    Heap_Mem SPACE Heap_Size
    __heap_limit
  3. 独立的过渡代码。

    1
    2
    PRESERVE8 ;指定当前文件的栈按 8B 对齐
    THUMB ;表示后面的指令兼容 Thumb 指令集(ARM以前的16位指令集)

    这里有一篇文章,详细解释了为什么要设置 PRESERVE8

  4. 向量表:内核在异常时会访问这个表地址,并根据异常类型查表,按表跳转到异常处理函数执行。

    指令 作用
    EXPORT 声明全局,可被外部文件使用,同义词 GLOBAL
    DCD 以字为单位分配内存,要求4字节对齐且初始化该内存
    DCD 的功能是申请(1个或多个)字地址,并赋初值。每行 DCD 都会生成一个4字节的二进制代码(中断服务代码入口地址)

    向量表的位置在代码段的最前面。具体物理地址由连接器的配置参数(IROM1 的地址决定, keil target 中可修改)。如果程序在 Flash 运行, 则中断向量表默认的起始地址是 0x08000000

    0x08000000 是flash的首地址, 可以看到从 flash 的首地址开始,依次存储sp指针,Reset_Handler 中断地址。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    ; Vector Table Mapped to Address 0 at Reset
    AREA RESET, DATA, READONLY
    ;新定义一个段名为RESRT的段,DATA说明本段用于定义数据段,属性为只读

    ;下述语句将3个标号申明为可外部引用,主要提供给连接器用于链接库文件或其他文件。

    EXPORT __Vectors
    ;定义一个标号 __Vectors 代表向量表的起始地址
    EXPORT __Vectors_End
    ;定义一个标号 __Vectors_End 代表向量表的结束地址
    EXPORT __Vectors_Size
    ;定义一个标号 __Vectors_Size 代表向量表的地址长度

    __Vectors DCD __initial_sp ; Top of Stack
    DCD Reset_Handler ; Reset Handler

    ;!此处省略大量相似语法的代码。76-172行的代码被省略,省略代码为向量表的具体内容

    DCD FPU_IRQHandler ; FPU


    __Vectors_End

    __Vectors_Size EQU __Vectors_End - __Vectors
    ;向量表的地址长度 __Vectors_Size 可以由 __Vectors_End - __Vectors 得到
  5. 各种中断处理程序 xxx_Handler

    指令 作用
    PROC 定义子程序,与ENDP成对使用,表示子程序结束 同义词 FUNCTION
    WEAK 编译器特性。弱定义,优先使用外部文件定义的标号。
    WEAK 声明其它的同名标号优先于该标号被引用,就是说如果外面声明了同样的标号,会优先调用在外部定义的
    EXPORT 声明全局,可被外部文件使用,同义词 GLOBAL
    IMPORT 声明标号来自外部文件,类似于C extern
    B 跳转到一个标号

    先指示编译器汇编一个新的代码段,名为 |.text|,只读

    1
    AREA    |.text|, CODE, READONLY

    除复位中断外的其它的函数默认跳转到中断后,在原地死循环。

    复位程序,也就是默认上电复位后执行的程序

    B:后面跟一个标签,标签直接对应一个地址,B的作用是跳转的标签指示的地址 BX:后面跟一个寄存器,寄存器里面保存地址,BX的作用是跳转到寄存器里面存放的地址 BL:后面跟一个标签,BL跳转到标签指示的地址,并且保存下一条指令地址到R14(即链接寄存器P33) BLX:后面跟一个寄存器,寄存器里面保存地址,并且保存下一条指令地址到R14(即链接寄存器P33)

    B {条件} 目标地址 条件可选,立即跳转执行。(不返回程序),B . 是死循环while(1);的用法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    ;Reset handler
    Reset_Handler PROC
    ;利用PROC、ENDP这一对伪指令把程序分为若干个过程,使程序结构更加清晰
    EXPORT Reset_Handler [WEAK]
    ;WEAK这个声明很重要,它可以让我们在 C 文件中的任意地方,放置中断服务程序,只要保证 C 函数的名字和向量表中的名字一致即可
    IMPORT SystemInit
    IMPORT __main

    LDR R0, =SystemInit
    BLX R0
    LDR R0, =__main
    BX R0
    ENDP

    其他异常处理程序

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    ; Dummy Exception Handlers (infinite loops which can be modified)
    ;_____________________________________________________________
    NMI_Handler PROC
    EXPORT NMI_Handler [WEAK]
    B .
    ENDP

    ;!此处省略大量相似语法的代码。200-232行的代码被省略,省略代码为向量表的具体内容

    SysTick_Handler PROC
    EXPORT SysTick_Handler [WEAK]
    B .
    ;B {条件} 目标地址 条件可选,立即跳转执行。(不返回程序),B . 是死循环while(1);的用法
    ENDP

    Default_Handler PROC
    ;_____________________________________________________________
    EXPORT WWDG_IRQHandler [WEAK]
    EXPORT PVD_IRQHandler [WEAK]
    ;!此处省略大量相似语法的代码。242-319行的代码被省略,省略代码为向量表的具体内容
    EXPORT FPU_IRQHandler [WEAK]
    ;_____________________________________________________________
    WWDG_IRQHandler
    PVD_IRQHandler

    ;!此处省略大量相似语法的代码。324-401行的代码被省略,省略代码为向量表的具体内容

    FPU_IRQHandler
    ;_____________________________________________________________
    B .

    ENDP

    ALIGN

  6. 用户堆栈和堆初始化

    • if
      • 如果使用了微库(MicroLib),就直接导出堆栈地址符号
    • else
      • 导入外部程序__use_two_region_memory并马上执行调用
      • 并导出子程序符号__user_initial_stackheap给外部程序调用
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    ;**********************************************************************
    ; User Stack and Heap initialization
    ;**********************************************************************
    ; 如果定义了 MICROLIB (微库,keil target 中设置),那么程序执行else后的语句
    IF :DEF:__MICROLIB

    EXPORT __initial_sp
    EXPORT __heap_base
    EXPORT __heap_limit

    ELSE

    IMPORT __use_two_region_memory
    EXPORT __user_initial_stackheap

    __user_initial_stackheap

    LDR R0, = Heap_Mem
    LDR R1, =(Stack_Mem + Stack_Size)
    LDR R2, = (Heap_Mem + Heap_Size)
    LDR R3, = Stack_Mem
    BX LR

    ALIGN

    ENDIF

    END
    ;***************END OF FIEL***************
启动流程总结
  1. 分配堆栈
  2. 向量表
  3. 中断/异常处理函数
    1. 复位程序(上电复位默认执行的函数)
      1. SystemInit()
      2. __main()
    2. 其他异常处理程序
  4. 导出堆栈信息等给外部使用
STM32 总体启动顺序

.s启动文件 -> 中断处理函数外部定义 -> SystemInit() -> SetSysClock -> __main -> main()

全部代码展示
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
;******************** (C) COPYRIGHT 2017 STMicroelectronics ********************
;* File Name : startup_stm32f407xx.s
;* Author : MCD Application Team
;* Description : STM32F407xx devices vector table for MDK-ARM toolchain.
;* This module performs:
;* - Set the initial SP
;* - Set the initial PC == Reset_Handler
;* - Set the vector table entries with the exceptions ISR address
;* - Branches to __main in the C library (which eventually
;* calls main()).
;* After Reset the CortexM4 processor is in Thread mode,
;* priority is Privileged, and the Stack is set to Main.
;* <<< Use Configuration Wizard in Context Menu >>>
;*******************************************************************************
;
;* Redistribution and use in source and binary forms, with or without modification,
;* are permitted provided that the following conditions are met:
;* 1. Redistributions of source code must retain the above copyright notice,
;* this list of conditions and the following disclaimer.
;* 2. Redistributions in binary form must reproduce the above copyright notice,
;* this list of conditions and the following disclaimer in the documentation
;* and/or other materials provided with the distribution.
;* 3. Neither the name of STMicroelectronics nor the names of its contributors
;* may be used to endorse or promote products derived from this software
;* without specific prior written permission.
;*
;* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
;* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
;* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
;* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
;* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
;* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
;* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
;* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
;* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
;* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
;
;*******************************************************************************

; Amount of memory (in bytes) allocated for Stack
; Tailor this value to your application needs
; <h> Stack Configuration
; <o> Stack Size (in Bytes) <0x0-0xFFFFFFFF:8>
; </h>·

Stack_Size EQU 0x00000400

AREA STACK, NOINIT, READWRITE, ALIGN=3
Stack_Mem SPACE Stack_Size
__initial_sp


; <h> Heap Configuration
; <o> Heap Size (in Bytes) <0x0-0xFFFFFFFF:8>
; </h>

Heap_Size EQU 0x00000200

AREA HEAP, NOINIT, READWRITE, ALIGN=3
__heap_base
Heap_Mem SPACE Heap_Size
__heap_limit

PRESERVE8
THUMB


; Vector Table Mapped to Address 0 at Reset
AREA RESET, DATA, READONLY
EXPORT __Vectors
EXPORT __Vectors_End
EXPORT __Vectors_Size

__Vectors DCD __initial_sp ; Top of Stack
DCD Reset_Handler ; Reset Handler
DCD NMI_Handler ; NMI Handler
DCD HardFault_Handler ; Hard Fault Handler
DCD MemManage_Handler ; MPU Fault Handler
DCD BusFault_Handler ; Bus Fault Handler
DCD UsageFault_Handler ; Usage Fault Handler
DCD 0 ; Reserved
DCD 0 ; Reserved
DCD 0 ; Reserved
DCD 0 ; Reserved
DCD SVC_Handler ; SVCall Handler
DCD DebugMon_Handler ; Debug Monitor Handler
DCD 0 ; Reserved
DCD PendSV_Handler ; PendSV Handler
DCD SysTick_Handler ; SysTick Handler

; External Interrupts
DCD WWDG_IRQHandler ; Window WatchDog
DCD PVD_IRQHandler ; PVD through EXTI Line detection
DCD TAMP_STAMP_IRQHandler ; Tamper and TimeStamps through the EXTI line
DCD RTC_WKUP_IRQHandler ; RTC Wakeup through the EXTI line
DCD FLASH_IRQHandler ; FLASH
DCD RCC_IRQHandler ; RCC
DCD EXTI0_IRQHandler ; EXTI Line0
DCD EXTI1_IRQHandler ; EXTI Line1
DCD EXTI2_IRQHandler ; EXTI Line2
DCD EXTI3_IRQHandler ; EXTI Line3
DCD EXTI4_IRQHandler ; EXTI Line4
DCD DMA1_Stream0_IRQHandler ; DMA1 Stream 0
DCD DMA1_Stream1_IRQHandler ; DMA1 Stream 1
DCD DMA1_Stream2_IRQHandler ; DMA1 Stream 2
DCD DMA1_Stream3_IRQHandler ; DMA1 Stream 3
DCD DMA1_Stream4_IRQHandler ; DMA1 Stream 4
DCD DMA1_Stream5_IRQHandler ; DMA1 Stream 5
DCD DMA1_Stream6_IRQHandler ; DMA1 Stream 6
DCD ADC_IRQHandler ; ADC1, ADC2 and ADC3s
DCD CAN1_TX_IRQHandler ; CAN1 TX
DCD CAN1_RX0_IRQHandler ; CAN1 RX0
DCD CAN1_RX1_IRQHandler ; CAN1 RX1
DCD CAN1_SCE_IRQHandler ; CAN1 SCE
DCD EXTI9_5_IRQHandler ; External Line[9:5]s
DCD TIM1_BRK_TIM9_IRQHandler ; TIM1 Break and TIM9
DCD TIM1_UP_TIM10_IRQHandler ; TIM1 Update and TIM10
DCD TIM1_TRG_COM_TIM11_IRQHandler ; TIM1 Trigger and Commutation and TIM11
DCD TIM1_CC_IRQHandler ; TIM1 Capture Compare
DCD TIM2_IRQHandler ; TIM2
DCD TIM3_IRQHandler ; TIM3
DCD TIM4_IRQHandler ; TIM4
DCD I2C1_EV_IRQHandler ; I2C1 Event
DCD I2C1_ER_IRQHandler ; I2C1 Error
DCD I2C2_EV_IRQHandler ; I2C2 Event
DCD I2C2_ER_IRQHandler ; I2C2 Error
DCD SPI1_IRQHandler ; SPI1
DCD SPI2_IRQHandler ; SPI2
DCD USART1_IRQHandler ; USART1
DCD USART2_IRQHandler ; USART2
DCD USART3_IRQHandler ; USART3
DCD EXTI15_10_IRQHandler ; External Line[15:10]s
DCD RTC_Alarm_IRQHandler ; RTC Alarm (A and B) through EXTI Line
DCD OTG_FS_WKUP_IRQHandler ; USB OTG FS Wakeup through EXTI line
DCD TIM8_BRK_TIM12_IRQHandler ; TIM8 Break and TIM12
DCD TIM8_UP_TIM13_IRQHandler ; TIM8 Update and TIM13
DCD TIM8_TRG_COM_TIM14_IRQHandler ; TIM8 Trigger and Commutation and TIM14
DCD TIM8_CC_IRQHandler ; TIM8 Capture Compare
DCD DMA1_Stream7_IRQHandler ; DMA1 Stream7
DCD FMC_IRQHandler ; FMC
DCD SDIO_IRQHandler ; SDIO
DCD TIM5_IRQHandler ; TIM5
DCD SPI3_IRQHandler ; SPI3
DCD UART4_IRQHandler ; UART4
DCD UART5_IRQHandler ; UART5
DCD TIM6_DAC_IRQHandler ; TIM6 and DAC1&2 underrun errors
DCD TIM7_IRQHandler ; TIM7
DCD DMA2_Stream0_IRQHandler ; DMA2 Stream 0
DCD DMA2_Stream1_IRQHandler ; DMA2 Stream 1
DCD DMA2_Stream2_IRQHandler ; DMA2 Stream 2
DCD DMA2_Stream3_IRQHandler ; DMA2 Stream 3
DCD DMA2_Stream4_IRQHandler ; DMA2 Stream 4
DCD ETH_IRQHandler ; Ethernet
DCD ETH_WKUP_IRQHandler ; Ethernet Wakeup through EXTI line
DCD CAN2_TX_IRQHandler ; CAN2 TX
DCD CAN2_RX0_IRQHandler ; CAN2 RX0
DCD CAN2_RX1_IRQHandler ; CAN2 RX1
DCD CAN2_SCE_IRQHandler ; CAN2 SCE
DCD OTG_FS_IRQHandler ; USB OTG FS
DCD DMA2_Stream5_IRQHandler ; DMA2 Stream 5
DCD DMA2_Stream6_IRQHandler ; DMA2 Stream 6
DCD DMA2_Stream7_IRQHandler ; DMA2 Stream 7
DCD USART6_IRQHandler ; USART6
DCD I2C3_EV_IRQHandler ; I2C3 event
DCD I2C3_ER_IRQHandler ; I2C3 error
DCD OTG_HS_EP1_OUT_IRQHandler ; USB OTG HS End Point 1 Out
DCD OTG_HS_EP1_IN_IRQHandler ; USB OTG HS End Point 1 In
DCD OTG_HS_WKUP_IRQHandler ; USB OTG HS Wakeup through EXTI
DCD OTG_HS_IRQHandler ; USB OTG HS
DCD DCMI_IRQHandler ; DCMI
DCD 0 ; Reserved
DCD HASH_RNG_IRQHandler ; Hash and Rng
DCD FPU_IRQHandler ; FPU


__Vectors_End

__Vectors_Size EQU __Vectors_End - __Vectors

AREA |.text|, CODE, READONLY

; Reset handler
Reset_Handler PROC
EXPORT Reset_Handler [WEAK]
IMPORT SystemInit
IMPORT __main

LDR R0, =SystemInit
BLX R0
LDR R0, =__main
BX R0
ENDP

; Dummy Exception Handlers (infinite loops which can be modified)

NMI_Handler PROC
EXPORT NMI_Handler [WEAK]
B .
ENDP
HardFault_Handler\
PROC
EXPORT HardFault_Handler [WEAK]
B .
ENDP
MemManage_Handler\
PROC
EXPORT MemManage_Handler [WEAK]
B .
ENDP
BusFault_Handler\
PROC
EXPORT BusFault_Handler [WEAK]
B .
ENDP
UsageFault_Handler\
PROC
EXPORT UsageFault_Handler [WEAK]
B .
ENDP
SVC_Handler PROC
EXPORT SVC_Handler [WEAK]
B .
ENDP
DebugMon_Handler\
PROC
EXPORT DebugMon_Handler [WEAK]
B .
ENDP
PendSV_Handler PROC
EXPORT PendSV_Handler [WEAK]
B .
ENDP
SysTick_Handler PROC
EXPORT SysTick_Handler [WEAK]
B .
ENDP

Default_Handler PROC

EXPORT WWDG_IRQHandler [WEAK]
EXPORT PVD_IRQHandler [WEAK]
EXPORT TAMP_STAMP_IRQHandler [WEAK]
EXPORT RTC_WKUP_IRQHandler [WEAK]
EXPORT FLASH_IRQHandler [WEAK]
EXPORT RCC_IRQHandler [WEAK]
EXPORT EXTI0_IRQHandler [WEAK]
EXPORT EXTI1_IRQHandler [WEAK]
EXPORT EXTI2_IRQHandler [WEAK]
EXPORT EXTI3_IRQHandler [WEAK]
EXPORT EXTI4_IRQHandler [WEAK]
EXPORT DMA1_Stream0_IRQHandler [WEAK]
EXPORT DMA1_Stream1_IRQHandler [WEAK]
EXPORT DMA1_Stream2_IRQHandler [WEAK]
EXPORT DMA1_Stream3_IRQHandler [WEAK]
EXPORT DMA1_Stream4_IRQHandler [WEAK]
EXPORT DMA1_Stream5_IRQHandler [WEAK]
EXPORT DMA1_Stream6_IRQHandler [WEAK]
EXPORT ADC_IRQHandler [WEAK]
EXPORT CAN1_TX_IRQHandler [WEAK]
EXPORT CAN1_RX0_IRQHandler [WEAK]
EXPORT CAN1_RX1_IRQHandler [WEAK]
EXPORT CAN1_SCE_IRQHandler [WEAK]
EXPORT EXTI9_5_IRQHandler [WEAK]
EXPORT TIM1_BRK_TIM9_IRQHandler [WEAK]
EXPORT TIM1_UP_TIM10_IRQHandler [WEAK]
EXPORT TIM1_TRG_COM_TIM11_IRQHandler [WEAK]
EXPORT TIM1_CC_IRQHandler [WEAK]
EXPORT TIM2_IRQHandler [WEAK]
EXPORT TIM3_IRQHandler [WEAK]
EXPORT TIM4_IRQHandler [WEAK]
EXPORT I2C1_EV_IRQHandler [WEAK]
EXPORT I2C1_ER_IRQHandler [WEAK]
EXPORT I2C2_EV_IRQHandler [WEAK]
EXPORT I2C2_ER_IRQHandler [WEAK]
EXPORT SPI1_IRQHandler [WEAK]
EXPORT SPI2_IRQHandler [WEAK]
EXPORT USART1_IRQHandler [WEAK]
EXPORT USART2_IRQHandler [WEAK]
EXPORT USART3_IRQHandler [WEAK]
EXPORT EXTI15_10_IRQHandler [WEAK]
EXPORT RTC_Alarm_IRQHandler [WEAK]
EXPORT OTG_FS_WKUP_IRQHandler [WEAK]
EXPORT TIM8_BRK_TIM12_IRQHandler [WEAK]
EXPORT TIM8_UP_TIM13_IRQHandler [WEAK]
EXPORT TIM8_TRG_COM_TIM14_IRQHandler [WEAK]
EXPORT TIM8_CC_IRQHandler [WEAK]
EXPORT DMA1_Stream7_IRQHandler [WEAK]
EXPORT FMC_IRQHandler [WEAK]
EXPORT SDIO_IRQHandler [WEAK]
EXPORT TIM5_IRQHandler [WEAK]
EXPORT SPI3_IRQHandler [WEAK]
EXPORT UART4_IRQHandler [WEAK]
EXPORT UART5_IRQHandler [WEAK]
EXPORT TIM6_DAC_IRQHandler [WEAK]
EXPORT TIM7_IRQHandler [WEAK]
EXPORT DMA2_Stream0_IRQHandler [WEAK]
EXPORT DMA2_Stream1_IRQHandler [WEAK]
EXPORT DMA2_Stream2_IRQHandler [WEAK]
EXPORT DMA2_Stream3_IRQHandler [WEAK]
EXPORT DMA2_Stream4_IRQHandler [WEAK]
EXPORT ETH_IRQHandler [WEAK]
EXPORT ETH_WKUP_IRQHandler [WEAK]
EXPORT CAN2_TX_IRQHandler [WEAK]
EXPORT CAN2_RX0_IRQHandler [WEAK]
EXPORT CAN2_RX1_IRQHandler [WEAK]
EXPORT CAN2_SCE_IRQHandler [WEAK]
EXPORT OTG_FS_IRQHandler [WEAK]
EXPORT DMA2_Stream5_IRQHandler [WEAK]
EXPORT DMA2_Stream6_IRQHandler [WEAK]
EXPORT DMA2_Stream7_IRQHandler [WEAK]
EXPORT USART6_IRQHandler [WEAK]
EXPORT I2C3_EV_IRQHandler [WEAK]
EXPORT I2C3_ER_IRQHandler [WEAK]
EXPORT OTG_HS_EP1_OUT_IRQHandler [WEAK]
EXPORT OTG_HS_EP1_IN_IRQHandler [WEAK]
EXPORT OTG_HS_WKUP_IRQHandler [WEAK]
EXPORT OTG_HS_IRQHandler [WEAK]
EXPORT DCMI_IRQHandler [WEAK]
EXPORT HASH_RNG_IRQHandler [WEAK]
EXPORT FPU_IRQHandler [WEAK]

WWDG_IRQHandler
PVD_IRQHandler
TAMP_STAMP_IRQHandler
RTC_WKUP_IRQHandler
FLASH_IRQHandler
RCC_IRQHandler
EXTI0_IRQHandler
EXTI1_IRQHandler
EXTI2_IRQHandler
EXTI3_IRQHandler
EXTI4_IRQHandler
DMA1_Stream0_IRQHandler
DMA1_Stream1_IRQHandler
DMA1_Stream2_IRQHandler
DMA1_Stream3_IRQHandler
DMA1_Stream4_IRQHandler
DMA1_Stream5_IRQHandler
DMA1_Stream6_IRQHandler
ADC_IRQHandler
CAN1_TX_IRQHandler
CAN1_RX0_IRQHandler
CAN1_RX1_IRQHandler
CAN1_SCE_IRQHandler
EXTI9_5_IRQHandler
TIM1_BRK_TIM9_IRQHandler
TIM1_UP_TIM10_IRQHandler
TIM1_TRG_COM_TIM11_IRQHandler
TIM1_CC_IRQHandler
TIM2_IRQHandler
TIM3_IRQHandler
TIM4_IRQHandler
I2C1_EV_IRQHandler
I2C1_ER_IRQHandler
I2C2_EV_IRQHandler
I2C2_ER_IRQHandler
SPI1_IRQHandler
SPI2_IRQHandler
USART1_IRQHandler
USART2_IRQHandler
USART3_IRQHandler
EXTI15_10_IRQHandler
RTC_Alarm_IRQHandler
OTG_FS_WKUP_IRQHandler
TIM8_BRK_TIM12_IRQHandler
TIM8_UP_TIM13_IRQHandler
TIM8_TRG_COM_TIM14_IRQHandler
TIM8_CC_IRQHandler
DMA1_Stream7_IRQHandler
FMC_IRQHandler
SDIO_IRQHandler
TIM5_IRQHandler
SPI3_IRQHandler
UART4_IRQHandler
UART5_IRQHandler
TIM6_DAC_IRQHandler
TIM7_IRQHandler
DMA2_Stream0_IRQHandler
DMA2_Stream1_IRQHandler
DMA2_Stream2_IRQHandler
DMA2_Stream3_IRQHandler
DMA2_Stream4_IRQHandler
ETH_IRQHandler
ETH_WKUP_IRQHandler
CAN2_TX_IRQHandler
CAN2_RX0_IRQHandler
CAN2_RX1_IRQHandler
CAN2_SCE_IRQHandler
OTG_FS_IRQHandler
DMA2_Stream5_IRQHandler
DMA2_Stream6_IRQHandler
DMA2_Stream7_IRQHandler
USART6_IRQHandler
I2C3_EV_IRQHandler
I2C3_ER_IRQHandler
OTG_HS_EP1_OUT_IRQHandler
OTG_HS_EP1_IN_IRQHandler
OTG_HS_WKUP_IRQHandler
OTG_HS_IRQHandler
DCMI_IRQHandler
HASH_RNG_IRQHandler
FPU_IRQHandler

B .

ENDP

ALIGN

;*******************************************************************************
; User Stack and Heap initialization
;*******************************************************************************
IF :DEF:__MICROLIB

EXPORT __initial_sp
EXPORT __heap_base
EXPORT __heap_limit

ELSE

IMPORT __use_two_region_memory
EXPORT __user_initial_stackheap

__user_initial_stackheap

LDR R0, = Heap_Mem
LDR R1, =(Stack_Mem + Stack_Size)
LDR R2, = (Heap_Mem + Heap_Size)
LDR R3, = Stack_Mem
BX LR

ALIGN

ENDIF

END

;************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE*****

简单汇编程序实现

三数中寻找最大数

题目要求:

找到3个数字中最大的数字并将结果存储在R0中。

代码展示:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
N1				EQU		456
N2 EQU 1278
N3 EQU 85
Stack_Size EQU 0x00000400
AREA Mystack, NOINIT, READWRITE, ALIGN=3
Stack_Mem SPACE Stack_Size
__initial_sp

AREA Reset, DATA, READONLY
__Vectors DCD __initial_sp
DCD Reset_Handler

THUMB
PRESERVE8
AREA Init, CODE, READONLY
ENTRY

Reset_Handler
LDR R0,=N1
LDR R1,=N2
LDR R2,=N3
CMP R0,R1
BHI next
MOV R0,R1
next
CMP R0,R2
BHI deadloop
MOV R0,R2
deadloop
B deadloop
NOP
END
1
2
3
4
5
6
7
8
9
10
11
12
13
14
Rebuild started: Project: Project_1
*** Using Compiler 'V6.16', folder: 'D:\MDK\ARM\ARMCLANG\Bin'
Rebuild target 'Target 1'
assembling startup_stm32f407xx.s...
compiling system_stm32f4xx.c...
linking...
.\Objects\Project_1.axf: Error: L6320W: Ignoring --entry command. Cannot find argument 'Reset_Handler'.
.\Objects\Project_1.axf: Warning: L6320W: Ignoring --first command. Cannot find argument '__Vectors'.
Not enough information to list image symbols.
Not enough information to list load addresses in the image map.
Finished: 2 information, 1 warning and 1 error messages.
".\Objects\Project_1.axf" - 1 Error(s), 1 Warning(s).
Target not created.
Build Time Elapsed: 00:00:00

image-20220506231919016

课本代码会报错,因此修正代码课本代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
N1				EQU		456
N2 EQU 1278
N3 EQU 85
Stack_Size EQU 0x00000400
AREA Mystack, NOINIT, READWRITE, ALIGN=3
Stack_Mem SPACE Stack_Size
__initial_sp

AREA Reset, DATA, READONLY
EXPORT __Vectors

__Vectors DCD __initial_sp
DCD Reset_Handler

THUMB
PRESERVE8
AREA Init, CODE, READONLY
ENTRY

Reset_Handler
EXPORT Reset_Handler [WEAK]
LDR R0,=N1
LDR R1,=N2
LDR R2,=N3
CMP R0,R1
BHI next
MOV R0,R1
next
CMP R0,R2
BHI deadloop
MOV R0,R2
deadloop
B deadloop
NOP
END

image-20220506231830620

结果展示:

image-20220506232317154

N个数相加

题目要求:

将N,N-1,…… 2,1共N个数字相加,将结果存储在R1,当N = 0时,输出的结果为0。

代码展示:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
N				EQU		10
Stack_Size EQU 0x00000400
AREA Mystack, NOINIT, READWRITE, ALIGN=3
Stack_Mem SPACE Stack_Size
__initial_sp

AREA Reset, DATA, READONLY
EXPORT __Vectors

__Vectors DCD __initial_sp
DCD Reset_Handler

THUMB
PRESERVE8
AREA Init, CODE, READONLY
ENTRY

Reset_Handler

EXPORT Reset_Handler [WEAK]

LDR R0, =N
MOV R1, #0
loop
ADD r1, r0
SUBS r0, #1
BNE loop
deadloop
B deadloop
NOP
END
结果展示:

image-20220506213516914

十六进制转ASIC码

题目要求:

调用子例程实现R3低4位中十六进制转换位对应的ASCII码,并将十六进制数和对应的结果存储在内存中,存储在从20000000开始的单元中

代码实现:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
Stack_Size		EQU		0x00000400
AREA Mystack, NOINIT, READWRITE, ALIGN=3
Stack_Mem SPACE Stack_Size
__initial_sp

AREA Reset, DATA, READONLY
EXPORT __Vectors
__Vectors DCD __initial_sp
DCD Reset_Handler

THUMB
PRESERVE8
AREA Init, CODE, READONLY
ENTRY

Reset_Handler
EXPORT Reset_Handler [WEAK]
MOV R3,0x0A
CMP R3,#9
BLE Next
ADD R3,R3,#7
Next
ADD R3,R3,#7
deadloop
B deadloop
NOP
END
结果展示:

image-20220506212809459

image-20220506213109858

课程实验2

——使用STM32F401VE控制LED

实验报告内容

  1. 实验目标与任务

    1. 掌握STM32F40x的GPIO输入输出的使用。
    2. 主寄存器方法,库函数方法设置GPIO。
    3. 了解如何使用STM32CubeMX构建初始代码。
  2. 实验内容

    1. 开关控制LED灯。当按下按钮时,对应的LED灯亮500ms,然后熄灭。

      image-20220507073550593

    2. 要求:需要详细的步骤,包括proteus仿真电路和程序。

  3. 实验步骤

实验报告

软件使用

  • STMCubeMX:

    STMCubeMX是ST公司推出的一种自动创建单片机工程及初始化代码的工具

  • Proteus 8 Professional:

    Proteus是一款EDA工具软件,它不仅具有其它EDA工具软件的仿真功能,还能较好的仿真单片机及外围器件。

  • MDK-ARM:

    Keil公司开发的ARM开发工具MDK,是用来开发基于ARM核的系列微控制器的嵌入式应用程序

实验流程

使用 Proteus 搭建仿真电路 -> 使用 CubeMX 生成初始化代码 -> 使用 MDK-ARM 编写主函数,生成 .hex 文件 -> 将 .hex 文件添加到仿真电路中

工程建立

  1. 使用Proteus建立仿真文件:

    1. 打开Proteus,选择新建一个原理图文件

      image-20220509173625891

    2. 使用快捷键“Ctrl+S”将文件保存到电脑中

    3. 添加元器件:

      image-20220509174203888

    4. 在工程中添加如下元器件:

      image-20220509174431386

      添加电源端和地:

      image-20220509174549453

      添加好后如下图所示:

      image-20220509174748405

    5. 按实验要求接线,如下图:

      image-20220509174839001

    6. 创建仿真文件完成

  2. 使用STMCubeMX建立初始化代码:

    1. 建立工程:

      image-20220509175001429

    2. 初次使用等待联网下载器件库,下载完成后做如下操作:

      image-20220509175144590

    3. 进入如下界面,点击管理工程

      image-20220509175343646

    4. 配置工程基本信息:

      image-20220509175857840
    5. 打开生成的文件夹,看到如下目录,进入MDK-ARM文件夹,打开其中的keil工程

      image-20220509180023158

      image-20220509180057731

    6. 打开工程中的main.c文件编写代码:

      image-20220509180339476

  3. 使用MDK开发环境编写代码操控LED灯闪烁:

    1. 首先确认工程配置为输出.hex文件:

      image-20220509180518790

    2. 在main.c中添加如下代码:

      image-20220509181336172

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      /* USER CODE BEGIN 0 */
      void init_LED()
      {
      RCC->AHB1ENR |= 1<<2;

      GPIOC->MODER |= (1<<0)|(1<<2)|(1<<4);
      GPIOC->OTYPER = 0x0;
      GPIOC->OSPEEDR = 0x0;
      }
      /* USER CODE END 0 */

      /* USER CODE BEGIN Init */
      init_LED();
      /* USER CODE END Init */

      /* USER CODE BEGIN WHILE */
      while (1)
      {
      GPIOC->BSRR = (1<<0)|(1<<1)|(1<<2);
      HAL_Delay(500);
      GPIOC->BSRR = (1<<16)|(1<<17)|(1<<18);
      HAL_Delay(500);
      /* USER CODE END WHILE */
      }
    3. 编译程序:

      image-20220509181515288

  4. 将 .hex 文件添加到仿真电路中

    1. 双击STM32F401VE,弹出编辑卡,点击程序文件:

      image-20220509181755170

    2. 在如下路径中找到.hex文件

      image-20220509182030753

    3. 选中文件,即可将文件拷入到仿真器件中,然后运行仿真:

      image-20220509182153882

    4. 即可看到小灯闪烁。

使用ST库函数来编写代码

  1. 使用CubeMX生成代码:

    image-20220509182403928

  2. 返回keil,允许重加载代码:

    image-20220509182454542

  3. 新代码中增加了一个初始化函数

    image-20220509182524721

  4. 使用库函数来实现与刚才相同的功能:

    image-20220509182719004

    1
    2
    3
    4
    HAL_GPIO_WritePin(GPIOC, GPIO_PIN_0|GPIO_PIN_1|GPIO_PIN_2, GPIO_PIN_RESET);
    HAL_Delay(500);
    HAL_GPIO_WritePin(GPIOC, GPIO_PIN_0|GPIO_PIN_1|GPIO_PIN_2, GPIO_PIN_SET);
    HAL_Delay(500);
  5. 重新编译并将代码拷入仿真软件,可以得到相同的效果。

实现题目要求:

  1. 结果展示:

    GIF 2022-5-9 18-35-34

  2. 代码展示(简化后的代码,包含注释):

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    #include "main.h"

    void SystemClock_Config(void);
    static void MX_GPIO_Init(void);

    int main(void)
    {
    //定义按键变量并初始化为1
    char button_0 = GPIO_PIN_SET;
    char button_1 = GPIO_PIN_SET;
    char button_2 = GPIO_PIN_SET;

    //初始化HAL、系统时钟和需要的GPIO口
    HAL_Init();
    SystemClock_Config();
    MX_GPIO_Init();

    //主循环
    while (1)
    {
    //循环读取GPIO口的值
    button_0 = HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_13);
    button_1 = HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_14);
    button_2 = HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_15);

    //如果GPIO口的值被拉到0
    if(button_0 == GPIO_PIN_RESET)
    {
    //延迟10ms消抖
    HAL_Delay(10);
    //如果GPIO口仍为0
    if(button_0 == GPIO_PIN_RESET)
    {
    //GPIOC_0口输出高电平,点亮LED灯
    HAL_GPIO_WritePin(GPIOC, GPIO_PIN_0, GPIO_PIN_SET);
    //延时500ms
    HAL_Delay(500);
    }
    }
    else if(button_1 == GPIO_PIN_RESET)
    {
    HAL_Delay(10);
    if(button_1 == GPIO_PIN_RESET)
    {
    HAL_GPIO_WritePin(GPIOC, GPIO_PIN_1, GPIO_PIN_SET);
    HAL_Delay(500);
    }
    }
    else if(button_2 == GPIO_PIN_RESET)
    {
    HAL_Delay(10);
    if(button_2 == GPIO_PIN_RESET)
    {
    HAL_GPIO_WritePin(GPIOC, GPIO_PIN_2, GPIO_PIN_SET);
    HAL_Delay(500);
    }
    }
    //将所有GPIO口输出置为低电平
    HAL_GPIO_WritePin(GPIOC, GPIO_PIN_0|GPIO_PIN_1|GPIO_PIN_2, GPIO_PIN_RESET);
    }
    }
    //CbueMX生成的系统时钟配置
    void SystemClock_Config(void)
    {
    RCC_OscInitTypeDef RCC_OscInitStruct = {0};
    RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};

    /** Configure the main internal regulator output voltage
    */
    __HAL_RCC_PWR_CLK_ENABLE();
    __HAL_PWR_VOLTAGESCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE2);

    /** Initializes the RCC Oscillators according to the specified parameters
    * in the RCC_OscInitTypeDef structure.
    */
    RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSI;
    RCC_OscInitStruct.HSIState = RCC_HSI_ON;
    RCC_OscInitStruct.HSICalibrationValue = RCC_HSICALIBRATION_DEFAULT;
    RCC_OscInitStruct.PLL.PLLState = RCC_PLL_NONE;
    if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
    {
    Error_Handler();
    }

    /** Initializes the CPU, AHB and APB buses clocks
    */
    RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
    |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
    RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_HSI;
    RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
    RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV1;
    RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;

    if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_0) != HAL_OK)
    {
    Error_Handler();
    }
    }
    //CubeMX生成的GPIO初始化代码
    static void MX_GPIO_Init(void)
    {
    GPIO_InitTypeDef GPIO_InitStruct = {0};

    /* GPIO Ports Clock Enable */
    __HAL_RCC_GPIOC_CLK_ENABLE();
    __HAL_RCC_GPIOA_CLK_ENABLE();

    /*Configure GPIO pin Output Level */
    HAL_GPIO_WritePin(GPIOC, GPIO_PIN_0|GPIO_PIN_1|GPIO_PIN_2, GPIO_PIN_RESET);

    /*Configure GPIO pins : PC0 PC1 PC2 */
    GPIO_InitStruct.Pin = GPIO_PIN_0|GPIO_PIN_1|GPIO_PIN_2;
    GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
    GPIO_InitStruct.Pull = GPIO_NOPULL;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
    HAL_GPIO_Init(GPIOC, &GPIO_InitStruct);

    /*Configure GPIO pins : PA13 PA14 PA15 */
    GPIO_InitStruct.Pin = GPIO_PIN_13|GPIO_PIN_14|GPIO_PIN_15;
    GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
    GPIO_InitStruct.Pull = GPIO_PULLUP;
    HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
    }
    //对main.h中的函数声明进行实现
    void Error_Handler(void){}
  3. 因为按键触发后即点亮灯长达500ms,因此可以不进行按键消抖,已经经过验证,故主函数中可做如下修改:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    int main(void)
    {
    char button_0 = GPIO_PIN_RESET;
    char button_1 = GPIO_PIN_RESET;
    char button_2 = GPIO_PIN_RESET;

    HAL_Init();
    SystemClock_Config();
    MX_GPIO_Init();

    while (1)
    {
    button_0 = HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_13);
    button_1 = HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_14);
    button_2 = HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_15);

    if(button_0 == GPIO_PIN_RESET)
    {
    HAL_GPIO_WritePin(GPIOC, GPIO_PIN_0, GPIO_PIN_SET);
    HAL_Delay(500);
    }
    else if(button_1 == GPIO_PIN_RESET)
    {
    HAL_GPIO_WritePin(GPIOC, GPIO_PIN_1, GPIO_PIN_SET);
    HAL_Delay(500);
    }
    else if(button_2 == GPIO_PIN_RESET)
    {
    HAL_GPIO_WritePin(GPIOC, GPIO_PIN_2, GPIO_PIN_SET);
    HAL_Delay(500);
    }
    HAL_GPIO_WritePin(GPIOC, GPIO_PIN_0|GPIO_PIN_1|GPIO_PIN_2, GPIO_PIN_RESET);
    }
    }
  4. 附完整代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    176
    177
    178
    179
    180
    181
    182
    183
    184
    185
    186
    187
    188
    189
    190
    191
    192
    193
    194
    195
    196
    197
    198
    199
    200
    201
    202
    203
    204
    205
    206
    207
    208
    209
    210
    211
    212
    213
    214
    215
    216
    217
    218
    219
    220
    221
    222
    223
    224
    225
    226
    227
    228
    229
    230
    231
    232
    233
    234
    235
    236
    237
    238
    239
    240
    241
    242
    243
    244
    245
    /* USER CODE BEGIN Header */
    /**
    ******************************************************************************
    * @file : main.c
    * @brief : Main program body
    ******************************************************************************
    * @attention
    *
    * Copyright (c) 2022 STMicroelectronics.
    * All rights reserved.
    *
    * This software is licensed under terms that can be found in the LICENSE file
    * in the root directory of this software component.
    * If no LICENSE file comes with this software, it is provided AS-IS.
    *
    ******************************************************************************
    */
    /* USER CODE END Header */
    /* Includes ------------------------------------------------------------------*/
    #include "main.h"

    /* Private includes ----------------------------------------------------------*/
    /* USER CODE BEGIN Includes */

    /* USER CODE END Includes */

    /* Private typedef -----------------------------------------------------------*/
    /* USER CODE BEGIN PTD */

    /* USER CODE END PTD */

    /* Private define ------------------------------------------------------------*/
    /* USER CODE BEGIN PD */
    /* USER CODE END PD */

    /* Private macro -------------------------------------------------------------*/
    /* USER CODE BEGIN PM */

    /* USER CODE END PM */

    /* Private variables ---------------------------------------------------------*/

    /* USER CODE BEGIN PV */

    /* USER CODE END PV */

    /* Private function prototypes -----------------------------------------------*/
    void SystemClock_Config(void);
    static void MX_GPIO_Init(void);
    /* USER CODE BEGIN PFP */

    /* USER CODE END PFP */

    /* Private user code ---------------------------------------------------------*/
    /* USER CODE BEGIN 0 */

    /* USER CODE END 0 */

    /**
    * @brief The application entry point.
    * @retval int
    */
    int main(void)
    {
    /* USER CODE BEGIN 1 */
    char button_0 = GPIO_PIN_SET;
    char button_1 = GPIO_PIN_SET;
    char button_2 = GPIO_PIN_SET;
    /* USER CODE END 1 */

    /* MCU Configuration--------------------------------------------------------*/

    /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
    HAL_Init();

    /* USER CODE BEGIN Init */

    /* USER CODE END Init */

    /* Configure the system clock */
    SystemClock_Config();

    /* USER CODE BEGIN SysInit */

    /* USER CODE END SysInit */

    /* Initialize all configured peripherals */
    MX_GPIO_Init();
    /* USER CODE BEGIN 2 */

    /* USER CODE END 2 */

    /* Infinite loop */
    /* USER CODE BEGIN WHILE */
    while (1)
    {

    button_0 = HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_13);
    button_1 = HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_14);
    button_2 = HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_15);

    if(button_0 == GPIO_PIN_RESET)
    {
    HAL_Delay(10);
    if(button_0 == GPIO_PIN_RESET)
    {
    HAL_GPIO_WritePin(GPIOC, GPIO_PIN_0, GPIO_PIN_SET);
    HAL_Delay(500);
    }
    }
    else if(button_1 == GPIO_PIN_RESET)
    {
    HAL_Delay(10);
    if(button_1 == GPIO_PIN_RESET)
    {
    HAL_GPIO_WritePin(GPIOC, GPIO_PIN_1, GPIO_PIN_SET);
    HAL_Delay(500);
    }
    }
    else if(button_2 == GPIO_PIN_RESET)
    {
    HAL_Delay(10);
    if(button_2 == GPIO_PIN_RESET)
    {
    HAL_GPIO_WritePin(GPIOC, GPIO_PIN_2, GPIO_PIN_SET);
    HAL_Delay(500);
    }
    }

    HAL_GPIO_WritePin(GPIOC, GPIO_PIN_0|GPIO_PIN_1|GPIO_PIN_2, GPIO_PIN_RESET);

    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
    }
    /* USER CODE END 3 */
    }

    /**
    * @brief System Clock Configuration
    * @retval None
    */
    void SystemClock_Config(void)
    {
    RCC_OscInitTypeDef RCC_OscInitStruct = {0};
    RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};

    /** Configure the main internal regulator output voltage
    */
    __HAL_RCC_PWR_CLK_ENABLE();
    __HAL_PWR_VOLTAGESCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE2);

    /** Initializes the RCC Oscillators according to the specified parameters
    * in the RCC_OscInitTypeDef structure.
    */
    RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSI;
    RCC_OscInitStruct.HSIState = RCC_HSI_ON;
    RCC_OscInitStruct.HSICalibrationValue = RCC_HSICALIBRATION_DEFAULT;
    RCC_OscInitStruct.PLL.PLLState = RCC_PLL_NONE;
    if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
    {
    Error_Handler();
    }

    /** Initializes the CPU, AHB and APB buses clocks
    */
    RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
    |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
    RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_HSI;
    RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
    RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV1;
    RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;

    if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_0) != HAL_OK)
    {
    Error_Handler();
    }
    }

    /**
    * @brief GPIO Initialization Function
    * @param None
    * @retval None
    */
    static void MX_GPIO_Init(void)
    {
    GPIO_InitTypeDef GPIO_InitStruct = {0};

    /* GPIO Ports Clock Enable */
    __HAL_RCC_GPIOC_CLK_ENABLE();
    __HAL_RCC_GPIOA_CLK_ENABLE();

    /*Configure GPIO pin Output Level */
    HAL_GPIO_WritePin(GPIOC, GPIO_PIN_0|GPIO_PIN_1|GPIO_PIN_2, GPIO_PIN_RESET);

    /*Configure GPIO pins : PC0 PC1 PC2 */
    GPIO_InitStruct.Pin = GPIO_PIN_0|GPIO_PIN_1|GPIO_PIN_2;
    GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
    GPIO_InitStruct.Pull = GPIO_NOPULL;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
    HAL_GPIO_Init(GPIOC, &GPIO_InitStruct);

    /*Configure GPIO pins : PA13 PA14 PA15 */
    GPIO_InitStruct.Pin = GPIO_PIN_13|GPIO_PIN_14|GPIO_PIN_15;
    GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
    GPIO_InitStruct.Pull = GPIO_PULLUP;
    HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);

    }

    /* USER CODE BEGIN 4 */

    /* USER CODE END 4 */

    /**
    * @brief This function is executed in case of error occurrence.
    * @retval None
    */
    void Error_Handler(void)
    {
    /* USER CODE BEGIN Error_Handler_Debug */
    /* User can add his own implementation to report the HAL error return state */
    __disable_irq();
    while (1)
    {
    }
    /* USER CODE END Error_Handler_Debug */
    }

    #ifdef USE_FULL_ASSERT
    /**
    * @brief Reports the name of the source file and the source line number
    * where the assert_param error has occurred.
    * @param file: pointer to the source file name
    * @param line: assert_param error line source number
    * @retval None
    */
    void assert_failed(uint8_t *file, uint32_t line)
    {
    /* USER CODE BEGIN 6 */
    /* User can add his own implementation to report the file name and line number,
    ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
    /* USER CODE END 6 */
    }
    #endif /* USE_FULL_ASSERT */