Cortex-M3

1.寄存器

Cortex-M3拥有R0~R15通用寄存器和一些特殊功能寄存器。

R0~R12这些通用寄存器,复位初始值都是不可预料的。

2.Cortex-M3有R0到R15的通用寄存器组

“”

注:绝大部分的16位thumb只能访问R0到R7,而32位thumb-2可以访问全部寄存器

3.特殊功能寄存器

“”

3.1程序状态寄存器组(应用程序PSR+中断号PSR+执行PSR)

3.2中断屏蔽寄存器组:用于控制异常的除能和使能

3.3控制寄存器:用于定义特权级别和当前使用哪个堆栈指针

4.操作模式和特权级别:

两种操作模式(处理器模式):Handler模式和线程模式(用于区分异常服务例程的代码和普通程序的代码)

两种特权等级:特权级和用户级(是指在硬件层面上对存储器访问权限的设置)

注:Cortex-M3在运行主程序(即线程模式)可以使用特权级别和用户级别;但是异常服务例程(即handler模式)只能使用特权级别。当处于线程+用户模式时一些访问权限将被禁止

将代码区分成用户级和特权级,有利于程序架构的稳定,如某一个用户代码出问题,不会使其成为害群之狗,因为用户级别的代码是禁止对一些要害寄存器操作的。

5.异常处理

5.1CONTROL[0]=0;

“”

5.2CONTROL[0]=1;

“”

CONTROL[0]只有在特权级别下可以访问,若在用户级别想访问先通过"系统服务呼叫指令(SVC)"来触发SVC异常,然后在该异常的服务例程中可以修改CONTROL[0]。

6.下面是各操作模式的转换

“”

7.异常和中断

可以有11个系统异常和最多240个外部中断(IRQ),具体芯片使用了多少要看芯片制造厂商。 

作为中断功能的强化,NVIC 还有一条NMI输入信号线,具体做什么由芯片制造商决定,NMI(not masked interrupted)

8.向量表:当一个异常被Cortex-M3内核接受。对应的异常Handler就会执行,向量表用来决定Handler的入口地址。

9.Cortex-M3的双堆栈:主堆栈(MSP)和进程堆栈(PSP)。是由CONTROL[1]控制的。

10.复位序列:

先从0X00地址取出MSP的值再从0x04地址取出PC的初始值,0X04处存的值是复位向量,而不是跳转指令。

“”

此处Cortex-M3与ARM及单片机不同。以前ARM都是从0X00地址开始执行第一条指令,一般第一条指令都是跳转指令

11.MSP及PC初始化的一个例程

“”

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

围观 543

1.寄存器

CM3拥有R0~R15通用寄存器和一些特殊功能寄存器

R0~R12这些通用寄存器,复位初始值都是不可预料的

2.CM3有R0到R15的通用寄存器组

Cortex-M3寄存器等基础知识

注:绝大部分的16位thumb只能访问R0到R7,而32位thumb-2可以访问全部寄存器

3.特殊功能寄存器

Cortex-M3寄存器等基础知识

3.1程序状态寄存器组(应用程序PSR+中断号PSR+执行PSR)

3.2中断屏蔽寄存器组:用于控制异常的除能和使能

3.3控制寄存器:用于定义特权级别和当前使用哪个堆栈指针

4.操作模式和特权级别:

两种操作模式(处理器模式):Handler模式和线程模式(用于区分异常服务例程的代码和普通程序的代码)

两种特权等级:特权级和用户级(是指在硬件层面上对存储器访问权限的设置)

注:CM3在运行主程序(即线程模式)可以使用特权级别和用户级别;但是异常服务例程(即handler模式)只能使用特权级别。当处于线程+用户模式时一些访问权限将被禁止

将代码区分成用户级和特权级,有利于程序架构的稳定,如某一个用户代码出问题,不会使其成为害群之狗,因为用户级别的代码是禁止对一些要害寄存器操作的。

5.异常处理

5.1CONTROL[0]=0;

Cortex-M3寄存器等基础知识

5.2CONTROL[0]=1;

Cortex-M3寄存器等基础知识

CONTROL[0]只有在特权级别下可以访问,若在用户级别想访问先通过"系统服务呼叫指令(SVC)"来触发SVC异常,然后在该异常的服务例程中可以修改CONTROL[0]。

6.下面是各操作模式的转换

Cortex-M3寄存器等基础知识

7.异常和中断

可以有11个系统异常和最多240个外部中断(IRQ),具体芯片使用了多少要看芯片制造厂商。 

作为中断功能的强化,NVIC 还有一条NMI输入信号线,具体做什么由芯片制造商决定,NMI(not masked interrupted)

8.向量表:当一个异常被CM3内核接受。对应的异常Handler就会执行,向量表用来决定Handler的入口地址。

