CMSIS
4444....1111 CMSIS
CMSIS
CMSIS 标准标准标准标准
ARM 公司于 2008 年 11 月 12 日发布了 ARM Cortex 微控制器软件接口标准 CMSIS 1.0。
CMSIS 是独立于供应商的 Cortex-M 处理器系列硬件抽象层,为芯片厂商和中间件供应商提
供了简单的处理器软件接口,简化了软件复用工作,降低了 Cortex-M 上操作系统的移植难
度,并减少了新入门的微控制器开发者的学习曲线和新产品的上市时间。
根据近期的调查研究,软件开发已经被嵌入式行业公认为最主要的开发成本,图 4-1 为
近年来软件开发与硬件开发花费对比图。因此,ARM 与 Atmel、IAR、KEIL、Luminary Micro、
Micrium、NXP、SEGGER 和 ST 等诸多芯片和软件工具厂商合作,将所有 Cortex 芯片厂
商的产品的软件接口标准化,制定了 CMSIS 标准。此举意在降低软件开发成本,尤其针对
进行新设备项目开发或将已有的软件移植到其他芯片厂商提供的基于 Cortex 处理器的微控
制器的情况。有了该标准,芯片厂商就能够将他们的资源专注于对其产品的外设特性进行差
异化,并且能够消除对微控制器进行编程时需要维持的不同的、互相不兼容的标准的需求,
从而达到降低开发成本的目的。
图 4-1 软件与硬件开发成本对比图
CMSIS 的现有标准是 CMSIS 2.0,与之前的版本有了一些新的变化。CMSIS 2.0 标准
包含 Cortex-M0、Cortex-M3、Cortex-M4 以及 SVD(System View Description )这四部
分。目前,各芯片厂商也还没有都推出各自基于 CMSIS 标准的完整 BSP 包。但未来的
Cortex-M 处理器应用将统一在 CMSIS 的标准之下是一个不可避免的趋势。本节将以
STM32F10x 处理器为对象,介绍 CMSIS 2.0 标准的 Cortex-M3 部分。
CMSIS
4444....1111.1.1.1.1 基于基于基于基于 CMSIS
CMSIS
CMSIS 标准的软件架构
标准的软件架构
标准的软件架构
标准的软件架构
基于 CMSIS 2.0 标准的软件架构如图 4-2 所示。与 CMSIS 1.x 版本相比,CMSIS 2.0
去除了中间层,增加了一个可选的外设访问函数(Access Functions for Peripherals)。
图 4-2 基于 CMSIS 标准的软件架构
从图 4-2 可以看到,基于 CMSIS 标准的软件架构主要分为以下四层:用户应用层,操
作系统层,CMSIS 层以及硬件寄存器层。其中 CMSIS 层起着承上启下的作用,一方面该
层对硬件寄存器层进行了统一的实现,屏蔽了不同厂商对 Cortex-M 系列微处理器核内外设
寄存器的不同定义,另一方面又向上层的操作系统和应用层提供接口,简化了应用程序开发
的难度,使开发人员能够在完全透明的情况下进行一些应用程序的开发。也正是如此,
CMSIS 层的实现也相对复杂,下面将对 CMSIS 层次结构进行剖析。
CMSIS 层主要分为以下 3 个部分:
� 核内外设访问层(CPAL,Core Peripheral Access Layer):该层由 ARM 负责实现。
包括对寄存器名称、地址的定义,对核寄存器、NVIC、调试子系统的访问接口定
义以及对特殊用途寄存器的访问接口(例如:CONTROL,xPSR)定义。由于对
特殊寄存器的访问以内联方式定义,所以针对不同的编译器 ARM 统一用__INLINE
来屏蔽差异。该层定义的接口函数均是可重入的。
� 片上外设访问层(DPAL, Device Peripheral Access Layer):该层由芯片厂商负责
实现。该层的实现与 CPAL 类似,负责对硬件寄存器地址以及外设访问接口进行定
义。该层可调用 CPAL 层提供的接口函数同时根据设备特性对异常向量表进行扩
展,以处理相应外设的中断请求。
� 外设访问函数(AFP, Access Functions for Peripherals):该层也由芯片厂商负责
实现,主要是提供访问片上外设的访问函数,这一部分是可选的。
对一个 Cortex-M 微控制系统而言,CMSIS 通过以上三个部分实现了:
� 定义了访问外设寄存器和异常向量的通用方法;
� 定义了核内外设的寄存器名称和核异常向量的名称;
� 为 RTOS 核定义了与设备独立的接口,包括 Debug 通道。
这样芯片厂商就能专注于对其产品的外设特性进行差异化,并且消除他们对微控制器进
行编程时需要维持的不同的、互相不兼容的标准需求,以达到低成本开发的目的。
CMSIS
4444....1111.2.2.2.2 CMSIS
CMSIS
CMSIS 代码代码代码代码规范规范规范规范
���� 基本规范
� CMSIS 的 C 代码遵照 MISRA 2004 规则。
� 使用标准 ANSI C 头文件中定义的标准数据类型。
� 由#define 定义的包含表达式的常数必须用括号括起来。
� 变量和参数必须有完全的数据类型。
� CPAL 层的函数必须是可重入的。
� CPAL 层的函数不能有阻塞代码,也就是说等待、查询等循环必须在其他的软
件层中。
� 定义每个异常/中断的:
� 每个异常处理函数的后缀是_Handler,每个中断处理器函数的后缀是
_IRQHandler。
� 默认的异常中断处理器函数(弱定义)包含一个无限循环。
� 用#define 将中断号定义为后缀为_IRQn 的名称。
���� 推荐规范
� 定义核寄存器、外设寄存器和 CPU 指令名称时使用大写。
� 定义外设访问函数、中断函数名称时首字母大写。
� 对于某个外设相应的函数,一般用该外设名称作为其前缀。
� 按照 Doxygen 规范撰写函数的注释,注释使用 C90 风格(/* 注释 */) 或者 C++
风格(// 注释),函数的注释应包含以下内容:
� 一行函数简介;
� 参数的详细解释;
� 返回值的详细解释;
� 函数功能的详细描述。
���� 数据类型及 IOIOIOIO 类型限定符
HAL 层使用标准 ANSI C 头文件 stdint.h 定义的数据类型。IO 类型限定符用于指定外设
寄存器的访问限制,定义如表 4-1 所列。
表 4-14-14-14-1 IOIOIOIO 类型限定符
IOIOIOIO 类型限定符
#define
#define
#define
#define
__I
__O
__IO
Volatile const
volatile
volatile
描述
只读
只写
读写
CMSIS
CMSIS
���� CMSIS
CMSIS 版本号
CMSIS 标准有多个版本号,对于 Cortex M3 处理器,在 core_cm3.h 中定义所用 CMSIS
的版本。
#define __CM3_CMSIS_VERSION_MAIN (0x01)
#define __CM3_CMSIS_VERSION_SUB
(0x30)
#define __CM3_CMSIS_VERSION
__CM3_CMSIS_VERSION_SUB)
对于 Cortex M0 处理器,在 core_cm0.h 中定义所用 CMSIS 的版本。
/* [31:16] main version
/* [15:0] sub version
*/
*/
((__CM3_CMSIS_VERSION_MAIN << 16) |
#define __CM0_CMSIS_VERSION_MAIN (0x01)
#define __CM0_CMSIS_VERSION_SUB
(0x30)
#define __CM0_CMSIS_VERSION
/* [31:16] main version
/* [15:0] sub version
*/
*/
((__CM0_CMSIS_VERSION_MAIN << 16) |
__CM0_CMSIS_VERSION_SUB)
Cortex
Cortex
���� Cortex
Cortex 内核
对于 Cortex M3 处理器,在头文件 core_cm3.h 中定义:
#define __CORTEX_M
对于 Cortex M0 处理器,在头文件 core_cm0.h 中定义:
#define __CORTEX_M
���� 工具链
CMSIS 支持目前嵌入式开发的三大主流工具链,即 ARM ReakView(armcc),IAR
EWARM(iccarm)以及 GNU 工具链(gcc)。通过在 core_cm0.c 中的如下定义,来屏蔽
一些编译器内置关键字的差异。
(0x03)
(0x00)
/* define compiler specific symbols */
#if defined ( __CC_ARM
)
__asm
__inline
#define __ASM
#define __INLINE
/*!< asm keyword for ARM Compiler */
/*!< inline keyword for ARM Compiler*/
#elif defined ( __ICCARM__ )
__asm
inline
#define __ASM
#define __INLINE
Only avaiable in High optimization mode! */
/*!< asm keyword for IAR Compiler */
/*!< inline keyword for IAR Compiler.
#elif defined
#define __ASM
#define __INLINE
( __GNUC__ )
__asm
inline
#elif defined
#define __ASM
#define __INLINE
( __TASKING__ )
__asm
inline
/*!< asm keyword for GNU Compiler */
/*!< inline keyword for GNU Compiler*/
/*!< asm keyword for TASKING Compiler */
/*!< inline keyword for TASKING Compiler*/
#endif
这样 CPAL 中的功能函数就可以被定义成静态内联类型(static __INLINE),以实现编
译优化。
CMSIS
4444....1111....3333 CMSIS
CMSIS
CMSIS 文件结构
文件结构文件结构文件结构
CMSIS 标准的文件结构如图 4-3 所示,下面将对其中各文件作简要介绍。
图 4-3 CMSIS 文件结构
device.h
device.h
���� device.h
device.h
device.h 由芯片厂商提供,是工程中 C 源程序的主要包含文件。其中“device”是指
处理器型号,例如 STM32F10x 系列处理器对应的头文件是 stm32f10x.h。它包含:
� 中断号的定义。提供所有内核及处理器定义的所有中断及异常的中断号
(IRQn)。 例如 stm32f10x 处理器,中断号定义如下:
/*!< 3 Cortex-M3 Hard Fault Interrupt
typedef enum IRQn
{
/****** Cortex-M3 Processor Exceptions/Interrupt Numbers ************/
*/
NonMaskableInt_IRQn = -14, /*!< 2 Non Maskable Interrupt
*/
HardFault_IRQn = -13,
MemoryManagement_IRQn = -12, /*!< 4 Cortex-M3 Memory ManagementInterrupt*/
/*! < 5 Cortex-M3 Bus Fault Interrupt
*/
/*! < 6 Cortex-M3 Usage Fault Interrupt
*/
/*! < 11 Cortex-M3 SV Call Interrupt
*/
/*!< 12 Cortex-M3 Debug Monitor Interrupt*/
*/
/*!< 14 Cortex-M3 Pend SV Interrupt
/*!< 15 Cortex-M3 System Tick Interrupt
*/
********************/
SVCall_IRQn = -5,
DebugMonitor_IRQn = -4,
PendSV_IRQn = -2,
SysTick_IRQn = -1,
/******
BusFault_IRQn = -11,
UsageFault_IRQn = -10,
STM32 specific Interrupt Numbers
/*!< Window WatchDog Interrupt
*/
/*!< PVD through EXTI Line detection Interrupt*/
WWDG_STM_IRQn = 0,
PVD_STM_IRQn = 1,
:
:
} IRQn_Type;
� 厂商实现处理器时 Cortex M 核的配置。Cortex M 处理器在具体实现时,有些
部件是可选、有些参数是可以设置的,例如 MPU、NVIC 优先级位等。在
stm32f10x.h 中包含头文件 core_cm0.h/core_cm3.h 的预处理命令之前,需要
先根据处理器的具体实现对以下参数做设置。
表 4-2 实现处理器时 Cortex M 核的配置
#define
#define
#define
#define
__NVIC_PRIO_BITS
__NVIC_PRIO_BITS
__MPU_PRESENT
文件
值
描述
core_cm0.h
core_cm3.h
core_cm0.h/core_cm3.h
(2) 实现 NVIC 时优先级位的位数
(2..8) 实现 NVIC 时优先级位的位数
(0,1) 是否实现 MPU
__Vendor_SysTickConfig
(1)
core_cm0.h/core_cm3.h
定义为 1,则 core_cm0.h/core_cm3.h 中
的 SysTickConfig 函数被排除在外;这种情
况下厂商必须在 devic.h 中实现该函数。
� DPAL 层。提供所有处理器片上外设的定义,包含数据结构和片上外设的地址
映射。一般数据结构的名称定义为“处理器或厂商缩写_外设缩写_TypeDef”,
也有些厂家定义的数据结构名称为“外设缩写_TypeDef”。
例如 LPC17xx 系列处理器的 I2C 寄存器组数据结构定义如下:
/*------------- Inter-Integrated Circuit (I2C) ---------------*/
typedef struct
{
__IO uint32_t I2CONSET;
__I uint32_t I2STAT;
__IO uint32_t I2DAT;
__IO uint32_t I2ADR0;
__IO uint32_t I2SCLH;
__IO uint32_t I2SCLL;
__O uint32_t I2CONCLR;
__IO uint32_t MMCTRL;
__IO uint32_t I2ADR1;
__IO uint32_t I2ADR2;
__IO uint32_t I2ADR3;
__I uint32_t I2DATA_BUFFER;
__IO uint32_t I2MASK0;
__IO uint32_t I2MASK1;
__IO uint32_t I2MASK2;
__IO uint32_t I2MASK3;
} LPC_I2C_TypeDef;
LPC17xx 处理器 I2C 接口基地址定义如下:
#define LPC_I2C0_BASE
(LPC_APB0_BASE + 0x1C000)
访问 LPC17xx 处理器 I2C 接口的定义如下:
#define LPC_I2C0
)
� 外设访问函数(可选)。这些函数由芯片厂商提供,为访问片上外设提供帮助,
((LPC_I2C_TypeDef *) LPC_I2C0_BASE
它们可以作为内联函数,也可以在厂商提供的库中外部引用。
core_cm0.h
core_cm0.c
core_cm0.h
core_cm0.c
core_cm0.h 和 core_cm0.c
���� core_cm0.h
core_cm0.c
这两个文件是实现 Cortex-M0 处理器 CMSIS 标准的 CPAL 层。其中,头文件 core_cm0.h
定义 Cortex-M0 核内外设的数据结构及其地址映射,另外它也提供一些访问 Cortex-M0 核
内寄存器及外设的函数,这些函数定义为静态内联。
c 文件 core_cm0.c 则定义了一些访问 Cortex-M0 核内寄存器的函数,例如对 xPSR、
MSP、PSP 等寄存器的访问;另外还将一些汇编语言指令也定义为函数。
core_cm3.h
core_cm3.c
core_cm3.h
core_cm3.c
core_cm3.h 和 core_cm3.c
���� core_cm3.h
core_cm3.c
这两个文件是实现 Cortex-M3 处理器 CMSIS 标准的 CPAL 层。其中,头文件 core_cm3.h
定义 Cortex-M3 核内外设的数据结构及其地址映射,另外它也提供一些访问 Cortex-M0 核
内寄存器及外设的函数,这些函数定义为静态内联。
c 文件 core_cm3.c 则定义了一些访问 Cortex-M3 核内寄存器的函数,例如对 xPSR、
MSP、PSP 等寄存器的访问;另外还将一些汇编语言指令也定义为函数。
startup_device
startup_device.s.s.s.s
startup_device
���� startup_device
汇编文件 startup_device.s 是在 ARM 提供的启动文件模板基础上,由各芯片厂商各自
修订而成的,例如 STM32F107 处理器的启动代码文件就是 startup_stm32f10x_c1.s,它主
要有三个功能。
� 配置并初始化堆和栈,例如 startup_stm32f10x_c1.s 对堆和栈的配置如下:
Stack_Size
EQU
0x00000400
AREA
SPACE
STACK, NOINIT, READWRITE, ALIGN=3
Stack_Size
Stack_Mem
__initial_sp
; Heap Configuration
;
;
Heap Size (in Bytes) <0x0-0xFFFFFFFF:8>
Heap_Size
EQU
0x00000200
AREA
HEAP, NOINIT, READWRITE, ALIGN=3
SPACE
Heap_Size
__heap_base
Heap_Mem
__heap_limit
对堆和栈的初始化如下:
;******************************************************************
; 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
LDR
LDR
LDR
BX
R0, = Heap_Mem
R1, =(Stack_Mem + Stack_Size)
R2, = (Heap_Mem + Heap_Size)
R3, = Stack_Mem
LR
� 定义中断向量表及相应的中断处理函数。例如,startup_stm32f10x_c1.s 定义中断
向量表如下:
; Vector Table Mapped to Address 0 at Reset
RESET, DATA, READONLY
AREA
EXPORT __Vectors
EXPORT __Vectors_End
EXPORT __Vectors_Size
__Vectors
DCD
DCD
DCD
DCD
DCD
DCD
DCD
DCD
DCD
DCD
DCD
DCD
DCD
DCD
DCD
DCD
__initial_sp
Reset_Handler
NMI_Handler
HardFault_Handler
MemManage_Handler
BusFault_Handler
UsageFault_Handler
0
0
0
0
SVC_Handler
DebugMon_Handler
0
PendSV_Handler
SysTick_Handler
; Top of Stack
; Reset Handler
; NMI Handler
; Hard Fault Handler
; MPU Fault Handler
; Bus Fault Handler
; Usage Fault Handler
; Reserved
; Reserved
; Reserved
; Reserved
; SVCall Handler
; Debug Monitor Handler
; Reserved
; PendSV Handler
; SysTick Handler
; External Interrupts
DCD
WWDG_IRQHandler
:
; Window Watchdog
:
DCD
DCD
CAN2_SCE_IRQHandler
OTG_FS_IRQHandler
; CAN2 SCE
; USB OTG FS
__Vectors_End
__Vectors_Size EQU __Vectors_End - __Vectors
AREA
|.text|, CODE, READONLY
所有中断处理函数均定义为弱函数;除了 Reset_Handler 之外,其他中断处理均为哑
函数。这样所有中断处理函数的名称都已经被定义好了,需要实现时只要在用户代码中重写
该函数即可。例如,startup_stm32f10x_c1.s 中定义中断处理函数如下:
; Reset Handler
[WEAK]
Reset_Handler
PROC
EXPORT Reset_Handler
IMPORT SystemInit
IMPORT __main
LDR
BLX
LDR
BX
ENDP
R0, =SystemInit
R0
R0, =__main
R0