Cortex-M0

本篇先简单介绍MDK的安装流程,然后重点说明如何把敏矽微电的Cortex-M0的PACK包添加到mdk中,这样才能顺利的在MDK环境下开发敏矽微电子Cortex-M0新片。最后用了很大篇幅介绍如何建立工程,工程中各种文件的添加等等。

1、敏矽微电子Cortex-M0的开发环境

敏矽微电子Cortex-M0是基于ARM内核的,所以基于ARM的开发环境都可以用来开发敏矽微电子Cortex-M0的芯片。最常见的两种ARM开发平台是MDK和IAR。我们今天着重介绍MDK环境下如何建立基于敏矽微电子Cortex-M0芯片的项目,以及如何在MDK环境下调试敏矽微电子Cortex-M0芯片。
需要说明的是:MDK软件是需要注册的,强烈建议大家使用正版软件。不过在刚开始学习以及资金有限的情况下,可以使用MDK的评估版本,评估版本的程序容量是32K版本。这个程序容量对于初学来说,是足够用的。

闲话少说,马上开始我们今天的学习之旅。

2、MDK安装

1、首先打开Keil安装包(资料链接中附有MDK 5.28版本安装包),打开后如图1.1所示。

“
图1

2、随后点击“Next”,进入后续的安装流程,勾选红圈选项后,点击“Next”下一步。随后会提示MDK的安装路径,强烈建议采用默认路径。如果要自定义安装路径,请保证自定义的安装路径中不要出现任何中文名字!点击“Next”下一步后,输入下姓名、公司、邮箱等信息,随意填写下即可,继续点击“Next”便开始正式安装。期间软件会自动安装仿真器驱动,耐心等待即可。

“敏矽微电子Cortex-M0开发环境的建立及调试"

3、等到最后出现下面的界面,软件便已安装成功。

“敏矽微电子Cortex-M0开发环境的建立及调试"

3、MDK注册

1、启动Keil 5,在File选项中选择License Management子选项。

“敏矽微电子Cortex-M0开发环境的建立及调试"

2、按照提示进行注册即可,请按照官方正版途径注册。

3、如果点击Add LIC提示以下的错误信息,看红色下划线的信息,得知是因为没有在管理员模式下操作,权限不足导致的。这个是Windows管理员模式产生的问题。如果你的注册过程没有此错误提示,可以跳过第7步。解决的办法也很简单,按照步骤4操作即可。

“敏矽微电子Cortex-M0开发环境的建立及调试"

4、以管理员模式运行Keil 5,鼠标右键Keil 图标,点击“以管理员身份运行”即可。随后重新按照步骤5操作即可。管理员运行方法如图所示:

“敏矽微电子Cortex-M0开发环境的建立及调试"

5、软件注册完成后,出现“LIC Added Sucessfully”的提示,说明注册成功。

4、安装ME32F030 PACK包

1、KEIL安装完成后,就需要安装芯片支持的PACK包,来让KEIL支持我们的芯片,这里建议安装资料提供的Keil 5版本,因为其对应的PACK包是傻瓜式一键安装,操作十分的方便,找到我们的PACK包双击安装即可。

“敏矽微电子Cortex-M0开发环境的建立及调试"

2、PACK包安装完成后,我们可以先确认下,看下KEIL是否已经识别并支持我们的芯片,方法如下,首先创建个新工程,选择project->New uVision Project来建立工程。

“敏矽微电子Cortex-M0开发环境的建立及调试"

3、新建工程时会提示选择芯片类型,从图中看出KEIL已经支持我们的Mesilicon系列芯片。

“敏矽微电子Cortex-M0开发环境的建立及调试"

5、新建工程

1、选择project->New uVision Project来建立工程。

“敏矽微电子Cortex-M0开发环境的建立及调试"

2、选择芯片类型,选择开发板的芯片为Mesilicon->ME32F030 Series->ME32F030C8x6,选择好后,点击“OK”。

“敏矽微电子Cortex-M0开发环境的建立及调试"

3、出现下面的界面,这个是根据需求自己添加开发组件,不多介绍,直接点取消跳过。

“敏矽微电子Cortex-M0开发环境的建立及调试"

4、接下来将资料中的Lib2.3 for keil5x例程解压缩出来,其中公用的.c和.h等文件都在common文件夹内,随后开始向工程中添加.c和.h文件。点击如图所示的快捷按钮。

“敏矽微电子Cortex-M0开发环境的建立及调试"

5、弹出如下界面,在Groups右边有4个按钮,依次为“新建”、“删除”、“上移”、“下移”功能,先选择新建comm、app两个组,你也可以尝试下删除组,把初始自带的Source Group通过红叉按钮删除掉。

“敏矽微电子Cortex-M0开发环境的建立及调试"

向每个Group中添加.程序c文件。那就先举个简单的例子作为开始,首先我们选中需要添加程序的组,比如我们向app组里添加需要的main.c文件,选中app组后,点击右侧的Files框体下的Add Files,选中要添加的main.c文件。点击Add便完成添加。

“敏矽微电子Cortex-M0开发环境的建立及调试"

添加成功后的效果如下图所示,右侧的Files中已包含main.c文件,那么想要删除的话,可以在选中文件后,通过点击上方的红叉进行删除。

“敏矽微电子Cortex-M0开发环境的建立及调试"

依次类推,我们接下来要向comm中添加.c文件。

①、添加core_cm0.c,这个就是我们的单片机的M0内核文件,它在Lib2.3 for keil 5->common->CoreSupport文件夹中。

“敏矽微电子Cortex-M0开发环境的建立及调试"

②、添加system_CMSDK.c,它在Lib2.3 for keil 5->common->DeviceSupport->arm->cmsdk文件夹中。

“敏矽微电子Cortex-M0开发环境的建立及调试"

③、添加startup_CMSDK_CM0.s文件,这个是启动程序文件,它是由汇编语言写成的。是以.s为结尾的文件,所以在添加它的时候需要注意将文件类型选择为All Files才能看见它。

Lib2.3 for keil 5->common->DeviceSupport->arm->cmsdk->Startup->arm文件夹中。

“敏矽微电子Cortex-M0开发环境的建立及调试"

“敏矽微电子Cortex-M0开发环境的建立及调试"

④、前面添加都属于单片机的系统文件,接下来就要开始添加我们自己的.c文件了,这个都在Lib2.3 for keil 5->common->Drivers->Source文件夹内。

“敏矽微电子Cortex-M0开发环境的建立及调试"

⑤、第一次新建工程时可以参照现有的例程,比如以Demo-Touch Me按键触摸试验为模板,尝试建立一下工程。添加自己所需要的文件,如图所示:

“敏矽微电子Cortex-M0开发环境的建立及调试"

6、添加完成后,关闭Manage Project Items功能栏。返回KEIL主界面后,在左侧的Project工程栏里,可以看到之前添加的所有程序文件。

“敏矽微电子Cortex-M0开发环境的建立及调试"

7、接下来我们是不是可以编译程序了呢?那不妨先试一下。编译后发现提示很多此类的报错,提示 cannot open source input file "gpio.h": No such file or directory,这是因为我们只添加了.c文件,而需要的头文件路径还没有指定位置。那么接下来就指定头文件路径。

“敏矽微电子Cortex-M0开发环境的建立及调试"

点击红圈标注的Options快捷按钮,也可以通过快捷键ALT + F7来打开。

“敏矽微电子Cortex-M0开发环境的建立及调试"

打开后选中C/C++选项卡,在下面可以看到Include Paths栏,这个就是需要指定的头文件路径,点击右边的 。。。按钮来进行添加。

“敏矽微电子Cortex-M0开发环境的建立及调试"

点击。。。按钮后,通过弹出的对话框来添加头文件路径。同上文讲到的一样,红圈的四个按钮依次为“新建”、“删除”、“上移”、“下移”功能。

“敏矽微电子Cortex-M0开发环境的建立及调试"

那就新建路径吧,点击新建后会生成一个新的路径框,点击红圈标识的。。。来添加。

“敏矽微电子Cortex-M0开发环境的建立及调试"

以core_cm0.c文件对应的头文件core_cm0.h为例子,一路进到上文中添加core_cm0.c的文件夹中,进入如图所示的路径后,点击选择文件夹。

“敏矽微电子Cortex-M0开发环境的建立及调试"

添加完成后,刚才新添加的路径便显示出来了。

“敏矽微电子Cortex-M0开发环境的建立及调试"

依次类推,再添加以下3个路径。

Lib2.3 for keil 5->common->DeviceSupport->arm->cmsdk
Lib2.3 for keil 5->common->Drivers->Include
Lib2.3 for keil 5->Demo-Touch Me->myapp->include

这里添加的时候要注意,是要选择.h文件所在的那个文件夹,错选成它的上级或下级文件夹,是无法找到需要的头文件的!添加完成后的效果如下。

“敏矽微电子Cortex-M0开发环境的建立及调试"

这时候我们再去编译一下试试。没有报错也没有警告,说明项目工程顺利建立。

“敏矽微电子Cortex-M0开发环境的建立及调试"

6、下载与调试

1、程序编译没问题后,接下来就可以下载程序并仿真测试了。在开始前先插上仿真器并连接开发板,打开Options for Target选项卡,选中Debug。这个时候选择仿真器类型(根据实际进行选择,建议买一个U-LINK2仿真器),勾选上Run to main,这样程序下载后直接运行到main函数,否则会先运行startup_CMSDK_M0.s中Reset_Handler程序。虽然这段程序最后也会跳转到我们的main函数,但我们没有必要每次去仿真它。点击Settings查看我们的仿真器配置情况。

“敏矽微电子Cortex-M0开发环境的建立及调试"

点击Settings后,在Debug子选项中看到下面的信息。则说明仿真器识别正常。

“敏矽微电子Cortex-M0开发环境的建立及调试"

再点击查看下Flash Download的选项。这时候看到ME20F030单片机的FLash的下载地址和RAM空间地址都已经明确了,这就是为什么前面强烈推荐安装KEIL 5版本,随后打上PACK包,很多设置项都是PACK包整合配置好的,我们直接用就可以了。

“敏矽微电子Cortex-M0开发环境的建立及调试"

2、仿真选项设置好之后,开始下载程序。点击工具栏上的

“敏矽微电子Cortex-M0开发环境的建立及调试"

图标来下载并仿真程序。

“敏矽微电子Cortex-M0开发环境的建立及调试"

图标所示的功能也可以下载程序,但是它是不带仿真功能的,这点需要注意!下载成功后会多出下面的工具条。

“敏矽微电子Cortex-M0开发环境的建立及调试"

下面我们分别来介绍一下这些仿真调试按钮的功能:

1:复位,点击后程序会从头开始重新运行。

2:全速运行,点击后程序便开始全速运行,运行到断点处会停止,或者使用停着功能。

3:停止,当程序在运行状态下,使用此功能,程序便会停止运行。

4:执行进去,本质是单步运行,如果下一步是要执行的是个函数,那么就行进入到函数 里面,进行单步仿真。

5:段执行,也是单步运行,但不同的是,如果下一步是要执行的是个函数,那么会直接运行整个函数,并不会进入函数内部运行,它是直接以一整段代码为单位进行执行的。

6:执行跳去,当不需要再继续在某个函数里继续单步仿真时,执行此功能,就会直接执行完函数内剩余的代码,随后跳出该函数后会暂停,等待下一步操作。

7:执行到光标处,使用此功能前,先确定想运行到地方,鼠标单击运行的那一行,此时光标便会在这一行显示,这时候再点击此按钮,程序会全速运行,直到在光标处停止。

8:全速运行,此功能是让程序全速运行,除非认为暂停或者遇到断点,否则程序会一直运行。

来源:敏矽MCU
免责声明:本文为转载文章,转载此文目的在于传递更多信息,版权归原作者所有。本文所用视频、图片、文字如涉及作品版权问题,请联系小编进行处理(联系邮箱:cathy@eetrend.com)。

围观 700

为了使单独编译的C语言程序和汇编程序之间能够相互调用,必须为子程序之间的调用规定一定的规则,ATPCS就是ARM程序和THUMB程序中子程序调用的基本规则。

01、ATPCS

ATPCS即ARM Thumb Procedure Call Standard(ARM-Thumb过程调用标准)的简称,ATPCS规定了一些调用和被调用程序之间调用的基本规则,这些基本规则包括子程序调用过程中寄存器的使用规则、数据栈的使用规则、参数的传递规则。为适应一些特定的需要,对这些基本的调用规则进行一些修改得到几种不同的子程序调用规则,这些特定的调用规则包括:

  • 支持数据栈限制检查的ATPCS
  • 支持只读段位置无关的ATPCS
  • 支持可读写段位置无关的ATPCS
  • 支持ARM程序和THUMB程序混合使用的ATPCS

有调用关系的所有子程序必须遵守同一种ATPCS,编译器或者汇编器在ELF格式的目标文件中设置相应的属性,标识用户选定的ATPCS类型。对应不同类型的ATPCS规则,有相应的C语言库,连接器根据用户指定的ATPCS类型连接相应的C语言库。

使用ADS的C语言编译器编译的C语言子程序满足用户指定的ATPCS类型。而对于汇编语言程序来说,完全要依赖用户来保证各子程序满足选定的ATPCS类型。具体来说,汇编语言子程序必须满足下面三个条件:在子程序编写时必须遵守相应的ATPCS规则;数据栈的使用要遵守ATPCS规则;在汇编编译器中使用“--apcs”选项,使用“--apcs”选项并不影响代码的产生,编译器只是在各段中放置相应的属性,标识用户选定的属性。

02、ATPCS基本规则

基本ATPCS规定了在子程序调用时的一些基本规则,包括以下四个方面的内容:

  • 各寄存器的使用规则及其相应的名字
  • 数据栈的使用规则
  • 参数传递的规则
  • 函数结果返回的规则

相对于其他类型的TPCS,满足基本ATPCS的程序的执行速度更快,所占用的内存更少。但是它不能提供以下的支持:ARM程序和THUMB程序相互调用;数据以及代码的位置无关的支持;子程序的可重入性;数据栈检查的支持。而派生的其他几种特定的ATPCS就是在基本ATPCS的基础上再添加其他的规则而形成的 ,其目的就是提供上述的功能。

2.1 寄存器的使用规则

“Cortex-M0中断控制和系统控制(七)"

前四个寄存器R0~R3用于将参数值传递到例程中并将结果值传递出例程,并在例程中保存中间值(但通常仅在子例程调用之间),子程序通过寄存器R0~R3来传递参数,这时寄存器可以记作:A1~A4,被调用的子程序在返回前无需恢复寄存器R0~R3的内容。

在子程序中,使用R4~R11来保存局部变量,这时寄存器R4~R11可以记作:V1~V8 。如果在子程序中使用到V1~V8的某些寄存器,子程序进入时必须保存这些寄存器的值,在返回前必须恢复这些寄存器的值,对于子程序中没有用到的寄存器则不必执行这些操作。在THUMB程序中,通常只能使用寄存器R4~R7来保存局部变量。

寄存器R12用作子程序间暂存寄存器,记作IP;在子程序的连接代码段中经常会有这种使用规则。

寄存器R13用作数据栈指针,记做SP,在子程序中寄存器R13不能用做其他用途。寄存器SP在进入子程序时的值和退出子程序时的值必须相等。

寄存器R14用作连接寄存器,记作LR;它用于保存子程序的返回地址,如果在子程序中保存了返回地址,则R14可用作其它的用途。

寄存器R15是程序计数器,记作PC;它不能用作其它用途。

ATPCS中的各寄存器在ARM编译器和汇编器中都是预定义的。

2.2 数据栈的使用规则

栈指针通常可以指向不同的位置,当栈指针指向栈顶元素(即最后一个入栈的数据元素)时,称为Full栈。当栈指针指向与栈顶元素相邻的一个元素时,称为Empty栈。数据栈的增长方向也可以不同,当数据栈向内存减小的地址方向增长时,称为Descending栈。当数据栈向着内存地址增加的方向增长时,称为Ascending栈。

综合这两种特点可以由以下4种数据栈:

FD(FULL Descending):递增满栈
ED(Empty Descending):递增空栈
FA(FULL Ascending):递减满栈
EA(Empty Ascending):递减空栈 

ATPCS规定数据栈为FD类型,并对数据栈的操作是8字节对齐的,下面是一个数据栈的示例及相关的名词:

1、数据栈栈指针,stack pointer指向最后一个写入栈的数据的内存地址。

2、数据栈的基地址,stack base是指数据栈的最高地址。由于ATPCS中的数据栈是FD类型的,实际上数据栈中最早入栈数据占据的内存单元是基地址的下一个内存单元。

3、数据栈界限,stack limit是指数据栈中可以使用的最低的内存单元地址。

4、已占用的数据栈,used stack是指数据栈的基地址和数据栈栈指针之间的区域,其中包括数据栈栈指针对应的内存单元。

5、数据栈中的数据帧(stack frames) 是指在数据栈中,为子程序分配的用来保存寄存器和局部变量的区域。

VAL(SP) <= stack base, 
VAL(SP) >= VAL(SL) >= stack limit + 256, 
VAL(LR) = return address

异常中断的处理程序可以使用被中断程序的数据栈,这时用户要保证中断的程序数据栈足够大。使用ADS编译器产生的目标代码中包含了DRFAT2格式的数据帧。在调试过程中,调试器可以使用这些数据帧来查看数据栈中的相关信息。而对于汇编语言来说,用户必须使用FRAME伪操作来描述数据栈中的数据帧。ARM汇编器根据这些伪操作在目标文件中产生相应的DRFAT2格式的数据帧。

在ARMv5TE中,批量传送指令LDRD/STRD要求数据栈是8字节对齐的,以提高数据的传送速度。用ADS编译器产生的目标文件中,外部接口的数据栈都是8字节对齐的,并且编译器将告诉连接器:本目标文件中的数据栈是8字节对齐的。而对于汇编程序来说,如果目标文件中包含了外部调用,则必须满足以下条件:外部接口的数据栈一定是8位对齐的,也就是要保证在进入该汇编代码后,直到该汇编程序调用外部代码之间,数据栈的栈指针变化为偶数个字;在汇编程序中使用PRESERVE8伪操作告诉连接器,本汇编程序是8字节对齐的。

“Cortex-M0中断控制和系统控制(七)"

2.3 参数的传递规则

根据参数个数是否固定,可以将子程序分为参数个数固定的子程序和参数个数可变的子程序。这两种子程序的参数传递规则是不同的。

参数个数可变的子程序参数传递规则,对于参数个数可变的子程序,当参数不超过4个时,可以使用寄存器R0~R3来进行参数传递,当参数超过4个时,还可以使用数据栈来传递参数。在参数传递时,将所有参数看做是存放在连续的内存单元中的字数据。然后,依次将各名字数据传送到寄存器R0,R1,R2,R3;如果参数多于4个,将剩余的字数据传送到数据栈中,入栈的顺序与参数顺序相反,即最后一个字数据先入栈。按照上面的规则,一个浮点数参数可以通过寄存器传递,也可以通过数据栈传递,也可能一半通过寄存器传递,另一半通过数据栈传递。

参数个数固定的子程序参数传递规则,对于参数个数固定的子程序,参数传递与参数个数可变的子程序参数传递规则不同,如果系统包含浮点运算的硬件部件,浮点参数将按照下面的规则传递:各个浮点参数按顺序处理;为每个浮点参数分配FP寄存器;分配的方法是,满足该浮点参数需要的且编号最小的一组连续的FP寄存器。第一个整数参数通过寄存器R0~R3来传递,其他参数通过数据栈传递。

2.4 子程序结果返回规则

1、结果为一个32位的整数时或小于32位的整数值以保留符号和符号的方式扩展为32位值的范围,可以通过寄存器R0返回。

2、结果为一个64位整数时,一个64位整数值被视为两个32位整数值,可以通过R0和R1返回,依此类推。

3、对于位数更多的结果,需要通过调用内存来传递,任何其它类型的值(例如结构化值)将转换为32位整数字序列,通过将其复制到连续的内存字中。

“Cortex-M0中断控制和系统控制(七)"

根据上面简单的测试可以看出:

MOVS     r2,#0x03
MOVS     r1,#0x02
MOVS     r0,#0x01

通过寄存器R0~R3来传递参数:

ADDS     r0,r3,r1
ADDS     r0,r0,r2

通过寄存器R0返回。

03、特定的ATPCS

3.1 支持数据栈限制检查的ATPCS

如果在程序设计期间能够准确地计算出程序所需的内存总量,就不需要进行数据栈的检查,但是在通常情况下这是很难做到的,这时需要进行数据栈的检查。在进行数据栈的检查时,使用寄存器R10作为数据栈限制指针,这时寄存器R10又记作SL。用户在程序中不能控制该寄存器。具体来说,支持数据栈限制的ATPCS要满足下面的规则:在已经占有的栈的最低地址和SL之间必须有256字节的空间,也就是说,SL所指的内存地址必须比已经占用的栈的最低地址低256个字节。当中断处理程序可以使用用户的数据栈时,在已经占用的栈的最低地址和SL之间除了必须保留的256个字节的内存单元外,还必须为中断处理预留足够的内存空间;用户在程序中不能修改SL的值;数据栈栈指针SP的值必须不小于SL的值。