9.CM3的双堆栈:主堆栈(MSP)和进程堆栈(PSP)。是由CONTROL[1]控制的。

10.复位序列:

先从0X00地址取出MSP的值再从0x04地址取出PC的初始值,0X04处存的值是复位向量,而不是跳转指令。

Cortex-M3寄存器等基础知识

此处CM3与ARM及单片机不同。以前ARM都是从0X00地址开始执行第一条指令,一般第一条指令都是跳转指令

11.MSP及PC初始化的一个例程

Cortex-M3寄存器等基础知识

转自:fx427103-博客园

围观 891

【工作模式】

线程模式(Thread mode):处理器复位或异常退出时为此模式。此模式下的代码可以是特权代码也可以是用户代码,通过CONTROL[0]控制。
处理模式(Handler mode):出现异常(包括中断)时进入此模式,此模式下所有代码为特权访问。

【代码限权】

特权访问:对处理器资源拥有完全访问限权;处理器复位后进入此访问模式;清零 CONTROL[0]进入用户模式。
用户访问:禁止访问多数系统寄存器。只能通过进入异常(中断)来返回特权模式。进入异常前是用户级访问,则退出异常时自动回到用户及,除非在异常中修改CONTROL[0]位。

【工作状态】

Thumb状态(正常执行指令状态)和调试状态;

【寄存器】

Cortex-M3的一些概念

r0-r12:通用寄存器,其中r8-r12只能被32位指令访问。
r13(SP):堆栈指针;线程模式时可以在线程堆栈和主堆栈之间切换,但处理模式只使用主堆栈。两个堆栈同一时刻只有一个可见,进入、退出异常时自动切换堆栈。
r14(LR):链接寄存器,保存子程序或异常的返回地址(要实现嵌套,必须入栈)。
r15(PC):程序计数器。
xPSR:特殊用途的程序状态寄存器。

【异常】

进入异常步骤:

1、处理器在当前堆栈上把xPSR、PC、LR、r12、r3~r0八个寄存器自动依次入栈。
2、读取向量表(如果是复位中断,更新SP值)
3、根据向量表更新PC值
4、加载新PC处的指令(2、3、4步与1步同时进行)
5、更新LR为EXC_RETURN(EXC_RETURN表示退出异常后返回的模式及使用的堆栈)。

退出异常步骤:

1、根据EXC_RETURN指示的堆栈,弹出进入中断时被压栈的8个寄存器。
2、从刚出栈的IPSR寄存器[8:0]位检测恢复到那个异常(此时为嵌套中断中),若为0则恢复到线程模式。
3、根据EXC_RETURN,选择使用相应SP。
末尾连锁(Tail-chaining):当前正在执行中断,又有一个中断到来且这个中断优先级比正在执行的中断优先级低(如果有其他被压栈的低优先级中断则要比这些中断优先级高),这个中断暂时被挂起,等到当前中断执行完后不再执行堆栈操作,而直接进入挂起的中断。
迟来:前一个中断还没有进入执行阶段(但处理器状态已经保存),后面来了一个高优先级中,则前一个中断被抢占,后来的高优先级中断不需要再保存寄存器状态。

【中断】

Cortex-M3中有两个优先级的概念——抢占式优先级和响应优先级,有人把响应优先级称作'亚优先级'或'副优先级',每个中断源都需要被指定这两种优先级。

具有高抢占式优先级的中断可以在具有低抢占式优先级的中断处理过程中被响应,即中断嵌套,或者说高抢占式优先级的中断可以嵌套低抢占式优先级的中断。

当两个中断源的抢占式优先级相同时,这两个中断将没有嵌套关系,当一个中断到来后,如果正在处理另一个中断,这个后到来的中断就要等到前一个中断处理完之后才能被处理。如果这两个中断同时到达,则中断控制器根据他们的响应优先级高低来决定先处理哪一个;如果他们的抢占式优先级和响应优先级都相等,则根据他们在中断表中的排位顺序决定先处理哪一个。

转自: uTank

围观 499

建筑能耗即建筑的运行能耗,就是人们日常用能,如采暖、空调、照明、炊事、洗衣等的能耗,是建筑能耗中的主导部分。随着城市化进程的加快和人民生活质量的改善,我国建筑能耗的总量逐年上升,建筑节能状况落后,浪费及其严重。因此建筑智能化刻不容缓,芯科科技(Silicon Labs)公司采用ARM Cortex-M3内核设计而来的EFM32是全球最低功耗的32位微控制器,对于用电池供电的温控器来说,大大延长电池的使用寿命,实现了节能环保。

节能设计架构与性能优异的外设

下面就是基于EFM32G2X MCU的智能温控器的原理框图,其利用各种传感器、无线Wi-Fi模块接收网络上的天气信息,以及通过网络传送的控制命令来达到控制功能。


