CMSIS设计的理解
一、CMSIS 是什么?
CMSIS 的全称是 Cortex Microcontroller Software Interface Standard,即 Cortex 微控制器软件接口标准。
简单来说,它是由 ARM 公司制定并提供的一套标准化软件框架。它的目的是为基于 ARM Cortex-M 处理器的芯片厂商和软件开发者提供一致性的软件接口。
您可以把它理解为芯片厂商和开发者之间的“普通话”。在没有“普通话”之前,每家芯片公司(如 ST、NXP、Microchip 等)都有自己的外设寄存器定义和函数库,导致为一家公司芯片写的代码很难移植到另一家。CMSIS 的出现就是为了解决这个碎片化问题。
二、CMSIS 的组成模块(核心内容)
其中最常用和核心的组件包括:
1. CMSIS-Core (Cortex-M)
这是最基础、最核心的部分,提供了对 Cortex-M 处理器本身的标准化访问。包含:
寄存器定义:对内核寄存器(如 NVIC, SCB, SysTick 等)的地址和位定义。
设备头文件:由芯片厂商提供,包含该型号芯片所有的外设寄存器定义(如
GPIOA->BSRR
)、中断向量表等。这个文件(如stm32f10x.h
)是您项目的基础。系统初始化函数:如
SystemInit()
,用于配置时钟和初始化关键系统。内核访问函数:用于开关中断、休眠模式、内存屏障指令等。例如
__enable_irq()
,__WFI()
。
2. CMSIS-DSP
一个功能强大且高度优化的数字信号处理库。包含:
数学函数:基本数学(乘、加)、快速数学(三角函数)、滤波器、矩阵运算、变换(FFT)、电机控制函数等。
优势:ARM 为 Cortex-M 内核进行了手写汇编优化,性能远超开发者自己编写的 C 代码。
3. CMSIS-RTOS API
一个为实时操作系统定义的通用 API 接口。
作用:应用程序通过调用 CMSIS-RTOS2 的接口(如
osThreadNew
,osMutexAcquire
),而不是操作系统的原生接口。这使得代码可以在不同的 RTOS(如 FreeRTOS, Azure RTOS, RT-Thread)之间无缝移植,因为只需要更换底层的 RTOS 实现适配层即可。
4. CMSIS-Driver
定义了通用外设驱动(如 UART, I2C, SPI, ETH, USB)的 API 标准。
目的:让中间件和应用程序可以不依赖具体的芯片型号,通过统一的驱动接口访问外设。芯片厂商或社区会提供符合此标准的驱动实现。
5. CMSIS-Pack
一个软件包分发机制。开发者可以通过 Keil MDK 的 Pack Installer 或类似的工具,一键安装芯片支持、设备驱动、中间件和示例代码,极大地简化了项目管理。
6. CMSIS-NN
专门为 Cortex-M 处理器优化的神经网络内核库。用于在微控制器上高效地运行机器学习推理模型,是 TinyML 领域的关键基础。
三、CMSIS 与MCU库的联系
拿stm32的hal库来举例,其关系如下:
核心关系概括
所以,CMSIS首先是ARM用来“约束”和“帮助”MCU设计商的工具。
2. MCU设计商(ST)的角色 & HAL库的目的
所以,HAL库是MCU设计商用来“服务”和“吸引”最终开发者的工具。
一个具体的例子:配置一个定时器中断
CMSIS 是 ARM 制定的基础标准。它定义了如何访问内核(如NVIC、SysTick)和芯片基本外设的通用方法。它更底层,更接近硬件。
HAL库 是 ST(意法半导体) 根据CMSIS标准,为自己家的STM32芯片实现的高级应用库。它基于CMSIS,但在其上构建了更易用、更抽象的一层。
为了更深入理解,我们再细化一下这个分工:
1. ARM 的角色 & CMSIS 的目的
ARM 只设计 Cortex-M CPU 核心(相当于设计了一个强大的“发动机和底盘平台”)。
但一个完整的MCU,除了CPU核心,还有内存、中断控制器、调试接口等,这些都是CPU核心必须的“贴身部件”。
CMSIS 的核心作用就在这里:ARM 通过 CMSIS-Core 为这些 “CPU核心及其贴身部件” 定义了统一的软件访问标准。这确保了无论哪个厂商,只要用了Cortex-M核心,操作内核寄存器、配置中断的方式都是一样的。
ST 这样的公司,拿到ARM的Cortex-M核心后,围绕它添加了GPIO、UART、I2C、ADC等各种各样的外设,最终做成STM32这颗具体的MCU(相当于在“发动机底盘”上造出了完整的“汽车”)。
对于开发者来说,他们面对的是成千上万个控制这些外设的寄存器,操作非常底层和复杂。
CMSIS层(ARM为MCU设计商提供的标准):
MCU设计商提供的设备头文件(如
stm32f4xx.h
)中,会使用CMSIS标准定义TIM2
外设的寄存器结构体。开发者可以使用
NVIC_EnableIRQ(TIM2_IRQn);
这个CMSIS函数来开启定时器2的中断,因为这个中断控制器是CPU核心的一部分,遵循ARM标准。
HAL库层(MCU设计商为开发者提供的服务):
开发者调用
HAL_TIM_Base_Start_IT(&htim2);
这个HAL函数。这个HAL函数在内部帮我们做了所有事:
它使用CMSIS标准设置了内核的SysTick计时器。
它直接操作STM32独有的定时器外设寄存器(通过CMSIS定义的结构体)来配置预分频、重载值。
它最终还会调用
NVIC_EnableIRQ(TIM2_IRQn);
来开启中断。
四、CMSIS的接口设计
1. CMSIS:提供“插槽”和“命名规则”
ARM在制定CMSIS标准时,并不知道ST、NXP、TI这些公司会具体设计多少个定时器、多少个UART。因此,CMSIS所做的是:
定义一个标准化的“中断编号枚举”:它知道Cortex-M内核有一个叫做NVIC的中断控制器,所以它定义了
IRQn_Type
这个枚举类型,就像预留了一排标好数字的“插槽”(例如,位置0, 1, 2...)。声明一个“设备头文件”必须提供这个枚举的具体内容:它要求芯片厂商(如ST)必须提供一个
device_name.h
的头文件,在这个文件里,必须具体化这个IRQn_Type
枚举,给每个插槽赋予具体的意义。
2. MCU设计商:填充“插槽”
MCU设计商(如ST)根据自己芯片的实际设计,来填充CMSIS提供的“插槽”。
CMSIS 的“弱定义”框架(在通用头文件中):
c
// 这是ARM CMSIS框架定义的,一个“不完整”的枚举 // 它相当于说:“每个芯片厂商必须在这里定义你们自己的中断号” typedef enum IRQn {// ... 一些Cortex-M内核标准中断(如SysTick, PendSV)是固定的MemoryManagement_IRQn = -12,// ... 其他内核中断SVCall_IRQn = -5,DebugMonitor_IRQn = -4,PendSV_IRQn = -2,SysTick_IRQn = -1,// 非内核中断从这里开始,具体是什么,由芯片决定 } IRQn_Type;
ST(MCU设计商)的具体实现(在
stm32f4xx.h
中):c
// ST在这里具体填充了STM32F4系列芯片的中断号 typedef enum IRQn {// ... 内核中断部分与CMSIS框架完全一致// 非内核中断:ST根据自己芯片的设计来定义WWDG_IRQn = 0,// ... 其他中断EXTI0_IRQn = 6,EXTI1_IRQn = 7,// ...TIM2_IRQn = 28, // 您的例子:定时器2中断被分配到了28号插槽TIM3_IRQn = 29,// ...USART1_IRQn = 37, // 您的例子:串口1中断USART2_IRQn = 38,// ... } IRQn_Type;
3. 地址映射也是同样的道理
CMSIS:定义了“外设寄存器应该用一个结构体来组织”这个方法。
ST:根据自己设计的总线布局和外设寄存器地址,具体实现这个结构体。
例如,CMSIS说GPIO应该有个叫 BSRR
的寄存器。ST就在自己的头文件里,根据STM32芯片上GPIO外设的实际内存地址,定义:
c
#define GPIOA_BASE (AHB1PERIPH_BASE + 0x0000) #define GPIOA ((GPIO_TypeDef *) GPIOA_BASE) // 将地址强制转换为标准结构体类型// 这个GPIO_TypeDef的结构体模板是由CMSIS标准或ST遵循该标准定义的 typedef struct {__IO uint32_t MODER;__IO uint32_t OTYPER;// ... 其他寄存器__IO uint32_t BSRR; // 位设置/清除寄存器,位于某个特定偏移地址// ... } GPIO_TypeDef;