STM32固件库V3.3.0的CMSIS文件简析
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
1CMSIS标准
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处理器的微控制器的情况。
有了该标准,芯片厂商就能够将他们的资源专注于对其产品的外设特性进行差异化,并且能够消除对微控制器进行编程时需要维持的不同的、互相不兼容的标准的需求,从而达到降低开发成本的目的。
如图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部分。
4.1.1基于CMSIS标准的软件架构
基于CMSIS 2.0标准的软件架构如图4-2所示。
与CMSIS 1.x版本相比,CMSIS 2.0
去除了中间层,增加了一个可选的外设访问函数(Access Functions for Peripherals)。
从图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通道。
这样芯片厂商就能专注于对其产品的外设特性进行差异化,并且消除他们对微控制器进
行编程时需要维持的不同的、互相不兼容的标准需求,以达到低成本开发的目的。
4.1.2CMSIS代码规范
基本规范
CMSIS的C代码遵照MISRA2004规则。
使用标准ANSI C头文件<stdint.h>中定义的标准数据类型。
由#define定义的包含表达式的常数必须用括号括起来。
变量和参数必须有完全的数据类型。
CPAL层的函数必须是可重入的。
CPAL层的函数不能有阻塞代码,也就是说等待、查询等循环必须在其他的软
件层中。
定义每个异常/中断的:
每个异常处理函数的后缀是_Handler,每个中断处理器函数的后缀是
_IRQHandler。
默认的异常中断处理器函数(弱定义)包含一个无限循环。
用#define将中断号定义为后缀为_IRQn的名称。
推荐规范
定义核寄存器、外设寄存器和CPU指令名称时使用大写。
定义外设访问函数、中断函数名称时首字母大写。
对于某个外设相应的函数,一般用该外设名称作为其前缀。
按照Doxygen规范撰写函数的注释,注释使用C90风格(/*注释*/)或者C++
风格(//注释),函数的注释应包含以下内容:
一行函数简介;
参数的详细解释;
返回值的详细解释;
函数功能的详细描述。
数据类型及IO类型限定符
HAL层使用标准ANSI C头文件stdint.h定义的数据类型。
IO类型限定符用于指定外设寄存器的访问限制,定义如表4-1所列。
表4-1IO类型限定符
IO类型限定符#define描述
__I Volatile const只读
__O volatile只写
__IO volatile读写
CMSIS版本号
CMSIS标准有多个版本号,对于Cortex M3处理器,在core_cm3.h中定义所用CMSIS
的版本。
#define__CM3_CMSIS_VERSION_MAIN(0x01)/*[31:16]main version*/
#define__CM3_CMSIS_VERSION_SUB(0x30)/*[15:0]sub version*/
#define__CM3_CMSIS_VERSION((__CM3_CMSIS_VERSION_MAIN<<16)|
__CM3_CMSIS_VERSION_SUB)
对于Cortex M0处理器,在core_cm0.h中定义所用CMSIS的版本。
#define__CM0_CMSIS_VERSION_MAIN(0x01)/*[31:16]main version*/
#define__CM0_CMSIS_VERSION_SUB(0x30)/*[15:0]sub version*/
#define__CM0_CMSIS_VERSION((__CM0_CMSIS_VERSION_MAIN<<16)|
__CM0_CMSIS_VERSION_SUB)
Cortex内核
对于Cortex M3处理器,在头文件core_cm3.h中定义:
#define__CORTEX_M(0x03)
对于Cortex M0处理器,在头文件core_cm0.h中定义:
#define__CORTEX_M(0x00)
工具链
CMSIS支持目前嵌入式开发的三大主流工具链,即ARM ReakView(armcc),IAR EWARM(iccarm)以及GNU工具链(gcc)。
通过在core_cm0.c中的如下定义,来屏蔽一些编译器内置关键字的差异。
/*define compiler specific symbols*/
#if defined(__CC_ARM)
#define__ASM__asm/*!<asm keyword for ARM Compiler*/
#define__INLINE__inline/*!<inline keyword for ARM Compiler*/
#elif defined(__ICCARM__)
#define__ASM__asm/*!<asm keyword for IAR Compiler*/
#define__INLINE inline/*!<inline keyword for IAR Compiler.
Only avaiable in High optimization mode!*/
#elif defined(__GNUC__)
#define__ASM__asm/*!<asm keyword for GNU Compiler*/
#define__INLINE inline/*!<inline keyword for GNU Compiler*/
#elif defined(__TASKING__)
#define__ASM__asm/*!<asm keyword for TASKING Compiler*/
#define__INLINE inline/*!<inline keyword for TASKING Compiler*/
#endif
这样CPAL中的功能函数就可以被定义成静态内联类型(static__INLINE),以实现编译优化。
4.1.3CMSIS文件结构
CMSIS标准的文件结构如图4-3所示,下面将对其中各文件作简要介绍。
图4-3CMSIS文件结构
device.h
device.h由芯片厂商提供,是工程中C源程序的主要包含文件。
其中“device”是指处理器型号,例如STM32F10x系列处理器对应的头文件是stm32f10x.h。
它包含:
中断号的定义。
提供所有内核及处理器定义的所有中断及异常的中断号(IRQn)。
例如stm32f10x处理器,中断号定义如下:
typedef enum IRQn
{
/******Cortex-M3Processor Exceptions/Interrupt Numbers************/
NonMaskableInt_IRQn=-14,/*!<2Non Maskable Interrupt*/
HardFault_IRQn=-13,/*!<3Cortex-M3Hard Fault Interrupt*/
MemoryManagement_IRQn=-12,/*!<4Cortex-M3Memory ManagementInterrupt*/
BusFault_IRQn=-11,/*!<5Cortex-M3Bus Fault Interrupt*/
UsageFault_IRQn=-10,/*!<6Cortex-M3Usage Fault Interrupt*/
SVCall_IRQn=-5,/*!<11Cortex-M3SV Call Interrupt*/
DebugMonitor_IRQn=-4,/*!<12Cortex-M3Debug Monitor Interrupt*/
PendSV_IRQn=-2,/*!<14Cortex-M3Pend SV Interrupt*/
SysTick_IRQn=-1,/*!<15Cortex-M3System Tick Interrupt*/
/******STM32specific Interrupt Numbers********************/
WWDG_STM_IRQn=0,/*!<Window WatchDog Interrupt*/
PVD_STM_IRQn=1,/*!<PVD through EXTI Line detection Interrupt*/
:
:
}IRQn_Type;
厂商实现处理器时Cortex M核的配置。
Cortex M处理器在具体实现时,有些部件是可选、有些参数是可以设置的,例如MPU、NVIC优先级位等。
在
stm32f10x.h中包含头文件core_cm0.h/core_cm3.h的预处理命令之前,需要
先根据处理器的具体实现对以下参数做设置。
表4-2实现处理器时Cortex M核的配置
#define文件值描述
__NVIC_PRIO_BITS core_cm0.h(2)实现NVIC时优先级位的位数
__NVIC_PRIO_BITS core_cm3.h(2..8)实现NVIC时优先级位的位数
__MPU_PRESENT core_cm0.h/core_cm3.h(0,1)是否实现MPU
__Vendor_SysTickConfig core_cm0.h/core_cm3.h(1)
定义为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
这两个文件是实现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
这两个文件是实现Cortex-M3处理器CMSIS标准的CPAL层。
其中,头文件core_cm3.h
定义Cortex-M3核内外设的数据结构及其地址映射,另外它也提供一些访问Cortex-M0核内寄存器及外设的函数,这些函数定义为静态内联。
c文件core_cm3.c则定义了一些访问Cortex-M3核内寄存器的函数,例如对xPSR、MSP、PSP等寄存器的访问;另外还将一些汇编语言指令也定义为函数。
startup_device.s
汇编文件startup_device.s是在ARM提供的启动文件模板基础上,由各芯片厂商各自
修订而成的,例如STM32F107处理器的启动代码文件就是startup_stm32f10x_c1.s,它主要有三个功能。
配置并初始化堆和栈,例如startup_stm32f10x_c1.s对堆和栈的配置如下:
Stack_Size EQU0x00000400
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 EQU0x00000200
AREA HEAP,NOINIT,READWRITE,ALIGN=3
__heap_base
Heap_Mem SPACE Heap_Size
__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 R0,=Heap_Mem
LDR R1,=(Stack_Mem+Stack_Size)
LDR R2,=(Heap_Mem+Heap_Size)
LDR R3,=Stack_Mem
BX LR
定义中断向量表及相应的中断处理函数。
例如,startup_stm32f10x_c1.s定义中断
向量表如下:
;Vector Table Mapped to Address0at 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
DCD0;Reserved
DCD0;Reserved
DCD0;Reserved
DCD0;Reserved
DCD SVC_Handler;SVCall Handler
DCD DebugMon_Handler;Debug Monitor Handler
DCD0;Reserved
DCD PendSV_Handler;PendSV Handler
DCD SysTick_Handler;SysTick Handler
;External Interrupts
DCD WWDG_IRQHandler;Window Watchdog
:
:
DCD CAN2_SCE_IRQHandler;CAN2SCE
DCD OTG_FS_IRQHandler;USB OTG FS
__Vectors_End
__Vectors_Size EQU__Vectors_End-__Vectors
AREA|.text|,CODE,READONLY
所有中断处理函数均定义为弱函数;除了Reset_Handler之外,其他中断处理均为哑
函数。
这样所有中断处理函数的名称都已经被定义好了,需要实现时只要在用户代码中重写该函数即可。
例如,startup_stm32f10x_c1.s中定义中断处理函数如下:
;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
:
:
SysTick_Handler PROC
EXPORT SysTick_Handler[WEAK]
B.
ENDP
Default_Handler PROC
EXPORT WWDG_IRQHandler[WEAK]
EXPORT PVD_IRQHandler[WEAK]
:
:
EXPORT CAN2_SCE_IRQHandler[WEAK]
EXPORT OTG_FS_IRQHandler[WEAK]
WWDG_IRQHandler
PVD_IRQHandler
:
:
CAN2_SCE_IRQHandler
OTG_FS_IRQHandler
B.
ENDP
引导到__main()函数,完成C库函数初始化并最终引导到应用程序的main()函数去。
上面汇编代码中的中断处理函数Rest_Handler就完成此功能。
system_<device>.h和system_<device>.c
system_<device>.h和system_<device>.c文件是由ARM提供模板,各芯片厂商根据
自己芯片的特性来实现。
一般是提供处理器的系统初始化配置函数,以及包含系统时钟频率的全局变量。
按CMSIS标准的最低要求,system_<device>.c中必须要定义SetSysClock 和SystemCoreClockUpdate两个函数,和全局变量SystemCoreClock。
STM32固件库V3.3.0的CMSIS文件简析
[2011-2-2311:26:00|By:banyai
2011-8-16Edit By:tony
STM32的V3.3.0库,内有CMSIS的文件夹为arm Cortex微控制器软件接口标准,现在将我实际工作中的作一个简要分析:1.选择启动文件:根据自己所用的芯片的型号,选择正确的启动文件。
这个根据数据手册上的划分。
例如STM32F101VBT6,就选择startup_stm32f10x_md.s,在这个文件里,首选要定义自已的堆和栈的大小,这个根据自已的需要确定。
文件中已经定义好了中断向量的位置及堆和栈的初始化操作。
Reset_Handler PROC
EXPORT Reset_Handler[WEAK]
IMPORT__main
IMPORT SystemInit
LDR R0,=SystemInit
BLX R0
LDR R0,=__main
BX R0
ENDP
从上面这段文字中,可以看到,在系统复位后,先执行SystemInit(),再进入main()函数。
SystemInit()在文件system_stm32f10x.c中定义,我们稍后再说。
2.stm32f10x.h:这个头文件包含了STM32的大部份定义:
a.定义芯片的类型,如#define STM32F10x_MD
b.定义是否包含标准库,#define USE_STDPERIPH_DRIVER
c.定义外部振荡器频率,#define HSE_VALUE
上面三个定义,建议在main.c文件中刚开始就定义好,或者是在编译器选项中定义好,这样就可以不修改这个文件了。
d.定义中断号
e.包含core_cm3.h,system_stm32f10x.h
f.定义数据类型,u8,s8等为了兼容以前的库所定义的数据,建议程序中用标准的uint8_t这样的类型。
此外还定义了bool,FlagStatus,alStatus及ErrorStatus
g.定义外设结构体,地址及用到的数据常量。
h.包含stm32f10x_conf.h来配置外设
i.定义位操作的宏
3.system_stm32f10x.h和.c,这两个文件中:
a.定义一个全局变量uint32_t SystemCoreClock:系统时钟频率与你选择有关
b.SystemInit():这个函数就是启动文件中调用的函数
(1)在system_stm32f10x.c的开始部份,选择相关的系统时钟频率,
如#define SYSCLK_FREQ_24MHz24000000
(2)通过SystemInit()函数,就将SYSCLK=HCLK=PCLK1=PCLK2=PLL 输出24MHz。
注意:这个频率为HSE为8MHz时为条件,如果HSE不为8MHz或用HSI
时,就会有问题。
c.SystemCoreClockUpdate():更新SystemCoreClock的值,与系统频率一致。
可能看到,这个文件中的RCC设置很有局限性,所以在程序中,可以不用它,而用标准库存中的stm32f10x_rcc中的函数进行设置。
4.stm32f10x_conf.h
a.配置需要的标准外设库,需要用到的外设,把相应头文件包含进去就可以。
b.定义assert_Param的模式,选择#define USE_FULL_ASSERT时,断言输出问题所在的位置,在调试时很有用,在正式版本时,把它注释掉即可。
5.core_cm3:与CM3内核相关的操作,重点如下:
a.在MDK中,开总中断:__enable_irq();关总中断:__disable_irq();
b.中断处理程序:
(1)NVIC_SetPriorityGrouping(uint32_t PriorityGroup);
设置中断组,这里的值只能是0~7,在STM32中,只能用8位中的前4位来设置组,可以分为两部份:抢占优先级和亚优先级。
这个数值就是亚优先级开始的位。
它前面的位是抢占式优先级的位。
例如:NVIC_SetPriorityGrouping(5),那么D7,D6表示抢占式优先级(0~3),D5,D4表示亚优先级(0~3)。
优先级数值越小,优先级越高。
抢占式优先级高的中断可以打断抢占式优先级低的中断。
抢占式优先级相同,亚优先级不同的两个中断,如果同时到来,先执行亚优先级高的中断,再执行亚优先级低的中断,但不能打断。
(2)NVIC_EnableIRQ(IRQn_Type IRQn);使能一个中断
(3)NVIC_DisableIRQ(IRQn_Type IRQn);禁止一个中断
(4)NVIC_SetPriority(IRQn_Type IRQn,uint32priority);设置一个中断的优先级
(5)NVIC_EncodePriority(uint32_t PriorityGroup,uint32_t PreemptPriority,uint32_t SubPriority);
(4)和(5)通常一起使用,这样设置更直观,例如要将外部中断0设为抢占式优先级为0,亚优先级为2,则:
NVIC_SetPriority(EXTI0_IRQn,NVIC_EncodePriority(5,0,2));
注意PriorityGroup的参数应与(1)中设置的一致。
除了这种方式设置中断外,也可以使用标准库中的misc中的中断设置函数来操作。
c.SysTick_Config(uint32_t ticks):设置系统嘀嗒时钟并使能中断
在STM32中与CM3内核描述不太一样,这个时钟源有两个选择:AHB/8和AHB,在该函数中是选择了HCLK(SysTick_CTRL_CLKSOURCE_Msk),所以定时时间=ticks/ HCLK,当要定时10ms,而HCLK为24MHz时,ticks=10000*24=240000。
如果需要选择HCLK/8,可以直接修改这个函数,或在这个函数后跟随misc中的SysTick_CLKSourceConfig(uint32_t SysTick_CLKSource)来设置。
d.NVIC_SystemReset():复位芯片。
6.stm32f10x_it中断实现,在这里编写相应的中断服务函数。
7.还需要注意的一点是:进入main函数后,除了设置嘀嗒时钟和中断外,在操作各外设之前,调用:RCC_AHBPeriphClockCmd(),RCC_APB1PeriphClockCmd(),RCC_APB2PeriphCloc kCmd(),启动相应的时钟,否则外设就不能正常工作。