图1:智能温控器框图

EFM32G系列MCU内核采用运算性能突出的Cortex-M3设计,极大地缩短了温控器应用中的算法处理时间,提高了系统的性能。它具有5大超低功耗模式,其中,停止模式能耗低至0.6µA/MHz,关闭模式仅为20nA/MHz。如此低功耗的MCU对于用电池供电的温控器来说,既实现了节能环保,又将大大延长电池的使用寿命。

另外,EFM32G系列MCU还具有低功耗性能优异的外设:
● 片上12bit的ADC在1Msps的速率下,功耗电流仅需350μA
● 模拟比较器工作仅需100nA
● LCD驱动 8×36段LCD显示,仅需0.55μA
● 全功能的LEUART,在9600bps的速率下,功耗电流仅需150nA
● AES执行128/256bit AES加/解密仅需54/75个时钟周期

综上可以看出, EFM32G系列MCU既可以缩短运算处理时间,又能在超低能耗模式下实现自主运行,还具有丰富的外围设备,非常适合低功耗要求的智能温控器。

电容触摸按键

EFM32G系列MCU特有的LESENSE模块,使得用户在外围硬件上仅用几个PCB上的铜箔即可实现触摸电容的功能,免除了机械按键长时间开关老化等问题。

LCD显示

EFM32G系列MCU内部自带了TFT的驱动,可以直接驱动TFT LCD,使智能温度控制器能更清晰地显示更多的客户定制化信息。

红外及温湿度传感器

温湿度传感器用来感知室内的温度和湿度,将测得的温度和湿度,发给MCU进行相应的处理,然后由MCU控制相应的设备对室内的温度和湿度进行相应的调节。

Si7020是Silicon labs公司推出的一款数字式温湿度一体传感器芯片。该芯片采用低K电介质聚合物检测温湿度的专利设计,具有低漂移和迟滞及卓越的长期稳定性。标准I2C数字输出,宽的工作电压范围,同时具有很高的温度精度(+/-0.3℃)和湿度精度(+/-2%RH),且体积仅为3*3*1mm,专为极低功耗、小体积应用设计。此外,还具有良好的品质、快的响应速度、抗干扰能力强、性价比高等优点。

红外传感器在智能温控器中的作用是用来感应是否有人在家。如传感器感测到长时间无人在家,然后将信息发给主控MCU,再由MCU控制实现自动关闭空调,从而实现降低能耗的问题。

Wi-Fi模块

无线Wi-Fi模块可以用来接收网络上的天气预报信息以及用户通过网络传送的控制命令来达到智能控制的目的。WGM110是Silicon Labs公司推出的一种功能全面的 Wi-Fi模块,具有卓越的射频性能,并可提供远程范围的稳定无线连接和安全性。

总结

在智能化建筑市场的迅速崛起中,采用节能型的技术技术、工艺、设备及材料来改善建筑物用能系统的运行管理越来越普遍。EFM32微控制器完美助力智能温控器,以其低功耗高性能的产品特性真正将建筑节能落实到了实处,同时还能使人们享受舒适的生活环境

原文地址:http://www.sekorm.com/news/4424.html

围观 465

摘要: 作为32位RISC 微处理器主流芯片, ARM 芯片得到长足发展和广泛应用。因而, ARM 芯片的测试需求更加强劲的同时,测试工作量在加大,测试复杂度也在增加.本文给出了基于ARMCo rte x-M3 的微处理器测试方法,该方法也可用于类似结构的微处理器测试。

作为32位RISC微处理器主流芯片,ARM芯片得到长足发展和广泛应用。因而,ARM芯片的测试需求更加强劲的同时,测试工作量在加大,测试复杂度也在增加。本文给出了基于ARM Cortex-M3的微处理器测试方法, 该方法也可用于类似结构的微处理器测试。

引言

随着半导体技术的发展,集成电路制程工艺从深亚微米发展到纳米级,晶体管集成度的大幅提高使得芯片复杂度增加,单个芯片的功能越来越强。二十世纪90年代ARM公司成立于英国剑桥,主要出售芯片设计技术的授权,采用ARM技术知识产权(I P 核)的微处理器, 即ARM微处理器, 已遍及工业控制、消费类电子产品、通信系统、网络系统、无线系统等各类产品市场,基于ARM技术的微处理器应用约占据了32 位RI S C 微处理器七成以上的市场份额,ARM芯片的广泛应用和发展也给测试带来了挑战,集成电路测试一般采用实际速度下的功能测试,但半导体技术的发展使得测试开发工程资源按几何规律增长,自动测试设备(ATE)的性能赶不上日益增加的器件I/ O 速度的发展, 同时也越来难以满足ARM等微处理器测试所用的时序信号高分辨率要求, 因而必须不断提高自动测试设备的性能,导致测试成本不断攀升。此外,因为ARM芯片的复杂度越来越高,为对其
进行功能测试,人工编写测试向量的工作量是极其巨大的,实际上一个ARM芯片测试向量的手工编写工作量可能达到数十人年甚至更多。本文针对ARM Cortex内核的工作原理,提出了一种高效的测试向量产生方法,并在BC3192测试系统上实现了对ARM Cortex-M3内核微处理器的测试。

