STM32固件库的CMSIS的学习

STM32固件库CMSIS简析(摘自网络2011-02)

 CMSIS软件层次CMSIS可以分为多个软件层次,分别由ARM公司、芯片供应商提供。

1、ARM Cortex™ 微控制器软件接口标准(CMSIS:Cortex Microcontroller Software Interface Standard)

        是 Cortex-M 处理器系列的与供应商无关的硬件抽象层。 使用CMSIS,可以为处理器和外设实现一致且简单的软件接口,从而简化软件的重用、缩短微控制器新开发人员的学习过程,并缩短新设备的上市时间。软件的创建被嵌入式行业公认为主要成本系数。通过在所有Cortex-M 芯片供应商产品中标准化软件接口,这一成本会明显降低,尤其是在创建新项目或将现有软件迁移到新设备时。最新版本的CMSIS[2]为3.5。

2结构

CMSIS是ARM公司与多家不同的芯片和软件供应商一起紧密合作定义的,提供了内核与外设、实时操作系统和中间设备之间的通用接口。
CMSIS软件层次

CMSIS可以分为多个软件层次,分别由ARM公司、芯片供应商提供。
其中ARM提供了下列部分,可用于多种编译器:
● 内核设备访问层:包含了用来访问内核的寄存器设备的名称定义,地址定义和助手函数。同时也为RTOS(实时操作系统)定义了独立于微控制器的接口,该接口包括调试通道定义。
● 中间设备访问层:为软件提供了访问外设的通用方法。芯片供应商应当修改中间设备访问层,以适应中间设备组件用到的微控制器上的外设。目前中间设备访问层仍处于开发过程中,本文不做详述。 芯片供应商扩展下列软件层:
● 微控制器外设访问层:提供片上所有外设的定义。
● 外设的访问函数(可选):为外设提供额外的助手函数。CMSIS为Cortex-Mx微控制器系统定义了:
● 访问外设寄存器的通用方法和定义异常向量的通用方法。
● 内核设备的寄存器名称和内核异常向量的名称。
● 独立于微控制器的RTOS接口,带调试通道。
● 中间设备组件接口(TCP/IP协议栈,闪存文件系统)。


CMSIS包含的组件

1、外围寄存器和中断定义: 适用于设备寄存器和中断的一致接口
2、内核外设函数:特定处理器功能和内核外设的访问函数
3、DSP 库:优化的信号处理算法,并为 SIMD 指令提供Cortex-M4 支持
4、系统视图说明(SVD):描述设备外设和中断的XML 文件。
该标准完全可扩展,可确保其适合于所有 Cortex-M处理器系列微控制器,从最小的8 KB 设备到具有复杂通信外设(如以太网或USB)的设备。(内核外设函数的内存要求少于1 KB 代码,少于10 字节RAM)。


基于CMSIS应用程序的基本结构CMSIS-外设访问层的文件

基于CMSIS应用程序的基本结构[3]

独立于编译器的文件:
● Cortex-M3内核及其设备文件(core_cm3.h + core_cm3.c)
─ 访问Cortex-M3内核及其设备:NVIC,SysTick等
─ 访问Cortex-M3的CPU寄存器和内核外设的函数
● 微控制器专用头文件(device.h)
─ 指定中断号码(与启动文件一致)
─ 外设寄存器定义(寄存器的基地址和布局)
─ 控制微控制器其他特有的功能的函数(可选)
● 微控制器专用系统文件(system_device.c)
─ 函数SystemInit,用来初始化微控制器
─ 函数Sysem_ExtMemCtl,用来配置外部存储器控制器。它位于文件
startup_stm32f10x_xx.s /.c,在跳转到main前调用
─SystemFrequncy,该值代表系统时钟频率
─ 微控制器的其他功能(可选)
编译器供应商+微控制器专用启动文件
● 编译器启动代码(汇编或者C)(startup_device.s)
─ 微控制器专用的中断处理程序列表(与头文件一致)
─ 弱定义(Weak)的中断处理程序默认函数(可以被用户代码覆盖)

CMSIS软件包详细目录

下载的CMSIS软件包含有Cortex M3核的支持文件以及基于Cortex M3核处理器的启动代码和库引导文件。

使用步骤以STM32F10XX序列处理为例,介绍CMSIS的使用步骤。

1、从ST官方网站下载3.0以上版本的标准外设库。
2、基于开发软件新建工程,建立相应的文件目录,注意询问是否加入启动文件时,选择“否”。
3、根据所选芯片,把Libraries\CMSIS\CM3\DeviceSupport\ST\STM32F10x\startup\arm中的启动代码加到工程中。
4、根据处理器FLASH容量的大小,选择启动代码。stm32f10x.h 的66-73 行表明了启动代码的类型。例如STM32F10X_HD 行表示大容量的,如果不需要直接注释掉即可。
5、选择是否启用标准外设库。stm32f10x.h的105行的USE_STDPERIPH_DRIVER用于确定是否启用,如果不启用直接注释掉。
6、确定处理器的频率。system_stm32f10x.c的110-115行用于确定处理器的频率,注释掉不需要的即可。例如处理器的频率为是36MHz,注释SYSCLK_FREQ_72MHz,去掉SYSCLK_FREQ_36MHz注释。

 

STM32的V3.5.0库,内有CMSIS的文件夹为arm Cortex微控制器软件接口标准,现将实际工作中应用作一个简要分析:

1.选择启动文件:

根据自己所用的芯片的型号,选择正确的启动文件。这个根据数据手册上的划分。例如STM32F103VBT6,就选择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 

END     

下面对上面的一段汇编进行解释,相同的就省略解释。

              Reset_Handler    PROC   //0x0复位入口

             EXPORT  Reset_Handler     [WEAK] // EXPORT或者GLOBAL:制定某标号代表的程序可为其它文件调用,即是声明一个全局标号,[WEAK]选项声明其他的同名标号优先于该标号被引用。声明一个可全局引用的标号Reset_Handler
             IMPORT  __main  //通知编译器当前文件要引用标号__main,但__main在其他源文件中定义
             IMPORT  SystemInit //通知编译器当前文件要引用标号SystemInit ,但SystemInit 在其他源文件中定义

                 LDR     R0, =SystemInit //
                 BLX     R0              //BLX指令进行ARM/Thumb状态切换并且保存返回地址的转移指令,放回地址(BLX指令指向下一条指令地址)保存链接寄存器R14
                 LDR     R0, =__main   //向通用寄存器传输任意不大于232的立即数,即__main,立即数用=32位立即数,等同于MOV R0,#_main
                 BX      R0             //BX指令进行ARM/Thumb状态切换但不保存返回地址的转移指令,R0为包含目标指令绝对地址的寄存器
                 END                   //结束

从上面这段文字中,可以看到,在系统复位后,先执行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.定义中断号stm32f10x_it.c    stm32f10x_it.h

        e.包含 core_cm3.hsystem_stm32f10x.h

        f.定义数据类型,u8s8等为了兼容以前的库所定义的数据,建议程序中用标准的uint8_t这样的类型。此外还定义了boolFlagStatusalStatusErrorStatus

        g.定义外设结构体,地址及用到的数据常量。

        h.包含stm32f10x_conf.h来配置外设

        i.定义位操作的宏

 

   3.system_stm32f10x.h.c,这两个文件中:

       a.定义一个全局变量 uint32_t SystemCoreClock: 系统时钟频率与你选择有关

       b.SystemInit():这个函数就是启动文件中调用的函数

        (1) system_stm32f10x.c的开始部份,选择相关的系统时钟频率,

             如#define  SYSCLK_FREQ_24MHz  24000000

        (2) 通过SystemInit()函数,就将SYSCLK = HCLK = PCLK1=PCLK2=PLL输出24MHz注意:这个频率为HSE8MHz时为条件,如果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),那么D7D6表示抢占式优先级(0~3),D5D4表示亚优先级(0~3)。优先级数值越小,优先级越高。抢占式优先级高的中断可以打断抢占式优先级低的中断。抢占式优先级相同,亚优先级不同的两个中断,如果同时到来,先执行亚优先级高的中断,再执行亚优先级低的中断,但不能打断。

可以理解为抢占式多任务。

         (2) NVIC_EnableIRQ(IRQn_Type IRQn);   使能一个中断EnableIRQ

         (3) NVIC_DisableIRQ(IRQn_Type IRQn);   禁止一个中断DisableIRQ

         (4) NVIC_SetPriority(IRQn_Type IRQn,uint32 priority); 设置一个中断的优先级

         (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/8AHB,在该函数中是选择了HCLK(SysTick_CTRL_CLKSOURCE_Msk),所以定时时间=ticks / HCLK,当要定时10ms,而HCLK24MHz时,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_APB2PeriphClockCmd(),启动相应的时钟,否则外设就不能正常工作。

 

在这里简单介绍一下NVIC

NVIC嵌套向量中断控制器 Nested Vectored Interrupt Controller

系统中断管理。——管理系统内部的中断,负责打开和关闭中断。
基础应用1,中断的初始化函数,包括设置中断向量表位置,和开启所需的中断两部分。所有程序中必须的。
用法:void NVIC_Configuration(void)
{
NVIC_InitTypeDef NVIC_InitStructure;//中断管理恢复默认参数
#ifdef  VECT_TAB_RAM  
//如果C/C++ Compiler\Preprocessor\Defined symbols中的定义了VECT_TAB_RAM(见程序库更改内容的表格)
NVIC_SetVectorTable(NVIC_VectTab_RAM, 0x0); //则在RAM调试
#else        //如果没有定义VECT_TAB_RAM
NVIC_SetVectorTable(NVIC_VectTab_FLASH, 0x0);//则在Flash里调试
#endif        //结束判断语句
//以下为中断的开启过程,不是所有程序必须的。
//NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); 
//设置NVIC优先级分组,方式。
//注:一共16个优先级,分为抢占式和响应式。两种优先级所占的数量由此代码确定,NVIC_PriorityGroup_x可以是01、 234,分别代表抢占优先级有124816个和响应优先级有168421个。规定两种优先级的数量后,所有的中断级别必须在其中选择,抢占级别高的会打断其他中断优先执行,而响应级别高的会在其他中断执行完优先执行。
//NVIC_InitStructure.NVIC_IRQChannel = 中断通道名;
//开中断,中断名称见函数库
//NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
//抢占优先级
//NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0; 
//响应优先级
//NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;//启动此通道的中断
//NVIC_Init(&NVIC_InitStructure);     //中断初始化
}

 

猜你喜欢

转载自www.cnblogs.com/yilangUAV/p/12684256.html