与支持数据栈限制检查的ATPCS相关的编译/汇编选项有下面几种:选项./ swst (编译过程中对输入文件使用堆栈检测)指示编译器生成的代码遵守支持数据栈限制检查的ATPCS,用户在程序设计期间不能够准确计算程序所需的数据栈大小时,需要指定该选项;选项./ noswst (编译过程中对输入文件不使用堆栈检测,这是编译器默认选项)指示编译器生成的代码不支持数据栈限制检查的功能,用户在程序设计期间能够准确计算出程序所需的数据栈大小,可以指定该选项;选项./ swst如果汇编程序对于是否进行数据栈检查无所谓,而与该汇编程序连接的其他程序指定了选项./ swst。

对于256字节或更少的帧,可以按如下方式检查:

CMP sp, sl
BHS no_ovf
BL |__16__rt_stkovf_split_small|
no_ovf

对于大于 256 字节的帧,可以如下方式检查:

LDR wr, framesize
ADD wr, sp
CMP wr, sl
BHS no_ovf
BL |__16__rt_stkovf_split_big|
no_ovf
MOV sp,wr
; ...
ALIGN
Framesize
DCD –Framesize

3.2 编写遵守支持数据栈限制检查的ATPCS的汇编语言程序

对于C程序和C++程序来说,如果在编译时指定了选项./swst,生成的目标代码将遵守支持数据栈限制检查的ATPCS。对于汇编语言程序来说,如果要遵守支持数据栈限制检查的ATPCS,用户在编写程序时必须满足支持数据栈限制检查的ATPCS所要求的规则,然后指定选项./swst,下面介绍用户编写汇编语言程序时的一些要求。

“Cortex-M0中断控制和系统控制(七)"

3.3 叶子子程序是指不调用别的程序的子程序

数据栈小于256字节的叶子子程序不许要进行数据栈检查,如果几个子程序组合起来构成的叶子子程序数据栈也小于256字节,这个规则同样适用;数据栈小于256字节的非叶子子程序可以使用下面的代码段来进行数据栈检查。

ARM程序使用:

SUB sp,sp,#size ;      #size 为sp和sl之间必须保留的空间大小
CMP sp,sl;
BLLO _ARM_stack_overflow

THUMB程序使用:

ADD sp,#-size ;         #size为sp和sl之间必须保留的空间大小
CMP sp,sl;
BLLO _THUMB_stack_overflow

数据栈大于256字节的子程序,为了保证SP的值不小于数据栈可用的内存单元最小的地址值,需要引入相应的寄存器。在使用超过 256 字节堆栈空间的例程中检查溢出更为复杂,不能简单地从SP减去帧大小。

在这种情况下,必须使用如下序列向限制检查代码建议SP的新值:

ARM程序使用下列代码:

SUB ip,sp,#size;
CMP ip,sl;
BLLO _ARM_stack_overflow

THUMB程序使用下列代码:

LDR wr,#-size;
ADD wr,sp;
CMP wr,sl;
BLLO _THUMB_stack_overflow

在编译或汇编时,/interwork (指定输入文件符合ARM/Thumb交互标准)告诉编译器或汇编器生成的目标代码遵守支持ARM-THUMB的ATPCS,它用在以下场合:

  • 程序中存在ARM程序调用THUMB程序的情况
  • 程序中存在THUMB程序调用ARM程序的情况
  • 需要连接器来进行ARM状态和THUMB状态切换的情况

在下述情况下使用选项/nointerwork:程序中不包含THUMB程序;用户自己进行ARM程序和THUMB程序切换。需要注意的是:在同一个C/C++程序中不能同时有ARM指令和THUMB指令。

“Cortex-M0中断控制和系统控制(七)"

__asm 关键字用于调用内联汇编程序,可以用在C或C++源码中内嵌汇编语言,如下所示:

“Cortex-M0中断控制和系统控制(七)"

ATPCS规则就是定义了函数传参以及返回数据的标准,定义了寄存器在函数调用时的作用。

本篇文章也是结合网上的资料和官方的资料加上自己的理解进行解读,可能不是很全面,详细的内容大家有兴趣可以自行查阅官方文档《The ARM-THUMB Procedure Call Standard》和《ARM Software Development Toolkit Version 2.50》。

来源:灵动MM32MCU
免责声明:本文为转载文章,转载此文目的在于传递更多信息,版权归原作者所有。本文所用视频、图片、文字如涉及作品版权问题,请联系小编进行处理(联系邮箱:cathy@eetrend.com)。

围观 44

Arm处理器是基于精简指令集计算机(RISC)原理设计的,指令集和相关译码机制较为简单,具有32位Arm指令集和16位Thumb指令集,Arm指令集效率高,但是代码密度低,而Thumb指令集具有更好的代码密度,却仍然保持Arm的大多数性能上的优势,它是Arm指令集的子集。所有Arm指令都是可以有条件执行的,而Thumb指令仅有一条指令具备条件执行功能。Arm程序和Thumb程序可相互调用,相互之间的状态切换开销几乎为零。

Cortex-M0处理器基于ARMv6-M架构,是一款功耗和性能较为均衡的处理器。Cortex-M0只支持56条指令的小指令集,其中大部分指令是16位指令。

Arm Cortex-M 指令集对比:

“”

01、指令集

1.1 在处理器内移动数据

MOV  <Rd>, <Rm> ;Rm and Rn can be high or low registers.
MOVS <Rd>, <Rm>
MOVS <Rd>, #immed8 ;8位立即数值
MRS  <Rd>, <SpecialReg>
MSR  <SpecialReg>, <Rd>

1.2 存储器访问

确保访问的内存地址是对齐的,这一点很重要。在ARMv6-M架构(包括Cortex-M0和Cortex-M0处理器)上不支持非对齐传输。任何未对齐内存访问的尝试都会导致HardFault异常。