1、微处理器测试方法

集成电路测试主要包括功能测试和直流参数的测试,微处理器的测试也包括功能和直流参数测试两项内容.微处理器包含丰富的指令集,而且微处理器种类繁多,不同微处理器之间很难有统一的测试规范。为了使测试具有通用性,我们有必要对微处理器的测试建立一个统一的模型, 如图1所示。芯片测试系统为被测微处理器提供电源和时钟, 并能够模拟微处理器的仿真通信接口来控制微处理器工作 同时配合仿真时序施加激励向量, 从而达到测试目的。

按微处理器仿真通信接口大致分两类, 一类是具有仿真接口(如JTAG)的微处理器,一类是没有仿真接口的微处理器,对于配备类似JTAG接口的微处理器,测试仪通过仿真一个JTAG接口对被测芯片进行功能或参数测试,没有配备仿真调试接口的芯片,可以根据芯片的外部接口和引导方式选择测试模型。

1.1 跟踪调试模式

大多数的微处理器都提供了跟踪调试接口, 例如最常用的JTAG接口,Cortex-M3内核除了支持JTAG调试外, 还提供了专门的指令追踪单元(ITM) . JTAG ( Joint Test Action Group,联合测试行动小组) 是一种国际标准测试协议(IEEE 1149.1 兼容), 主要用于芯片内部测试。现在多数的高级器件都支持JTAG协议,如ARM.DSP. FPGA 器件等,标准的JTAG接口是4线:TMS.TCK.TDI. TDO,分别为模式选择、时钟、 数据输入和数据输出线。JTAG最初是用来对芯片进行测试的,因此使用JTAG接口测试微处理器具有很多优点。

用JTAG接口对微处理器进行仿真测试,是通过测试系统用测试矢量模拟一个JT A G 接口实现对微处理器的仿真控制, 其核心是状态机的模拟,图2所示为测试系统使用的JTAGTAP控制器的状态转换图。

通过测试仪来模拟状态转换就可以实现JTAG通信控制。

JTAG在物理层和数据链路层具有统一的规范,但针对不同的芯片仿真测试协议可能略有差异。为了使测试模型具有通用性,我们对测试模型的JTAG 接口做了一个抽象层,如图3所示。图中抽象层将类型多样的控制函数转化成芯片能识别的数据流来控制被测芯片的工作状态。

1.2 引导模式/FLASH编程模式

针对没有配备仿真调试接口的微处理器,可以利用引导功能实现对微处理器的测试。因没有配备仿真调试功能,不能实现仿真测试。因此针对这一类的微处理器测试中,需要在芯片中加载测试代码。大多数的微处理器芯片都具有上电引导功能,可以利用引导功能将测试代码加载到微处理器中,进而实现功能和直流参数测试。而对于内部配备FLASH的微处理器可以先将测试代码下载到片内FLASH中,以实现对微处理器的功能和参数测试。为了实现对微处理器的测试控制,通常,测试系统利用微处理器的片上通信接口与片上测试程序通信,互相配合完成功能和参数测试.

2、ARM Cortex-M3的测试

2.1 ARM Cortex-M3内核简介

ARM Cortex-M系列微处理器主要用于低成本和低功耗领域,如智能测量、人机接口设备、汽车和工业控制系统、大型家用电器、消费性产品和医疗器械等领域。图4为Cortex-M系列微处理器的简要框图。

ARM Cortex-M3内核搭载了若干种调试相关的特性。

最主要的就是程序执行控制,包括停机(halting)、单步执行(stepping) 、指令断点、数据观察点、寄存器和存储器访问、性能速写(profiling)以及各种跟踪机制。Cortex-M3的调试系统基于ARM最新的Core Sight 架构,虽然内核本身不再含有JT A G 接口,但是提供了调试访问接口(DAP)的总线接口。通过DAP可以访问芯片的寄存器,也可以访问系统存储器,并且可以在内核运行的时候访问,这就对芯片的测试提供了接口支持。集成 Cortex-M3内核的微处理器一般提供一个调试端口(DP)与DAP相连,目前可用的调试端口包括SWJ‐DP,既支持传统的JTAG调试,也支持新的串行线调试协议。Cortex-M3内核还能挂载一个嵌入式跟踪宏单元(ETM)。 ETM 可以不断地发出跟踪信息,这些信息通过跟踪端口接口单元(TPIU)送到内核的外部,对于外部集成再跟踪信息分析仪的ARM芯片, 可把TI PU输出的已执行指令信息捕捉到,并且送给芯片测试系统。

2. 2 测试向量生成

用自动测试设备(A T E )测试AR M 芯片是一种传统的测试技术,其优点是可以灵活编制测试向量,专注于应用相关的功能模块和参数。但是由于ARM芯片的功能与应用有相当的复杂性,因此对测试系统所具有的能力也要求较高。这就要求测试设备本身必须要具备测试各种不同功能模块的能力, 包含对逻辑、模拟、内存、高速或高频电路的测试能力等等。同时测试系统最好是每个测试通道都有自己的独立测试能力,避免采用资源共享的方式,以便能够灵活运用在各种不同的测试功能上。所以常规的AR M 芯片测试设备往往要求相当高的配置才能应对测试需求。

测试的含义非常广泛, 就ARM芯片测试而言,可以定义多种类型的测试,不同类型的测试需要产生不同类型的测试向量。而测试向量生成的方法,虽然可以人工编制, 但多数情况需要由测试向量生成工具(ATPG) 生成, 才能产生比较完备的测试集。本文介绍的ARM芯片测试方法,借助对应的ARM芯片开发工具产生测试代码,再由专用的测试向量生成工具生成测试向量.这种方法的优点是能针对ARM芯片应用开发人员关心的测试集合产生测试向量,因而比较高效,测试成本也能控制在比较低的水平上。此外,可以借助大量的ARM芯片应用软件来转码, 能大幅减少工作量。缺点是不容易用算法来实现自动生成完备的测试代码。

图5为ARM芯片测试向量生成器。测试代码一般可以从ARM芯片开发例程中获得, 测试向量通过编译器编译成ARM芯片可执行代码, 然后与激励向量和期望向量混合生成完整的ARM芯片测试向量。ARM芯片测试向量生成工具通过时间参数来确定测试代码。激励向量与期望向量之间的时序关系,ARM芯片时间参数可从芯片手册中获得.测试向量生成后, 通过BC3192 集成开发环境下载到测试系统图形卡中,启动测试程序,激励向量依序施加到被测ARM 芯片的输入端口,同时对输出端进行监测比较获得测试结果.综上,测试向量的产生是ARM芯片测试的核心,本文所述测试向量生成器通过输入ARM 芯片可执行代码和芯片时间参数来产生测试逻辑,具有易用.高效的特点,现已用于多个ARM Cortex 内核微处理器的测试中。

3、结论

本文通过分析ARM Cortex-M3内核的工作原理和跟踪调试方法, 利用通用的ARM集成开发环境,结合BC3192 V50测试系统的测试向量生成器,能够快速高效产生基于ARM Cortex- M3内核的微处理器测试向量,进而完成功能和直流参数测试,本案所述方法同样适于其他微处理器的测试。

围观 521

本文的目的是希望读者能够通过本文的内容掌握移植uCOS-II 的规范方法。如果只是需要移植文件,可以直接去Micriμm的官网上下载。

移植uCOS-II,主要的移植工作是编写如下三个文件:

OS_CPU.H

OS_CPU_C.C
OS_CPU_A.ASM

下面就按照这三个文件的顺序来介绍。本文以STM32F107+RealView Compiler 开发环境为例。如果使用的其他的开发环境,个别代码可能需要做些小修改。

OS_CPU.H

OS_CPU.H 的第一部分是定义了一个宏OS_CPU_EXT。这一部分暂时可以先不去管。
#ifdef OS_CPU_GLOBALS
#define OS_CPU_EXT
#else
#define OS_CPU_EXT extern
#endif

接下来是一系列的类型定义。这一部分的移植需参考RealView Compiler Reference Guide的如下章节:
RealView Compiler Reference Guide->C and C++ Implementation Details->Basic data types
从这里可以得到如下信息。

Type
Size in bits
Natural alignment in bytes
char
8
1 (byte-aligned)
short
16
2 (halfword-aligned)
int
32
4 (word-aligned)
long
32
4 (word-aligned)
long long
64
8 (doubleword-aligned)
float
32
4 (word-aligned)
double
64
8 (doubleword-aligned)
long double
64
8 (doubleword-aligned)
All pointers
32
4 (word-aligned)
_Bool
8
1 (byte-aligned)

根据上面的信息,形成下面的代码:

typedef unsigned char BOOLEAN;
typedef unsigned char INT8U;
typedef signed char INT8S;
typedef unsigned short INT16U;
typedef signed short INT16S;
typedef unsigned int INT32U;
typedef signed int INT32S;
typedef float FP32;
typedef double FP64;
typedef unsigned int OS_STK;
typedef unsigned int OS_CPU_SR;