LDR  <Rt>,[<Rn>, <Rm>]    ; Rt = memory[Rn + Rm]
STR  <Rt>,[<Rn>, <Rm>]    ; memory[Rn + Rm] = Rt
LDRH <Rt>,[<Rn>, <Rm>]    ; Rt = memory[Rn + Rm]
STRH <Rt>,[<Rn>, <Rm>]    ; memory[Rn + Rm] = Rt
LDRB <Rt>,[<Rn>, <Rm>]    ; Rt = memory[Rn + Rm]
STRB <Rt>,[<Rn>, <Rm>]    ; memory[Rn + Rm] = Rt
LDRSH <Rt>,[<Rn>, <Rm>]   ; Rt = SignExtend(memory[Rn + Rm])
LDRSB <Rt>,[<Rn>, <Rm>]   ; Rt = SignExtend(memory[Rn + Rm])
LDR  <Rt>,[<Rn>, #immed5] ; Rt = memory[Rn + ZeroExtend (#immed5<<2)]
STR  <Rt>,[<Rn>, #immed5] ; memory[Rn + ZeroExtend(#immed5<<2)] = Rt
LDRH <Rt>,[<Rn>, #immed5] ; Rt = memory[Rn + ZeroExtend (#immed5<<1)]
STRH <Rt>,[<Rn>, #immed5] ; memory[Rn + ZeroExtend(#immed5<<1)] = Rt
LDRB <Rt>,[<Rn>, #immed5] ; Rt = memory[Rn + ZeroExtend (#immed5)]
STRB <Rt>,[<Rn>, #immed5] ; memory[Rn + ZeroExtend(#immed5)] = Rt
LDR  <Rt>,[SP, #immed8]   ; Rt = memory[SP + ZeroExtend(#immed8<<2)]
STR  <Rt>,[SP, #immed8]   ; memory[SP + ZeroExtend(#immed8<<2)] = Rt
LDR  <Rt>,[PC, #immed8]   ; Rt =memory[WordAligned(PC+4)+ZeroExtend(#immed8<<2)]
LDR  <Rd>, =immed32       ; pseudo instruction translated to LDR <Rt>,[PC, #immed8]
LDR  <Rd>, label          ; pseudo instruction translated to LDR <Rt>,[PC, #immed8]
LDM <Rn>,{<Ra>, <Rb>,..} ; Load Multiple
// Ra = memory[Rn]
// Rb = memory[Rn + 4],
// ...
LDMIA <Rn>!, {<Ra>, <Rb>,..} ; Load Multiple Increment After
LDMFD <Rn>!, {<Ra>, <Rb>,..}
// Ra = memory[Rn],
// Rb = memory[Rn + 4],
// ...
// and then update Rn to last read address plus 4.
STMIA <Rn>!, {<Ra>, <Rb>,..} ; Store Multiple Increment After
STMEA <Rn>!, {<Ra>, <Rb>,..}
// memory[Rn] = Ra,
// memory[Rn + 4] = Rb,
// ...
// and then update Rn to last store address plus 4.

1.3 栈空间访问

PUSH {<Ra>, <Rb>, ..}
PUSH {<Ra>, <Rb>, .., LR}
POP  {<Ra>, <Rb>, ..}
POP  {<Ra>, <Rb>, .., PC}

1.4 算数运算

ADD  <Rd>, <Rm>          ; Rd = Rd + Rm. Rd, Rm can be high or low registers.
ADDS <Rd>, <Rn>, <Rm>    ; Rd = Rn + Rm
SUBS <Rd>, <Rn>, <Rm>    ; Rd = Rn – Rm
ADDS <Rd>, <Rn>, #immed3 ; Rd = Rn + ZeroExtend(#immed3)
SUBS <Rd>, <Rn>, #immed3 ; Rd = Rn – ZeroExtend(#immed3)
ADDS <Rd>, #immed8       ; Rd = Rd + ZeroExtend(#immed8)
SUBS <Rd>, #immed8       ; Rd = Rd – ZeroExtend(#immed8)
ADCS <Rd>, <Rd>, <Rm>    ; Rd = Rd + Rm + Carry
SBCS <Rd>, <Rd>, <Rm>    ; Rd = Rd – Rm – Borrow
ADD  SP, SP, #immed7     ; SP = SP + ZeroExtend(#immed7<<2)
SUB  SP, SP, #immed7     ; SP = SP – ZeroExtend(#immed7<<2)
ADD  SP, <Rm>            ; SP = SP + Rm. Rm can be high or low register.
ADD  <Rd>, SP, <Rd>      ; Rd = Rd + SP. Rd can be high or low register.
ADD  <Rd>, SP, #immed8   ; Rd = SP + ZeroExtend(#immed8<<2)
ADD  <Rd>, PC, #immed8   ; Rd = (PC[31:2]<<2) + ZeroExtend(#immed8<<2)
ADR  <Rd>, <label>       ; pseudo instruction translated to ADD <Rd>, PC, #immed8
RSBS <Rd>, <Rn>,#0       ; Rd = 0 – Rm, Reverse Subtract (negative)
MULS <Rd>, <Rm>, <Rd>    ; Rd = Rd * Rm
CMP <Rn>, #immed8        ; Rd – ZeroExtended(#immed8)
CMP <Rn>, <Rm>           ; Rn – Rm
CMN <Rn>, <Rm>           ; Rn – NEG(Rm)

1.5 逻辑运算

ANDS , ,  ; Rd = AND(Rd, Rm)ANDS <Rd>, <Rd>, <Rm> ; Rd = AND(Rd, Rm)
ORRS <Rd>, <Rd>, <Rm> ; Rd = OR(Rd, Rm)
EORS <Rd>, <Rd>, <Rm> ; Rd = XOR(Rd, Rm)
BICS <Rd>, <Rd>, <Rm> ; Rd = AND(Rd, NOT(Rm))
MVNS <Rd>, <Rm>       ; Rd = NOT(Rm)
TST  <Rn>, <Rm>       ; AND(Rn, Rm)

1.6 移位和循环操作

ASRS <Rd>, <Rm>, #immed5 ; Rd = Rm>>immed5
LSLS <Rd>, <Rm>, #immed5 ; Rd = Rm<<#immed5
LSRS <Rd>, <Rm>, #immed5 ; Rd = Rm>>#immed5
ASRS <Rd>, <Rd>, <Rm>    ; Rd = Rd>>Rm
LSLS <Rd>, <Rd>, <Rm>    ; Rd = Rd<<Rm
LSRS <Rd>, <Rd>, <Rm>    ; Rd = Rd>>Rm
RORS <Rd>, <Rd>, <Rm>    ; Rd = Rd rotate right by Rm bits
// Rotate_Left(Data, offset) = Rotate_Right(Data, (32-offset))

1.7 展开和顺序反转操作

这些反向指令通常用于在小端和之间转换数据大整数。

REV <Rd>, <Rm> ; Byte-Reverse Word
// Rd = {Rm[7:0], Rm[15:8], Rm[23:16], Rm[31:24]}
REV16 <Rd>, <Rm> ; Byte-Reverse Packed Half Word
// Rd = {Rm[23:16], Rm[31:24], Rm[7:0] , Rm[15:8]}
REVSH <Rd>, <Rm> ; Byte-Reverse Signed Half Word
// Rd = SignExtend({Rm[7:0] , Rm[15:8]})

1.8 扩展操作

它们通常用于数据类型转换。

SXTB <Rd>, <Rm> ; Signed Extended Byte
// Rd = SignExtend(Rm[7:0])
SXTH <Rd>, <Rm> ; Signed Extended Half Word
// Rd = SignExtend(Rm[15:0])
UXTB <Rd>, <Rm> ; Unsigned Extended Byte
// Rd = ZeroExtend(Rm[7:0])
UXTH <Rd>, <Rm> ; Unsigned Extended Half Word
// Rd = ZeroExtend(Rm[15:0])

1.9 程序流控制

B <label>       ; Branch, Branch range is ±2046 bytes of current PC
B<cond> <label> ; Conditional Branch, Branch range is ±254 bytes of current PC
BL <label>      ; Branch and Link, Branch range is ±16 MB of current PC
BX <Rm>         ; Branch and Exchange
BLX <Rm>        ; Branch and Link with Exchange

条件转移指令B

“Cortex-M0中断控制和系统控制(六)"

1.10 内存屏障指令

在Cortex-M0和Cortex-M0处理器上支持内存屏障指令,从而在Cortex-M处理器和其他ARM处理器家族中提供更好的兼容性。

//数据内存屏障,确保所有内存访问都完成

//在新的内存访问被提交之前。

DMB

//数据同步屏障,确保所有的内存访问都完成

//在执行下一条指令之前。

DSB

//指令同步障碍,刷新管道和

//确保之前所有的指令都已完成

//在执行新指令之前。

ISB

1.11 异常相关指令

SVC <immed8> ; Supervisor call
CPSIE I      ; Enable Interrupt (Clearing PRIMASK)
CPSID I      ; Disable Interrupt (Setting PRIMASK)

1.12 睡眠模式功能相关说明

//等待中断,停止程序执行,直到一个中断到达,

//如果处理器进入调试状态。

WFI

//等待事件,如果设置了内部事件寄存器,则清除

//内部事件注册和继续执行。

//停止程序执行,直到事件(如中断)到达

//如果处理器进入调试状态。

WFE

//发送事件,设置本地事件寄存器并发送一个事件脉冲

//多处理器系统中的其他微处理器。

SEV

1.13 其他说明

NOP           ; No Operation
BKPT <immed8> ; Break point
YIELD         ; Execute as NOP on the Cortex-M0 processor

02、指令说明

2.1 可访问high registers的指令

绝大部分指令只能访问low registers,也就是只能访问R0~R7寄存器。可以访问high registers的指令只有两条,这两条指令都不更新APSR,指令没有S后缀。

MOV  <Rd>, <Rm> ; Rm and Rn can be high or low registers.
ADD  <Rd>, <Rm> ; Rd = Rd + Rm. Rd, Rm can be high or low registers.

其它两条和SP加法有关的可以访问high registers的指令其本质是ADD指令。

ADD  SP, <Rm>        
ADD  <Rd>, SP, <Rd>  

2.2 分配临时变量的指令

函数内的临时变量分配到堆栈,进入函数给临时变量分配空间时使用SUB指令。

SUB  SP, SP, #immed7     ; SP = SP – ZeroExtend(#immed7<<2)

退出函数释放临时变量空间时使用ADD指令。

ADD  SP, SP, #immed7     ; SP = SP + ZeroExtend(#immed7<<2)

上面两条指令的立即数只有7位,最多可以增减SP指针127个字空间,如果超过127个字,使用这条指令:

ADD  SP, <Rm>            ; SP = SP + Rm. Rm can be high or low register.

只有ADD指令,没有SUB指令,如果需要SUB,那么给Rm赋值负数即可。

2.3 取临时变量地址的指令

在堆栈分配了临时变量空间后,总要取得临时变量的地址才能做进一步的操作。

ADD  <Rd>, SP, #immed8   ; Rd = SP + ZeroExtend(#immed8<<2)

立即数不够,可以用寄存器。

ADD  <Rd>, SP, <Rd>      ; Rd = Rd + SP. Rd can be high or low register.

2.4 RSBS指令

RSBS <Rd>, <Rn>, #0       ; Rd = 0 – Rm, Reverse Subtract (negative)

这是倒过来的减法,常量减去寄存器值,而且常量只能是0。所以这条指令实质上就是一条取负数指令。

Rd = 0 - Rm

等价于:Rd = -Rm

Rd 寄存器值等于负的 Rm 寄存器值。

来源:灵动MM32MCU
免责声明:本文为转载文章,转载此文目的在于传递更多信息,版权归原作者所有。本文所用视频、图片、文字如涉及作品版权问题,请联系小编进行处理(联系邮箱:cathy@eetrend.com)。

围观 156

OS能够支持多任务,能够以周期性地完成上下文的切换,以并行的架构处理任务,单一任务的崩溃并不会牵连到整个系统。上下文周期性切换需要一个定时器能够打断程序执行,SysTick定时器就可以提供必要的时钟节拍,为OS的任务调度提供一个有节奏的“心跳”。

SysTick定时器即系统滴答定时器,也称“心跳定时器”,它是一个24 位的倒计数定时器,计到0 时,将从重装载寄存器中自动重装载定时初值。只要不把它在SysTick 控制及状态寄存器中的使能位清除,就永不停息,即使在睡眠模式下也能工作。

SysTick定时器被捆绑在NVIC中,用于产生SysTick异常(异常号:15),SysTick中断的优先级也可以设置。

它会根据节拍来工作,把整个时间段分成很多小小的时间片,而每个任务每次只能运行一个时间片的时间长度,超时就退出给别的任务运行,这样可以确保任何一个任务都不会霸占操作系统提供的各种定时功能,都与这个滴答定时器有关。

当启用时,定时器从重载值递减计数到零,在下一个时钟周期将重装载SYST_RVR 中的值,然后在后续时钟周期递减。将零值写入 SYST_RVR 会在下一次回调时禁用计数器。当计数器变为零时,COUNTFLAG 状态位设置为 1。读取 SYST_CSR 将 COUNTFLAG 位清除为 0。写入 SYST_CVR 将清除寄存器和 COUNTFLAG 状态位为 0,写入不会触发 SysTick 异常逻辑,读取该寄存器会在访问时返回其值。

01、SysTick寄存器

SysTick定时器主要由4个寄存器组成:

“Cortex-M0中断控制和系统控制(五)"

在CMSIS中系统控制寄存器结构体:

typedef struct
{
  __IO uint32_t CTRL; /*!< Offset: 0x00  SysTick Control and Status Register */
  __IO uint32_t LOAD; /*!< Offset: 0x04  SysTick Reload Value Register       */
  __IO uint32_t VAL;  /*!< Offset: 0x08  SysTick Current Value Register      */
  __I  uint32_t CALIB;/*!< Offset: 0x0C  SysTick Calibration Register        */
} SysTick_Type;

在Arm官方资料中4个寄存器的命名分别是SYST_CSR、SYST_RVR、SYST_CVR和SYST_CALIB,但是在CMSIS中进行了简化命名,更加清晰明了。

1.1、SysTick控制和状态寄存器(SYST_CSR)

“Cortex-M0中断控制和系统控制(五)"

CSR寄存器用到的位有4个,bit0用于是否开启定时器,置1表示使能SysTick定时器;bit1用于控制是否产生中断,该位置为1为允许产生中断;bit2用于设置定时器的时钟源,设为1,定时器的时钟源为主时钟,反之设为0的话定时器的时钟源为主时钟的四分之一。

MM32F0130系列的SysTick的HCLK来源于AHB总线经过硬件4分频,FCLK直接来源于AHB时钟总线。

“Cortex-M0中断控制和系统控制(五)"

当 SysTick 定时器从1计到0时,它将把COUNTFLAG位置位;

而下述方法可以清零:

读取 SysTick 控制及状态寄存器(STCSR)

往 SysTick 当前值寄存器(STCVR)中写任何数据

1.2、SysTick重装载寄存器(SYST_RVR)

“Cortex-M0中断控制和系统控制(五)"

RVR寄存器用到0~23位,这个值是定时器倒计时的初始值,打开定时器以后,就会从这里设置的值倒计时到0,倒计时到0以后,又会从此值开始倒计时。

1.3、SysTick当前值寄存器(SYST_CVR)

“Cortex-M0中断控制和系统控制(五)"

CVR寄存器也是用到0~23位,这是一个状态寄存器,当定时器开始运作的时候,这个值在不断的变化,从RVR寄存器获取初值以后,倒计时到0。

CURRENT:读此寄存器返回系统定时器的当前值,给这个寄存器赋值,将使定时器归0,且清CTRL中的COUNTFLAG位。

1.4、SysTick当前值寄存器(SYST_CALIB)

“Cortex-M0中断控制和系统控制(五)"

如果不知道校准信息,则根据处理器时钟或外部时钟的频率计算所需的校准值。

校准值寄存器提供了这样一个解决方案:它使系统即使在不同的CM0产品上运行,也能产生恒定的SysTick中断频率。最简单的作法就是:直接把TENMS的值写入重装载寄存器,这样一来,只要没突破系统极限,就能做到每10ms来一次 SysTick异常。如果需要其它的SysTick异常周期,则可以根据TENMS的值加以比例计算。

在系统定时器的四个寄存器中,SYST_CALIB为校准寄存器,这个是在出厂之前就已经配置好了的,我们不必考虑这个寄存器。

02、SysTick编程

SysTick配置需要遵循一定的流程:

1、 开始

2、 禁止SysTick

3、 设置重装值寄存器

4、 清除当前值寄存器

5、 使能SysTick

6、 完成

SysTick操作相关函数有:

__STATIC_INLINE uint32_t SysTick_Config(uint32_t ticks);
void SysTick_CLKSourceConfig(u32 systick_clk_source);
void RCC_SystickDisable(void);
void RCC_SystickEnable(u32 sys_tick_period);

下面的代码演示启用 SysTick 的基本程序:

; 使能SysTick定时器,并且使能SysTick异常
LDR R0, =0xE000E010    ; 加载STCSR的地址
MOV R1, #0
STR R1, [R0]           ; 先停止SysTick,以防意外产生异常请求
LDR R1, =0x3FF         ; 让SysTick每1024周期计完一次。因为是从1023数到
                       ; 0,总共数了1024个周期,所以加载值为0x3FF
STR R1, [R0,#4]        ; 写入重装载的值
STR R1, [R0,#8]        ; 往STCVR中写任意的数,以确保清除COUNTFLAG标志
MOV R1, #0x7           ; 选择FCLK作为时钟源,并使能SysTick及其异常请求
STR R1, [R0]           ; 写入数值,开启定时器

在CMSIS库中有定义对应的配置函数:

__STATIC_INLINE uint32_t SysTick_Config(uint32_t ticks)
{
/* Reload value impossible */
    if ((ticks - 1UL) > SysTick_LOAD_RELOAD_Msk) {
        return (1UL);                              
    }
/* set reload register */
    SysTick->LOAD  = (uint32_t)(ticks - 1UL);   
/* set Priority for Systick Interrupt */
    NVIC_SetPriority (SysTick_IRQn, (1UL << __NVIC_PRIO_BITS) - 1UL); 
/* Load the SysTick Counter Value */
    SysTick->VAL   = 0UL;   
/* Enable SysTick IRQ and SysTick Timer */                       
    SysTick->CTRL  = SysTick_CTRL_CLKSOURCE_Msk |
                     SysTick_CTRL_TICKINT_Msk   |
                     SysTick_CTRL_ENABLE_Msk;   
/* Function successful */
    return (0UL);                          
}

SysTick可以通过轮询或者中断方式进行操作,使用轮询的程序可以读取SysTick控制和状态寄存器,检查COUNTFLAG,如果该位置位,则表明SysTick计数已减到0。

中断方式延时参考程序:

static __IO uint32_t TimingDelay;
void Delay(__IO uint32_t nTime)
{ 
     TimingDelay = nTime;
     while(TimingDelay != 0);
}

void SysTick_Handler(void)
{
     if (TimingDelay != 0x00) 
     { 
       TimingDelay--;
     }
}

int main(void)
{ 
     //systick时钟为HCLK,中断时间间隔1ms
     if (SysTick_Config(SystemCoreClock / 1000)) 
     {
         while (1);
     }
     while(1)
     { 
         Delay(200);//200ms
     }
}

轮询方式延时参考程序:

void delay_init()
{
    SysTick_CLKSourceConfig(SysTick_CLKSource_HCLK_Div4);   //选择外部时钟HCLK/4
    //为系统时钟的1/4,实际上也就是在计算1usSysTick的VAL减的数目
    fac_us=SystemCoreClock/4000000; 
    //代表每个ms需要的systick时钟数,即每毫秒SysTick的VAL减的数目
    fac_ms=(u16)fac_us*1000;        
} 

void delay_ms(u16 nms)
{                  
    u32 temp;          
    SysTick->LOAD=(u32)nms*fac_ms;          //时间加载(SysTick->LOAD为24bit)
    SysTick->VAL =0x00;           //清空计数器
    SysTick->CTRL|=SysTick_CTRL_ENABLE_Msk ;          //开始倒数  
    do
    {
        temp=SysTick->CTRL;
     }while((temp&0x01)&&!(temp&(1<<16)));   //等待时间到达,看CTRL的第16位(COUNTFLAG)是否为1,看STRL的第0位(ENABLE)是否为1   
     SysTick->CTRL&=~SysTick_CTRL_ENABLE_Msk;       //关闭计数器
     SysTick->VAL =0X00;       //清空计数器        
}

void delay_us(u32 nus)
{        
    u32 temp;            
    SysTick->LOAD=nus*fac_us;               //时间加载           
    SysTick->VAL=0x00;        //清空计数器
    SysTick->CTRL|=SysTick_CTRL_ENABLE_Msk ;  //开始倒数      
    do
    {
        temp=SysTick->CTRL;
    }while((temp&0x01)&&!(temp&(1<<16)));        //等待时间到达   
    SysTick->CTRL&=~SysTick_CTRL_ENABLE_Msk;  //关闭计数器
    SysTick->VAL =0X00;       //清空计数器    
}

SysTick定时器除了能服务于操作系统之外,还能用于其它目的:如时间测量、定时或者闹铃等。

来源:灵动MM32MCU
免责声明:本文为转载文章,转载此文目的在于传递更多信息,版权归原作者所有。本文所用视频、图片、文字如涉及作品版权问题,请联系小编进行处理(联系邮箱:cathy@eetrend.com)。

围观 131

Cortex-M0系统控制块(SCB)是内核外设的主要模块之一,提供系统控制以及系统执行信息,包括配置,控制,上报系统异常等。

“

为了提高软件效率,CMSIS简化了SCB寄存器表示,在CMSIS中系统控制寄存器结构体:

typedef struct 
{
    __IM  uint32_t CPUID; /*!< Offset: 0x000 (R/ )  CPUID Base Register */
    __IOM uint32_t ICSR;  /*!< Offset: 0x004 (R/W)  Interrupt Control and State Register */
    uint32_t RESERVED0;
    __IOM uint32_t AIRCR; /*!< Offset: 0x00C (R/W)  Application Interrupt and Reset Control Register */
    __IOM uint32_t SCR; /*!< Offset: 0x010 (R/W)  System Control Register */
    __IOM uint32_t CCR; /*!< Offset:0x014 (R/W) Configuration Control Register */
    uint32_t RESERVED1;
    __IOM uint32_t SHP[2U];/*!< Offset: 0x01C (R/W)  System Handlers Priority Registers. [0] is RESERVED */
    __IOM uint32_t SHCSR;  /*!< Offset: 0x024 (R/W)  System Handler Control and State Register */
} SCB_Type;

01、CPUID

CPUID基地址寄存器包含处理器型号、版本等相关信息,是只读的,可以通过应用软件、调试器和烧录器等获取处理器的类型和版本信息。

Address: 0xE000ED00

Reset value: 0x410CC200

“

这个地方CPUID与我们经常提到的MCU的96位UID不同,CPUID是处理器的ID号,由Arm提供并实现,通过CPUID可以知道内核型号及版本等信息。而96位UID是MCU产品ID,属于MM32,由上海灵动微电子股份有限公司提供并按照一定的规则实现,96位的产品唯一身份标识所提供的参考号码对任意一个系列微控制器,在任何情况下都是唯一的。用户在何种情况下,都不能修改这个身份标识。

MCU还有一个DEV_ID编码,这个ID定义了MCU的器件号和硅片版本号,它是DBG_MCU的一个组成部分,并且映射到外部APB总线上。SW 调试口(2个引脚) 或通过用户代码都可以访问此编码。

DEV_ID地址:0x40013400 只支持32位访问,只读。

“

在CMSIS驱动库中,可以直接使用“ SCB->CPUID ” 获取处理器ID。读取MM32F0130的CPUID、UID和DEV_ID如下所示:

“

CPUID (0x410CC200)解析处理器信息:

“

02、ICSR(Interrupt Control and State Register)

提供:

  • NMI异常的设置挂起位
  • 为PendSV和SysTick异常设置挂起和清除挂起位

表示:

  • 正在处理的异常的异常编号
  • 是否有被抢占的活动异常
  • 最高优先级未决异常的异常编号
  • 是否有任何中断待处理

Address: 0xE000ED04

Reset value: 0x0000 0000

“

ICSR中的某些控制位仅供调试使用,大多数情况下,应用程序只会用ICSR来控制或者检查系统异常挂起状态。

PendSV(可挂起的系统调用)异常对 OS 操作非常重要,其优先级可以通过编程设置。可以通过将中断控制和壮态寄存器 ICSR 的 bit28挂起位置1来触发PendSV中断。与SVC异常不同,它是不精确的,因此它的挂起状态可在更高优先级异常处理内设置,且会在高优先级处理完成后执行。利用该特性,若将PendSV设置为最低的异常优先级,可以让PendSV异常处理在所有其他中断处理完成后执行,这对于上下文切换非常有用,也是各种OS设计中的关键。在具有嵌入式OS的典型系统中,处理时间被划分为了多个时间片。

通过向中断控制和状态寄存器 ICSR 的 bit28 写入1挂起PendSV来启动PendSV中断,如果中断启用且有编写 PendSV 异常服务函数的话,则内核会响应 PendSV 异常,去执行PendSV 异常服务函数,这样就可以在PendSV中断服务函数中进行任务切换了。

03、AIRCR(Application Interrupt and Reset Control Register)

AIRCR为数据访问和系统的复位控制提供字节序状态。

要写入该寄存器,您必须写入0x05FA VECTKEY 字段,否则处理器将忽略写入。

Address: 0xE000ED0C

Reset value: 0xFA05 0000

“

任何对该寄存器的写操作,都必须将0x05FA写入到AIRCR[30:16],否则写操作将无效,若需要半字读取,需要写入0xFA05。

应用程序中系统执行软复位函数:

__STATIC_INLINE void NVIC_SystemReset(void)
{
    __DSB();  //确保所有未完成的内存访问包括缓冲写入在重置之前完成
    SCB->AIRCR  = ((0x5FAUL << SCB_AIRCR_VECTKEY_Pos) |
                   SCB_AIRCR_SYSRESETREQ_Msk);
    __DSB();    //确保完成内存访问

    for(;;) /* wait until reset */
    {   
        __NOP();
    }
}

04、SCR(System Control Register)

SCR 控制进入和退出低功耗状态的特性。

Address: 0xE000ED10

Reset value: 0x0000 0000

“

SCR(系统控制)寄存器可以选择使用立即休眠还是退出时休眠,当SCR寄存器的SLEEPONEXIT位为0的时候使用立即休眠,当为 1 的时候使用退出时休眠。

MM32F0130执行进入stop模式实现函数:

void PWR_EnterSTOPMode(u32 regulator, u8 stop_entry)
{
    PWR->CR |= regulator;

    SCB->SCR |= SCB_SCR_SLEEPDEEP_Msk;

    (stop_entry == PWR_STOPEntry_WFI) ? __WFI() : __WFE();
}

05、CCR(Configuration and Control Register)

CCR 是只读寄存器,指示 Cortex-M0 处理器行为的某些方面。

Address: 0xE000ED14

Reset value: 0x0000 0204

“Cortex-M0中断控制和系统控制(四)"

SCB->CCR寄存器控制除数为零和未对齐内存访问是否触发用法HardFault。

06、SHPR(System Handler Priority Registers)

因为在ARMv7-M架构上才有SHPR1,所以 Cortex – M0使用系统优先级寄存器只有SHPR2和SHPR3,而没有SHPR1。

SHPR2-SHPR3 寄存器设置具有可配置优先级的异常处理程序的优先级级别,从0到192。

SHPR2-SHPR3 是可字访问的。

要使用 CMSIS 访问系统异常优先级,可以使用CMSIS函数:

uint32_t NVIC_GetPriority(IRQn_Type IRQn)
void NVIC_SetPriority(IRQn_Type IRQn, uint32_t priority)

系统故障处理程序,以及每个处理程序的优先级字段和寄存器是:

“

6.1、SHPR2 (System Handler Priority Register 2)

Address: 0xE000ED1C

Reset value: 0x0000 0000

“

6.2、SHPR3 (System Handler Priority Register 3)

如果您的设备未实现 SysTick 计时器,则此字段为保留字段

Address: 0xE000 ED20

Reset value: 0x0000 0000

“

SVCall、PendSV和SysTick可编程的中断多用于在操作系统之上的软件开发中。SVC用于产生系统函数的调用请求,例如,操作系统不让用户程序直接访问硬件,而是通过提供一些系统服务函数,用户程序使用SVC发出对系统服务函数的呼叫请求,以这种方法调用它们来间接访问硬件。因此,当用户程序想要控制特定的硬件时,它就会产生一个SVC异常,然后操作系统提供的SVC异常服务例程得到执行,它再调用相关的操作系统函数,后者完成用户程序请求的服务。

另一个相关的异常是 PendSV,它和 SVC 协同使用。一方面, SVC异常是必须立即得到响应的(若因优先级不比当前正处理的高, 或是其它原因使之无法立即响应, 将上访成Hardfault),应用程序执行 SVC 时都是希望所需的请求立即得到响应。另一方面,PendSV则不同,它是可以像普通的中断一样被挂起的(不像SVC那样会上访)。OS可以利用它“延期执行”一个异常——直到其它重要的任务完成后才执行动作。挂起 PendSV 的方法是:程序中往NVIC的PendSV挂起寄存器中写 1。挂起后, 如果优先级不够高,则将延期等待执行。PendSV 的典型使用场合是在上下文切换时(在不同任务之间切换)。有两个就绪的任务,上下文切换被触发的场合可以是:

  • 执行一个系统调用
  • 系统滴答定时器(SysTick)中断,(轮转调度中需要)

ARMv7-M和ARMv6-M都有的SCB寄存器名称相同,但是ARMv7-M寄存器数量和有效控制bit位比ARMv6-M丰富了不少。一般来说,ARMv6-M向上兼容ARMv7-M,这意味着应用层和系统为ARMv6-M开发的级别软件无需修改即可在 ARMv7-M 上执行,这也是Arm从一个平台切换到另一个平台可以很快速的完成切换的原因,有兴趣深入研究可以参考《ARMv6-M Architecture Reference Manual》白皮书。

来源:灵动MM32MCU
免责声明:本文为转载文章,转载此文目的在于传递更多信息,版权归原作者所有。本文所用视频、图片、文字如涉及作品版权问题,请联系小编进行处理(联系邮箱:cathy@eetrend.com)。

围观 394

Cortex-M0处理器允许两种形式的中断请求:电平触发和脉冲输入。

电平触发是外设的中断请求有持续的电平信号,若电平信号在处理器从ISR返回之前没有被取消,则中断返回后将再次触发已经服务过的中断。

脉冲中断是在信号的上升沿同步采样的中断信号,为了确保NVIC检测到中断,外设必须维持中断信号至少一个时钟周期,在此期间,NVIC检测脉冲和锁存中断。后续的脉冲可以将暂挂状态加到活动中断中,使状态为中断活动且挂起。然而,在有限周期内发生的多个脉冲只登记作为中断调度的单个事件。

哪些中断是电平触发的,哪些是脉冲触发的,具体选择哪一种是根据芯片设计来决定,不过大多数的外设使用电平触发中断输出。

01、电平触发和脉冲输入

Cortex-M0处理器锁存所有中断,外围中断成为等待其中一个原因是:

  • NVIC检测到中断信号被置位并且对应的中断不是active
  • NVIC检测到中断信号的上升沿
  • 软件写入相应的中断集挂起寄存器位

挂起的中断将一直挂起,直到下列情况之一发生:

处理器为中断进入ISR,这将改变中断的状态等待活跃:

  • 对于电平触发型中断,当处理器从ISR返回时,NVIC采样中断信号。如果中断信号仍然有效,表示中断的状态更改为pending,这可能会导致处理器立即重新进入ISR。否则,中断的状态将变为非活动状态。
  • 对于脉冲触发型中断,NVIC继续监视中断信号,如果这样触发时,中断状态变为挂起和活动状态。在这种情况下,当处理器从ISR返回中断状态时,中断状态变为挂起状态可能会导致处理器立即重新进入ISR。如果中断信号不是脉冲而处理器是在ISR,当处理器从ISR返回中断状态变为非活动状态。

软件写入相应的中断清除寄存器位。

对于电平触发型中断,如果中断信号仍然有效,则中断的状态中断不会改变。否则,中断的状态将变为非活动状态。

对于脉冲中断,中断状态变为:

  • inactive,如果状态是pending
  • active,如果状态是活动的和挂起的

02、中断处理

当中断事件发生时,由于外设连接到了NVIC上,中断信号就会得到确认。在处理器执行中断服务并且没有清除外设的中断的信号以前,该信号会保持高电平。在NVIC内部,当检测到有中断发生时,该中断的挂起状态会被置位,当处理器接受该中断并且开始执行中断服务后,挂起状态就会被清除。

“Cortex-M0中断控制和系统控制(三)"

针对脉冲输入的中断请求,这种情况下,在中断得到服务之前,挂起状态寄存器将会一直保持该请求。

“Cortex-M0中断控制和系统控制(三)"

如果中断请求没有立即执行,并且在确认之前被软件清除了,处理器将会忽略掉本次请求,并且不会执行中断处理。

如果在软件清除挂起状态时,外设仍然保持着中断请求,挂起状态寄存器还会立即生成。

03、中断等待

通常情况下,处理器的中断等待时间为16个周期,这个等待时间从中断确认的处理器时钟周期开始,一直到中断处理开始执行结束。

计算中断等待需具备以下前提:

  • 该中断使能并没有PRIMASK或者其他正在执行的异常处理所屏蔽
  • 存储器系统没有任何等待状态,在中断处理、压栈、取向量表或者中断处理开始时取指都会用到总线传输,如果存储器系统需要等待,那么总线传输时产生的等待状态则可能使得中断延迟。

下面几种情况可能会导致不同的中断等待:

  • 中断的咬尾连锁,如果一个中断返回时立即产生另外一个中断请求,处理器就会跳过出栈和压栈时间,减少了中断等待时间。
  • 延迟到达,如果中断发生时,另外一个低优先级中断正在进行压栈处理,由于延迟到达,高优先级的中断就会立即执行,这样会导致高优先级的中断等待时间减少。

04、异常屏蔽寄存器PRIMASK

有些对时间敏感的应用,需要在短时间内禁止响应所有的中断,对于这种应用,处理器不是直接使用中断使能、禁止控制寄存器来禁止所有中断再恢复,而是一个单独的特殊寄存器 - PRIMASK,通过它可以屏蔽掉除了NMI和HardFault异常的其他的所有的中断和系统异常。

PRIMASK寄存器只有1位有效,并且在复位后默认为0。该寄存器为0时,所有的中断和异常都处于允许状态,设置为1后,只有NMI和HardFault处于使能状态。

MOVS R0, #0x1         ;  //中断#2
MSR PRIMASK , R0       ;  //将R0的值送到PRIMASK

NVIC编程提示软件使用CPSIE i和CPSID i指令来启用和禁用中断。

CPSIE i                 ;  //清除 PRIMASK(使能中断)
CPSID i                 ;  //设置 PRIMASK(不响应中断)

CMSIS设备驱动库提供了C语言的实现函数,用户可以直接使用函数来设置和清除PRIMASK寄存器:

void __disable_irq(void) //不响应中断
void __enable_irq(void)  //启用中断

在对时间敏感的程序完成后,应该清除PRIMASK。要不然即使在中断处理中使用__disable_irq()函数,处理器将停止接受新的中断请求。主要原因是PRIMASK寄存器和Xpsr是相互独立的,因此异常返回不会影响中断屏蔽状态。

05、NVIC使用提示

确保软件使用正确对齐的寄存器访问,处理器不支持对 NVIC 寄存器的未对齐访问。

即使中断被禁用,它也可以进入挂起状态。

禁用中断只能防止处理器处理中断。

在对中断向量表重定义之前,必须包含所有的异常中断,例如 NMI、HardFault 和外设中断等。

来源: 灵动MM32MCU
免责声明:本文为转载文章,转载此文目的在于传递更多信息,版权归原作者所有。本文所用视频、图片、文字如涉及作品版权问题,请联系小编进行处理(联系邮箱:cathy@eetrend.com)。

围观 57

在前几天有客户问了一个问题:如果外部中断来的频率足够快,上一个中断没有处理完成,新来的中断该如何处理?

在研究了arm的官方手册后,了解中断有使能、清除或挂起等实现方式,今天分享给大家。

中断一般是由硬件(例如外设、外部引脚)产生,当某种内部或外部事件发生时,MCU 的中断系统将迫使 CPU 暂停正在执行的程序,转而去进行中断事件的处理,中断处理完毕后,又返回被中断的程序处,继续执行下去,所有的Cortex-M 内核系统都有一个用于中断处理的组件NVIC,主要负责处理中断,还处理其他需要服务的事件。嵌套向量式中断控制器(NVIC: Nested Vectored Interrupt Controller)集成在Cortex-M0处理器里,它与处理器内核紧密相连,并且提供了中断控制功能以及对系统异常的支持。

处理器中的NVIC能够处理多个可屏蔽中断通道和可编程优先级,中断输入请求可以是电平触发,也可以是最小的一个时钟周期的脉冲信号。每一个外部中断线都可以独立的使能、清除或挂起,并且挂起状态也可以手动地设置和清除。

主程序正在执行,当遇到中断请求(Interrupt Request)时,暂停主程序的执行转而去执行中断服务例程(Interrupt Service Routine,ISR),称为响应,中断服务例程执行完毕后返回到主程序断点处并继续执行主程序。多个中断是可以进行嵌套的。正在执行的较低优先级中断可以被较高优先级的中断所打断,在执行完高级中断后返回到低级中断里继续执行,采用“咬尾中断”机制。

“Cortex-M0中断控制和系统控制(一)"

内核中断(异常管理和休眠模式等),其中断优先级则由SCB寄存器来管理,IRQ的中断优先级是由NVIC来管理。

NVIC的寄存器经过了存储器映射,其寄存器的起始地址为0xE000E100,对其访问必须是每次32bit。

SCB寄存器的起始地址:0xE000ED00,也是每次32bit访问,SCB寄存器主要包含SysTick操作、异常管理和休眠模式控制。

NVIC具有以下特性:

  • 灵活的中断管理:使能\清除、优先级配置
  • 硬件嵌套中断支持
  • 向量化的异常入口
  • 中断屏蔽

01、中断使能和清除使能

arm将处理器的中断使能设置和清除设置寄存器分在两个不同的地址,这种设计主要有如下优势:一方面这种方式减少了使能中断所需要的步骤,使能一个中断NVIC只需要访问一次,同时也减少了程序代码并且降低了执行时间,另一方面当多个应用程序进程同时访问寄存器或者在读写操作寄存器时有操作其他的中断使能位,这样就有可能导致寄存器丢失,设置和清除分成两个寄存器能够有效防止控制信号丢失。

“Cortex-M0中断控制和系统控制(一)"

因此我可以独立的操作每一个中断的使能和清除设置。

1.1、C代码

*(volatile unsigned long) (0xE000E100) = 0x4 ; //使能#2中断
*(volatile unsigned long) (0xE000E180) = 0x4 ; //清除#2中断

1.2、汇编代码

__asm void Interrupt_Enable()
{
    LDR R0, =0xE000E100  ;  //ISER寄存器的地址
    MOVS R1, #04         ;  //设置#2中断
    STR R1, [R0]         ;  //使能中断#2
}

__asm void Interrupt_Disable()
{
    LDR R0, =0xE000E180  ;  //ICER寄存器的地址
    MOVS R1, #04         ;  //设置#2中断
    STR R1, [R0]         ;  //使能中断#2
}

1.3、CMSIS标准设备驱动函数

//使能中断#IRQn
__STATIC_INLINE void __NVIC_EnableIRQ(IRQn_Type IRQn) 
{
    if ((int32_t)(IRQn) >= 0) {
        NVIC->ISER[0U] = (uint32_t)(1UL << (((uint32_t)(int32_t)IRQn) & 0x1FUL));
    }
}
//清除中断#IRQn
__STATIC_INLINE void __NVIC_DisableIRQ(IRQn_Type IRQn) 
{
    if ((int32_t)(IRQn) >= 0) {
        NVIC->ICER[0U] = (uint32_t)(1UL << (((uint32_t)(int32_t)IRQn) & 0x1FUL));
        __DSB();
        __ISB();
    }
}
//读取使能中断#IRQn
__STATIC_INLINE uint32_t __NVIC_GetEnableIRQ(IRQn_Type IRQn)
{
    if ((int32_t)(IRQn) >= 0) {
        return((uint32_t)(((NVIC->ISER[0U] & (1UL << (((uint32_t)(int32_t)IRQn) & 0x1FUL))) != 0UL) ? 1UL : 0UL));
    }
    else {
        return(0U);
    }
}

02、中断挂起和清除挂起

如果一个中断发生了,却无法立即处理,这个中断请求将会被挂起。挂起状态保存在一个寄存器中,如果处理器的当前优先级还没有降低到可以处理挂起的请求,并且没有手动清除挂起状态,该状态将会一直保持。

可以通过操作中断设置挂起和中断清除挂起两个独立的寄存器来访问或者修改中断挂起状态,中断挂起寄存器也是通过两个地址来实现设置和清除相关位。这使得每一个位都可以独立修改,并且无需担心在两个应用程序进程竞争访问时出现的数据丢失。

“Cortex-M0中断控制和系统控制(一)"

中断挂起状态寄存器允许使用软件来触发中断。如果中断已经使能并且没有被屏蔽掉,当前还没有更高优先级的中断在运行,这时中断的服务程序就会立即得以执行。

2.1、C代码

*(volatile unsigned long)(0xE000E100) = 0x4 ; //使能中断#2
*(volatile unsigned long)(0xE000E200) = 0x4 ; //挂起中断#2
*(volatile unsigned long)(0xE000E280) = 0x4 ; //清除中断#2的挂起状态

2.2、汇编代码

__asm void Interrupt_Set_Pending()
{
    LDR R0, =0xE000E100   ;  //设置使能中断寄存器地址
    MOVS R1, #0x4         ;  //中断#2
    STR R1, [R0]          ;  //使能#2中断
    LDR R0, =0xE000E200   ; //设置挂起中断寄存器地址
    MOVS R1, #0x4         ;  //中断#2
    STR R1, [R0]          ;  //挂起#2中断
}

__asm void Interrupt_Clear_Pending()
{
    LDR R0, =0xE000E100   ;  //设置使能中断寄存器地址
    MOVS R1, #0x4         ;  //中断#2
    STR R1, [R0]          ;  //使能#2中断
    LDR R0, =0xE000E280   ; //设置清除中断挂起寄存器地址
    MOVS R1, #0x4         ;  //中断#2
    STR R1, [R0]          ;  //清除#2的挂起状态
}

2.3、CMSIS标准设备驱动函数

//设置一个中断挂起
__STATIC_INLINE void __NVIC_SetPendingIRQ(IRQn_Type IRQn) 
{
    if ((int32_t)(IRQn) >= 0) {
        NVIC->ISPR[0U] = (uint32_t)(1UL << (((uint32_t)(int32_t)IRQn) & 0x1FUL));
    }
}

//清除中断挂起
__STATIC_INLINE void __NVIC_ClearPendingIRQ(IRQn_Type IRQn) 
{
    if ((int32_t)(IRQn) >= 0) {
        NVIC->ICPR[0U] = (uint32_t)(1UL << (((uint32_t)(int32_t)IRQn) & 0x1FUL));
    }
}

//读取中断挂起状态
__STATIC_INLINE uint32_t __NVIC_GetPendingIRQ(IRQn_Type IRQn) 
{
    if ((int32_t)(IRQn) >= 0) {
        return((uint32_t)(((NVIC->ISPR[0U] & (1UL << (((uint32_t)(int32_t)IRQn) & 0x1FUL))) != 0UL) ? 1UL : 0UL));
    }
    else {
        return(0U);
    }
}

NVIC属于处理器内核部分,因此在MM32 MCU芯片的用户手册中只有简单的提及,没有重点讲述,需要深入了解相关寄存器和功能需要参考《Cortex-M0技术参考手册》。

在下一章节中,我们将和大家一起学习中断优先级的实现方式。

来源:灵动MM32MCU
免责声明:本文为转载文章,转载此文目的在于传递更多信息,版权归原作者所有。本文所用视频、图片、文字如涉及作品版权问题,请联系小编进行处理(联系邮箱:cathy@eetrend.com)。

围观 247

NASA成功发射SpaceX CRS-10货运飞船进行第十次国际空间站商业货运任务。SpaceX CRS-10搭载了一个电子辐射效应实验,关键是该实验由美国Vorago公司的ARM架构抗辐射MCU进行控制。2016年,NASA宣布将选择ARM Cortex-A53处理器构建下一代空间电子产品平台,计划于2020年发射。可见,ARM技术在航天工业中势头越来越猛。此次搭载实验是由美国空军实验室和NASA联合支持,标志着基于ARM的耐极端环境抗辐射微处理器首次部署于空间系统。

抗辐射存储器实验

空间环境下,存储器暴露于高能质子和辐射粒子的辐射环境下,当这些粒子轰击存储器或其他微电路时将导致存储器位信息错误,这将导致电子设备故障或危及任务。

为解决这一问题,研究人员设计了抗辐射电子存储器实验——RHEME(监测空间存储器),RHEME将持续一年监测空间粒子辐射对存储器的影响。该实验采用了VORAGO公司基于ARM Cortex-M0的抗辐射微控制器进行控制。该微控制器采用VORAGO HARDSIL®工艺制造,抗辐射且耐受极端温度环境。实验结果将有助于空间用存储器的错误检测、纠正和消除。该实验是挑战空间环境、实现下一代空间计算的重要一步。

ARM芯片空间发展前景

一直以来,空间电子系统基于FPGA高度定制,且不重复利用。但随着太空探测的不断发展和运载火箭再次利用成为可能,电子系统需要变的可扩展、经济、可靠。而标准化的ARM架构正好具有这种灵活性。

该实验将改变空间抗辐射系统的设计方法,加快芯片的设计,允许基于MCU的电子设备集成于飞行器中的关键控制和安全功能系统,且成本更低。

VORAGO公司市场主管表示:空间计算应用,尤其是小型卫星平台的设计人员越来越倾向于选择抗辐射微控制器。ARM技术具有低功耗的特点,能够适应广泛的生态系统,且尺寸很小,这对于太阳能供电、受尺寸严格限制的飞行器而言非常重要。该实验将为未来芯片的发展提供关键数据。

这些辐射效应也可能发生在近地飞行的设备。例如,已开发了许多ARM CPU用于“关键任务”的功能安全应用。在这些应用中,需要检测和消除辐射效应,保证设备可控,这对于保护人们和地球环境至关重要。

围观 663

前几天刚好同事问起在Cortex-M上延时不准的问题,在网上也没找到比较满意的答案,干脆自己对这个问题做一个总结。

根据我们的经验,最容易想到的大概通过计算指令周期来解决。该思路在Cortex上并不是很适用:一方面MCU从Flash取指是有延时的,另一方面Cortex的指令集不是固定周期的,特别从M3加入分支预测后,分支指令在Cortex-M不同型号上的结果都不相同。因此除了指令周期外,我们需要考虑的东西还有很多,才能得到正确的结果。

不带分支预测器的情况

仍然先从不带分支预测器的Cortex-M0开始,通过计算指令周期延时的实现代码如下:

void delay_us(us) {
delay_ntimes((us * sysclk - 8) / 4);
}
__asm void delay_ntimes(unsigned int n)
{
L1
SUBS R0, #1
BCS L1
BX LR
}

从这段代码可发现两个主要问题:

一、delay_us里的公式是怎么来的:

假如想延时us微秒,系统时钟为48MHz,即sysclk=48,那么周期数period_count满足以下公式:

period_count = us * sysclk;

然后再delay_ntimes这个函数,又能推出period_count还满足以下公式(见第二个问题的分析):

period_count = 8 + 4 n

于是:

n = (us sysclk - 8 ) / 4;

这就解决了第一个问题,需要注意的是:该公式忽略了跳转到delay_us和(us * sysclk -8 )/4的几个固定周期。

二、delay_ntimes的周期数怎么算:

它的周期数满足以下公式:

period_count = 8 + 4 * n;

这个要根据指令集的周期数来确定,请看下表:

先考虑n为0的情况,

SUBS为1周期+BCS为1周期+BX为3周期+外层调用delay_times(相当于BLX指令)的3周期=8周期。
当n不为0时,将再执行n次SUBS和BCS执行,SUBS仍为1周期,BCS有跳转3周期,所以是4n个周期,因此该函数的执行周期数为:

period_count=8+4n;

好了,在了解了原理之后,是时候到真正的板子上去测试了。

然而在MCU上的实测结果却不如预期,延时5MS,实测为7.5MS;延时10MS,实测15MS。为什么会出现这样的现象?

这个跟MCU的设计有关。一般代码都放在FLASH上,MCU中Cortex核要从FLASH上先取出指令,然后才能将指令放到指令流水线上执行。而上面的分析忽略了Cortex核从FLASH取出指令的时间,因此实测值与理论值分析不一致。

不同的MCU从FLASH读取指令的时间消耗各不相同,因此需要根据不同MCU去调整公式,这是一个比较繁琐的过程,比如这款MCU,将公式修改为(us * sysclk - 8) / 6就得到了正确结果。

另外一个做法是不修改公式,将延时代码放到RAM中,许多MCU从RAM取出指令没有等待周期。使用该方法再次测试,延时结果与理论计算一致。

但值得注意的是,不是所有MCU都满足RAM取值零等待周期的条件,因此一定要做测试。

读者若对MCU如何从FLASH读取指令感兴趣,参考资料[4]的分析是比较清楚的。

带分支预测器的情况

将上面的代码放到Cortex-M3和Cortex-M4的芯片上测试,测试结果是错误的,不论在FLASH还是在RAM中,这个是由于Cortex-M3,Cortex-M4上的指令流水线带有分支预测器引起的。

要了解分支预测器,就不得不提指令流水线。Cortex-M3是三级流水线:取指,解码,执行。但是没找到CORTEX方面较好的图,以下讨论就基于下图的4级流水线,该图多了一步:写回。这并不影响我们的讨论。

(该图引用自参考资料[1])

假设一条指令从执行开始到执行结束需要4个时钟周期,在没有流水线的情况下,需要等待第一条指令执行结束,才能取第二条指令,这时两条指令就用了8个周期,效率是很低的。

引入4级流水线将指令拆成4个步骤:取指、解码、执行、写回。当第一条指令处于解码时,同时对第二条指令取指;对第一条指令执行时,同时对第二条指令解码,对第三条指令取指;对第一条指令写回时,同时对第二条执行,第三条解码,第四条取指;如此这般。最终达到的效果就如上图所示,只有第一条指令需要4个周期,其他后续的指令都只需要1个周期,极大地提高了处理效率。

流水线的高效率是基于指令顺序执行的前提,在执行跳转指令时,流水线将被清空,又回到了上图中的第一步,跳转后的第一条指令要执行仍然需要4周期。因此如果程序频繁跳转,流水线的作用就大打折扣。

为了解决这个问题,就引入了分支预测器:它会提前检测到跳转指令,并根据预判结果取指。如果预判结果是不跳转,就按顺序取下一条指令;如果预判结果是跳转,就从跳转的目的地址取下一条指令。假如预测对了,那么流水线就不会被清空,仍然可以一条指令1个周期;如果预测错了,下一条指令仍然要4周期。从这里看出,分支预测器对于提高流水线效率是有帮助的。值得一提的是,预判对了能减少指令延迟,但是否是零延迟取决于MCU的设计;预判错了清空流水线也未必是唯一的做法,同样取决于MCU的设计。

回到Cortex-M3的延时问题,网络上找到的资料提到分支预测器将延迟减小到1个周期,没有找到更详细的说明。那么理论上计算公式就应该调整为(us * sysclk - 8) / 3,在两款Cortex-M3和两款Cortex-M4上测试,测试结果与理论值一致。

微秒级精确延时的其他方法

对于Cortex而微秒级延时最通用的方法,大概便是通过比较SysTick的SYST_CVR寄存器来做延时,理论误差在1us内(基于48MHz主频)。以下为实现代码:

/*
* 使用SysTick的CVR实现微秒级精确延时,一般SysTick周期设置为10MS,因此该方法适用于10MS以内的延时
*/
void delay_us(int us) {
unsigned t1, t2, count, delta, sysclk;
sysclk = 48;//假设为48MHz主频

t1 = SYST_CVR;
while (1) {
t2 = SYST_CVR;
delta = t2 < t1 ? (t1 - t2) : (SYST_RVR - t2 + t1) ;
if (delta >= us * sysclk)
break;
}
}

其他补充点

1、本文假设在延时过程中没有产生任何中断,如果有中断产生,将影响延时精确性。

2、这部分的内容属于计算机体系结构。

3、以上测试时间范围在[0,10MS),该范围之外未详细测试,建议采用其他方法。

4、覆盖测试的MCU:1款Cortex-M0,2款Cortex-M3,2款Cortex-M4。

5、在我测试的两款Cortex-M3 MCU上,将代码都放RAM上,测试结果比放在FLASH差,而在Cortex-M4 MCU上,测试结果都一样,目前没有找到合理的解释。

参考资料

1、浅谈分支预测、流水线与条件转移

2、Cortex-M0指令集

3、CPU性能衡量参数-主频,MIPS,CPI,时钟周期,机器周期,指令周期

4、Cortex-M3的周期判断的依据是什么

5、计算机体系结构——流水线中的相关——延迟分支方法

文章来源:博客园

(直接点击图片可进入调查页面)

开发板测评图片
围观 812

作者:Founder_U

1. Cortex-M0 的两种总线协议:

1)AHB_Lite 系统总线协议:32位地址线,高速高性能访问(Flash, SRAM,总线桥,外部存储器接口)

2)APB 外设总线协议: 32位, 外设等较慢设备通讯(I/O,Timer, UART, Watch Dog。)

系统总线和外设总线是相互分离的,两种总线通过总线桥连接通讯,时钟频率控制不同,可能有多个外设总线段,并且每个段运行在不同的时钟频率下。有些高速外设是直接连接在AHB_Lite系统总线上的。

2. 存储器映射(4GB地址空间)

Cortex-M0(+)处理器的4G存储空间从架构上被分为多个区域。总的被分成8个大部分,每个部分512M。

Cortex-M0处理器架构定义的存储器映射

虽然映射已经被架构预先定义,但是实际分配却是很灵活的。

存储器设计的一个例子:

3. 程序存储器(Flash),Bootloader和存储器重映射

1)程序存储器一般使用片上Flash,不过也可以使用外部或其他类型存储器设备(EEPROM)

2)Flash存储器一般是从地址0开始,当Reset 后,会首先访问0地址的向量表,取得MSP的初始值和复位向量。

3)Bootloader 是位于芯片上的一小段加载引导程序,与用户应用程序是分开的。

4)存储器重映射:系统总线的一种存储器映射切换特性。Bootloader执行时会设置硬件寄存器来控制重映射。常见的处理方式是通过地址别名被重映射到地址的开头。

5)Bootloader其他特性:硬件初始化(时钟,PLL设置),多种启动配置,固件保护,Flash擦除工具。

6)SRAM重映射到地址0,code可以被复制到SRAM并以最快速度执行,可以避免取向量的等待时间。

4. 数据存储器(SRAM)

1) Cortex-M0数据存储:数据(全局变量、静态变量、数据结构)、栈存储(临时数据、局部变量、函数调用参数传递和异常处理的寄存器备份)和堆存储(C函数自动分配存储区域:alloc()、malloc()) 。

2)没有OS,就之后使用一个栈(MSP),数据存储分配如下:

SRAM常见数据存储使用示例

5. 支持小端和大端

1)Cortex-M0支持大端和小端两种存储,都支持字节(8bit)、半字(16bit)和字(32bit)传输, 存储器会根据传输大小和地址的最低两位选择数据链路。

2)大端模式:字节不变模式(BE8)和字不变模式(BE32), 字数据最低字节会保存在24到31位;

3)小端模式:字数据最低字节会保存在0到7位;

4) 多数情况下,连接到APB的外设应该用字传输,因为APB协议没有定义传输宽度信号,都被默认为字大小,因此,通过APB访问的外设寄存器通常被声明为”volatile unsigned integer“。

5)Thumb指令只能产生对齐访问(字对齐,半字对齐,字节对齐),传输地址只能是传输大小的整数倍。

6) C程序直接操作一个指针或用汇编,可进行非对齐传输。

6. 存储器属性

存储器访问属性:可执行(Executable),可缓冲(Bufferable),可缓存(Cacheable),可共享(Shareable)。

文章来源:极客头条

围观 556

页面

订阅 RSS - Cortex-M0