上面代码中OS_STK 表示堆栈出栈、入栈的基本数据长度。我们知道Cortex-M3 的所有堆栈操作都是以字为单位的,所以这里为 unsigned int 型。OS_CPU_SR 对应的是程序状态寄存器PSRs,自然也是unsigned int 型。然后是关于临界区的处理,一般来说我们都喜欢使用第三种方法来实现临界区,这里也不例外。这里多说几句,第一种直接开关中断的实现临界区的方法很少采用,因为这种方法可能将原本关闭了的中断意外的打开。第二种方法是最高效的实现方法,但是这种方法调整了堆栈指针,对于需要利用堆栈指针间接寻址局部变量的系统并不适用。(x86通常采用第二种方法,因为它有单独的寄存器来做局部变量的寻址)第三种方法最保险,虽然效率比第二种方法略低一点。

#define OS_CRITICAL_METHOD 3
#define OS_ENTER_CRITICAL() {cpu_sr = OS_CPU_SR_Save();}
#define OS_EXIT_CRITICAL() {OS_CPU_SR_Restore(cpu_sr);}
#if OS_CRITICAL_METHOD == 3
OS_CPU_SR OS_CPU_SR_Save(void);
void OS_CPU_SR_Restore(OS_CPU_SR cpu_sr);
#endif

这两个函数可以用汇编(OS_CPU_A.ASM)来编写:

EXPORT OS_CPU_SR_Save
  EXPORT OS_CPU_SR_Restore
OS_CPU_SR_Save
MRS R0, PRIMASK ; Set prio int mask to mask all (except faults)
CPSID I
BX LR
OS_CPU_SR_Restore
MSR PRIMASK, R0
  BX LR

也可以通过C代码(OS_CPU_C.C)中插入汇编的方式来实现:

__asm OS_CPU_SR OS_CPU_SR_Save(void)
{
MRS R0, PRIMASK ; Set prio int mask to mask all (except faults)
CPSID I
BX LR
}
__asm void OS_CPU_SR_Restore(OS_CPU_SR cpu_sr)
{
MSR PRIMASK, R0
BX LR
}

上面的代码利用的RealView Compiler 的特殊功能(Embedded assembler),如需进一步的信息,可以参考RealView Compiler Reference Guide中Using the Inline and Embedded Assemblers这一章的内容。然后是堆栈增长方向,ARM Cortex-M3 的堆栈是倒生的:

#define OS_STK_GROWTH 1

任务切换,OSCtxSw()在OS_CPU_A.ASM 中定义:

#define OS_TASK_SW() OSCtxSw()

最后是一些函数原型声明:

void OSCtxSw(void);
void OSIntCtxSw(void);
void OSStartHighRdy(void);
void OS_CPU_PendSVHandler(void);
void OS_CPU_SysTickHandler(void);

OS_HOOK.C

在原本uCOS-II 的移植代码中是没有这个文件的。由于下面这9个函数的函数体基本都是空的,并且移植时几乎不需要更改,所以我就将其拿出到一个单独的文件中来了。

OSInitHookBegin()OSInitHookEnd()OSTaskCreateHook()OSTaskDelHook()OSTaskIdleHook()OSTaskStatHook()OSTaskSwHook()OSTCBInitHook()OSTimeTickHook()这9个函数的代码都很简单,下面是代码,不多介绍。

#if OS_CPU_HOOKS_EN > 0 && OS_VERSION > 203
void OSInitHookBegin (void)
{
#if OS_TMR_EN > 0
OSTmrCtr = 0;
#endif
}
#endif
#if OS_CPU_HOOKS_EN > 0 && OS_VERSION > 203
void OSInitHookEnd (void)
{
}
#endif
#if OS_CPU_HOOKS_EN > 0
void OSTaskCreateHook (OS_TCB *p_tcb)
{
#if OS_VIEW_MODULE > 0
OSView_TaskCreateHook(p_tcb);
#else
(void)p_tcb ; /* Prevent compiler warning */
#endif
}
#endif
#if OS_CPU_HOOKS_EN > 0
void OSTaskDelHook (OS_TCB *p_tcb)
{
(void)p_tcb ; /* Prevent compiler warning */
}
#endif
#if OS_CPU_HOOKS_EN > 0 && OS_VERSION >= 251
extern volatile unsigned long wdg_clr_flag;
void OSTaskIdleHook (void)
{
}
#endif
#if OS_CPU_HOOKS_EN > 0
void OSTaskStatHook (void)
{
}
#endif
#if (OS_CPU_HOOKS_EN > 0) && (OS_TASK_SW_HOOK_EN > 0)
void OSTaskSwHook (void)
{
#if OS_VIEW_MODULE > 0
OSView_TaskSwHook();
#endif
}
#endif
#if OS_CPU_HOOKS_EN > 0 && OS_VERSION > 203
void OSTCBInitHook (OS_TCB *ptcb)
{
(void) ptcb; /* Prevent Compiler warning */
}
#endif
#if (OS_CPU_HOOKS_EN > 0) && (OS_TIME_TICK_HOOK_EN > 0)
void OSTimeTickHook (void)
{
#if OS_VIEW_MODULE > 0
OSView_TickHook();
#endif

#if OS_TMR_EN > 0
OSTmrCtr++;
if (OSTmrCtr >= (OS_TICKS_PER_SEC / OS_TMR_CFG_TICKS_PER_SEC))
{
OSTmrCtr = 0;
OSTmrSignal();
}
#endif
}

OS_CPU_C.C 和 OS_CPU_A.ASM

重要的移植工作都在这两个文件中提供,由于RealView Compiler 支持在C文件中插入汇编代码,所以OS_CPU_A.ASM 文件实际上可以去掉。所有的函数都在OS_CPU_C.C 中实现。下面分别介绍。

OSTaskStkInit()

OSTaskStkInit 的移植是比较有难度的。这个函数是用来初始化各个任务的堆栈,使各个任务的堆栈就像是刚才中断处理函数中返回那样。

OS_STK *OSTaskStkInit (void (*p_task)(void *p_arg), void *p_arg, OS_STK *p_tos, INT16U opt)
{
OS_STK *stk;
(void)opt; /* 'opt' is not used, prevent warning */
stk = p_tos; /* Load stack pointer */
/* Registers stacked as if auto-saved on exception */
*(stk) = (INT32U)0x01000000L; /* xPSR, the ‘T’ bit is set */
*(--stk) = (INT32U)p_task; /* Entry Point of the task */
*(--stk) = (INT32U)OS_TaskReturn; /* the return address of the task */

*(--stk) = (INT32U)0x12121212L; /* R12 */
*(--stk) = (INT32U)0x03030303L; /* R3 */
*(--stk) = (INT32U)0x02020202L; /* R2 */
*(--stk) = (INT32U)0x01010101L; /* R1 */
*(--stk) = (INT32U)p_arg; /* R0 : 1st argument to the task */

/* Remaining registers saved on process stack */
*(--stk) = (INT32U)0x11111111L; /* R11 */
*(--stk) = (INT32U)0x10101010L; /* R10 */
*(--stk) = (INT32U)0x09090909L; /* R9 */
*(--stk) = (INT32U)0x08080808L; /* R8 */
*(--stk) = (INT32U)0x07070707L; /* R7 */
*(--stk) = (INT32U)0x06060606L; /* R6 */
*(--stk) = (INT32U)0x05050505L; /* R5 */
*(--stk) = (INT32U)0x04040404L; /* R4 */
return(stk);
}

想要理解上面的代码需要知道Cortex-M3在响应外部中断时对寄存器的压栈顺序,还需知道函数的第一个参数是通过R0来传递的。建议阅读ARM Cotex M3 权威指南,里面有详细的介绍。这里我只说一处,就是OS_TaskReturn 位置对应的是任务的返回地址。我们知道,uCOS-II 中任务就是简单的函数。普通的函数执行完成后会返回到调用它的地方的下一条语句处继续执行。这个位置就记录在堆栈中,也就是OS_TaskReturn所在的位置。uCOS-II要求任务必须是无限的循环,不允许退出。所以理论上永远不会跳转到OS_TaskReturn处执行。OS_TaskReturn的作用是当程序异常退出时不至于程序跑飞。在现在的移植代码中OS_TaskReturn 也是个简单的函数,没有加入额外的保护代码。

void OS_TaskReturn(void)
{
  while(1);
}
OSStartHighRdy

这个函数只被OSStart()调用。用来运行最高优先级的任务。代码如下。

__asm void OSStartHighRdy(void)
{
LDR R0, =NVIC_SYSPRI14 ; Set the PendSV exception priority
LDR R1, =NVIC_PENDSV_PRI
STRB R1, [R0]
MOVS R0, #0 ; Set the PSP to 0 for initial context switch call
MSR PSP, R0
LDR R0, =OSRunning ; OSRunning = TRUE
MOVS R1, #1
STRB R1, [R0]
LDR R0, =NVIC_INT_CTRL ; Trigger the PendSV exception (causes context switch)
LDR R1, =NVIC_PENDSVSET
STR R1, [R0]
  CPSIE I
}

其中如下三行代码是用来设置PendSV异常的优先级为最低。

LDR R0, =NVIC_SYSPRI14 ; Set the PendSV exception priority
LDR R1, =NVIC_PENDSV_PRI
STRB R1, [R0]

相当于如下的C代码:

NVIC->IP[14] = 0xFF;

下面两行代码的作用是使线程堆栈指针 PSP = 0。PendSV_Handler 中需要根据它来判断是否是OSStartHighRdy 引起的PendSV,因为这时要特殊处理一下。

MOVS R0, #0 ; Set the PSP to 0 for initial context switch call
MSR PSP, R0

最后四行的作用是引起一次 PendSV。相当于下面的C代码:

SCB->ICSR |= SCB_ICSR_PENDSVSET_Msk;
OSCtxSw 和 OSIntCtxSw

在其他处理器的移植代码中,这两个函数还是有些工作要做的。但是对于Cortex-M3 就简单的多了,只要引起一次PendSV 就行了,具体的任务切换由PendSV来处理。

void OSCtxSw(void)
{
SCB->ICSR |= SCB_ICSR_PENDSVSET_Msk;
}
void OSIntCtxSw(void)
{
SCB->ICSR |= SCB_ICSR_PENDSVSET_Msk;
}

也可以写为汇编代码,写为汇编的好处是两个函数可以共用一个函数体:

OSCtxSw
OSIntCtxSw
  LDR R0, =NVIC_SYSPRI14 ; Set the PendSV exception priority
  LDR R1, =NVIC_PENDSV_PRI
  STRB R1, [R0]

SysTick_Handler

SysTick 用来处理操作系统的计时。代码很简单,无需多说。

void SysTick_Handler(void)
{
OS_CPU_SR cpu_sr;
OS_ENTER_CRITICAL(); /* Tell uC/OS-II that we are starting an ISR */
OSIntNesting++;
OS_EXIT_CRITICAL();
OSTimeTick(); /* Call uC/OS-II's OSTimeTick() */
OSIntExit();
}

PendSV_Handler

最终的任务切换工作都在这里完成。下面先给出伪代码。从这里就可以看出OSStartHighRdy 中将PSP 写为0 的作用了。

OS_CPU_PendSVHandler
{
if (PSP != NULL)
{
Save R4-R11 onto task stack;
OSTCBCur->OSTCBStkPtr = SP;
}
OSTaskSwHook();
OSPrioCur = OSPrioHighRdy;
OSTCBCur = OSTCBHighRdy;
PSP = OSTCBHighRdy->OSTCBStkPtr;
Restore R4-R11 from new task stack;
Return from exception;
}

下面给出真实的代码,可以看出与伪代码是对应的:

__asm void PendSV_Handler(void)
{
EXTERN OSPrioCur
EXTERN OSPrioHighRdy
EXTERN OSTCBCur
EXTERN OSTCBHighRdy
EXTERN OSTaskSwHook
CPSID I ; Prevent interruption during context switch
MRS R0, PSP ; PSP is process stack pointer
CBZ R0, PendSVHandler_nosave ; Skip register save the first time
SUBS R0, R0, #0x20 ; Save remaining regs r4-11 on process stack
STM R0, {R4-R11}
LDR R1, =OSTCBCur ; OSTCBCur->OSTCBStkPtr = SP;
LDR R1, [R1]
STR R0, [R1] ; R0 is SP of process being switched out
; At this point, entire context of process has been saved
PendSVHandler_nosave
PUSH {R14} ; Save LR exc_return value
LDR R0, =OSTaskSwHook ; OSTaskSwHook();
BLX R0
POP {R14}
LDR R0, =OSPrioCur ; OSPrioCur = OSPrioHighRdy;
LDR R1, =OSPrioHighRdy
LDRB R2, [R1]
STRB R2, [R0]
LDR R0, =OSTCBCur ; OSTCBCur = OSTCBHighRdy;
LDR R1, =OSTCBHighRdy
LDR R2, [R1]
STR R2, [R0]
LDR R0, [R2] ; R0 is new process SP; SP = OSTCBHighRdy->OSTCBStkPtr;
LDM R0, {R4-R11} ; Restore r4-11 from new process stack
ADDS R0, R0, #0x20
MSR PSP, R0 ; Load PSP with new process SP
ORR LR, LR, #0x04 ; Ensure exception return uses process stack
CPSIE I
BX LR ; Exception return will restore remaining context
}

至此,移植工作完成。uCOS-II 在 Cortex-M3上的移植与其他单片机上移植代码的最大区别在于所有的任务切换工作都放到了 PendSV 中进行,而PendSV 中断的优先级被设为最低,这样就能保证更高优先级的中断能够及时被处理。不过,在PendSV 中断处理代码中第一条语句就是关中断,这时如果来了更高优先级的中断,也是无法响应的。能否进一步改善中断响应性能还需再思考。个人认为应该还有进一步优化的可能,不过具体该如何优化,暂时还没有头绪。

围观 423

前几天刚好同事问起在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、计算机体系结构——流水线中的相关——延迟分支方法

文章来源:博客园

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

开发板测评图片
围观 833

页面

订阅 RSS - Cortex-M3