第5章
CHAPTER 5


ARM微控制器开发







学习嵌入式系统的根本目的在于应用微控制器进行应用系统设计。为了使得读者尽快进入应用开发的学习状态,本章介绍CortexM3微控制器应用系统的开发方法和过程。这些方法对于后续的知识点学习具有重要意义。
5.1开发流程
ARM微控制器可以使用的开发工具链有很多种,它们大多数都支持C和汇编语言。开发嵌入式工程时可以使用C语言,也可以用汇编,或者两者混合编程。一般情况下,程序代码生成流程如图51所示。



图51程序代码的生成流程


需要使用汇编实现的工程,则使用汇编器将汇编源代码转换成目标文件,工程中所有的目标代码被链接生成一个可执行映像。除了程序代码,目标文件和可执行映像中也可能含有各种调试信息。
大多数的简单应用程序可以完全用C语言编写。C编译器将C程序代码编译成目标代码,然后由链接器生成程序映像文件。
生成可执行映像之后,可以将其下载到微控制器的Flash存储器或内存中进行测试。大多数开发工具都包含了一个接口友好的集成开发环境(IDE),当其与在线调试器(有时也被称作在线仿真器ICE)配合使用时,可以分步进行以下工作: 创建工程,编译应用程序,然后下载嵌入式应用程序到微控制器中。开发过程如图52所示。



图52开发过程


5.2处理器的启动过程
为了保存编译好的程序代码,大多数的现代微控制器都包含片上Flash存储器。程序代码以二进制机器码的形式存放在Flash存储器中,因此汇编语言程序代码必须经过汇编,而C程序代码必须经过编译,才能烧写到Flash中。有些微控制器可能还配备了一个独立的启动ROM,里面有一个小的Boot Loader程序。微控制器启动以后,在执行Flash中的用户程序前,Boot Loader会首先运行。大多数情况下,Boot Loader都是固定的,只有Flash存储器中的用户程序才是可变的。
程序代码烧写到Flash存储器以后,处理器就可以访问程序了。复位以后,处理器将会运行复位流程。若使用C语言编程,则处理器复位以后的工作过程如图53所示。



图53处理器复位以后的工作过程


在复位流程中,处理器会取出MSP的初始值和复位向量,然后开始执行复位处理,这些所需信息都放在一个叫作启动代码的程序文件中。启动代码中的复位处理可能还会履行系统初始化的职责(例如,时钟控制电路和锁相环PLL的初始化),有些情况下,系统初始化是在C程序的main()函数中开始的。例如,如果在开发中使用Keil微控制器开发套件(MDK),工程创建向导可以将所选芯片对应的默认启动代码复制到工程中。
对于用C语言开发的应用程序,在进入主流程以前,启动代码就已经开始执行,并对应用程序用到的变量和内存等进行了初始化。无须编程者考虑启动代码,因为C语言开发工具会将其自动插入程序映像中。而对于使用汇编语言开发的应用程序,许多工作需要开发者自行完成,例如设置堆栈、设置程序的入口等。第4章提供了完整的汇编语言程序框架。
执行完C启动代码以后,应用程序就开始执行了。应用程序通常包含以下几个部分: 
 硬件初始化(如时钟、PLL和外设); 
 应用程序的处理部分; 
 中断服务程序。
另外,应用程序可能还会用C库函数,此时,C编译器/链接器会将所需的库函数纳入编译好的程序映像中。
硬件初始化可能会涉及一系列的外设、系统控制寄存器及CortexM3中的中断控制寄存器。如果在复位处理时没有进行处理,系统时钟控制和PLL也需要进行初始化。外设初始化完成后,程序就可以继续执行应用程序处理部分了。
5.3输入和输出接口
在许多嵌入式系统中可用的输入和输出可能有数字和模拟输入/输出(I/O)、UART、I2C和SPI等。许多微控制器还提供USB、以太网、CAN、LCD以及SD卡等接口,这些接口是由微控制器的外设控制的。
CortexM3的寄存器映射到了系统空间,并且它们还控制着外设。有些外设要比8位机和16位机上的更加复杂,配置时可能会涉及更多的寄存器。
外设的典型初始化过程一般包括以下步骤: 
(1) 配置时钟控制回路,使能外设的时钟信号,如果有必要,那么初始化相应的引脚。
在许多低功耗微控制器中,时钟信号被分为了许多路,而且为了降低功耗,它们可以单独开关。大多数时钟信号默认都是关闭的,配置外设前通常需要使能相应的时钟。有些情况下,用户可能还需要使能外设总线系统的时钟。
(2) 配置I/O,大多数微控制器的引脚都是复用的,需要对I/O引脚的功能进行配置,以确保外设接口正常工作。另外,有些微控制器的I/O引脚的电气特性也是可以配置的,这样也就增加了配置步骤。
(3) 配置外设,大多数接口外设都有多个可编程的控制寄存器,因此,为了确保外设工作正常,就需要对寄存器进行一系列的编程操作。
(4) 配置中断,如果外设操作需要中断处理,就需要另外配置中断控制器(例如CortexM3的NVIC)。
为了方便软件开发,大多数微控制器供应商都会为外设编程提供设备驱动库。
5.4程序映像
CortexM3的程序映像一般包含以下几个部分: 
 向量表; 
 C启动代码; 
 程序代码(应用程序代码和数据); 
 C库代码(C库函数的程序代码,链接时插入)。
1. 向量表
向量表可以用C语言或汇编语言实现。由于向量表的入口需要编译器和链接器生成的内容,所以向量表代码的实现细节同开发工具链相关。例如,栈指针的初始值被链接到链接器生成的栈空间地址,而复位向量则指向了C启动代码的地址,这些都是同编译器相关的。
Keil MDK创建STM32工程时,则将向量表作为汇编启动代码的一部分,并且使用定义常量数据(DCD)指令创建。ST公司提供的启动文件startup_stm32f10x_hd.s使用汇编实现向量表,文件的部分内容如下(在第6章创建C语言工程文件时自动加入工程中,可以直接打开查看完整的内容): 

Stack_SizeEQU0x00000400

AREASTACK, NOINIT, READWRITE, ALIGN=3

Stack_MemSPACEStack_Size   ;分配栈空间

__initial_sp

Heap_SizeEQU0x00000200

AREAHEAP, NOINIT, READWRITE, ALIGN=3

__heap_base

Heap_Mem  SPACEHeap_Size   ;分配堆空间

__heap_limit

PRESERVE8    ; 表明该文件中的代码预留8字节对齐的栈

THUMB

; 向量表,复位时映射到地址0

AREARESET, DATA, READONLY

EXPORT__Vectors

EXPORT  __Vectors_End

EXPORT  __Vectors_Size

__VectorsDCD__initial_sp           ;栈顶

DCDReset_Handler         ;复位处理程序入口地址

DCDNMI_Handler            ;NMI处理程序入口地址

DCDHardFault_Handler     ;硬件错误处理程序入口地址

DCDMemManage_Handler     ;MPU Fault Handler

DCDBusFault_Handler      ;Bus Fault Handler

DCDUsageFault_Handler    ;Usage Fault Handler

DCD0                          ;Reserved(保留,占位用)

DCD0                          ;Reserved(保留,占位用)

DCD0                          ;Reserved(保留,占位用)

DCD0                          ;Reserved(保留,占位用)

DCDSVC_Handler              ;SVCall Handler(SVCall处理)

DCDDebugMon_Handler       ;Debug Monitor Handler

DCD0                          ;Reserved(保留,占位用)

DCDPendSV_Handler         ;PendSV Handler(PendSV处理)

DCDSysTick_Handler        ;SysTick Handler(SysTick处理)

;外部中断

DCDWWDG_IRQHandler        ;Window Watchdog

DCDPVD_IRQHandler         ;PVD through EXTI Line detect

DCDTAMPER_IRQHandler      ;Tamper

DCDRTC_IRQHandler             ;RTC

DCDFLASH_IRQHandler           ;Flash

DCDRCC_IRQHandler             ;RCC

DCDEXTI0_IRQHandler           ;EXTI Line 0

......

__Vectors_End



__Vectors_Size  EQU  __Vectors_End - __Vectors



AREA|.text|, CODE, READONLY

; Reset handler

Reset_HandlerPROC

EXPORTReset_Handler[WEAK]

IMPORT__main

IMPORTSystemInit

LDRR0, =SystemInit

BLXR0               

LDRR0, =__main

BXR0

ENDP



; Dummy Exception Handlers (infinite loops which can be modified)



NMI_Handler     PROC

EXPORTNMI_Handler[WEAK]

B .

ENDP

HardFault_Handler\

PROC

EXPORT  HardFault_Handler[WEAK]

B.

ENDP

MemManage_Handler\

PROC

EXPORTMemManage_Handler [WEAK]

B.

ENDP

BusFault_Handler\

PROC

EXPORTBusFault_Handler[WEAK]

B.

ENDP

UsageFault_Handler\

PROC

EXPORTUsageFault_Handler[WEAK]

B.

ENDP

SVC_HandlerPROC

EXPORTSVC_Handler[WEAK]

B.

ENDP

DebugMon_Handler\

PROC

EXPORTDebugMon_Handler        [WEAK]

B.

ENDP

PendSV_HandlerPROC

EXPORT  PendSV_Handler[WEAK]

B.

ENDP

SysTick_HandlerPROC

EXPORTSysTick_Handler[WEAK]

B.

ENDP

Default_HandlerPROC

EXPORTWWDG_IRQHandler[WEAK]

EXPORTPVD_IRQHandler[WEAK]

EXPORTTAMPER_IRQHandler [WEAK]

EXPORT RTC_IRQHandler  [WEAK]

EXPORT  FLASH_IRQHandler     [WEAK]

EXPORT  RCC_IRQHandler             [WEAK]

EXPORT  EXTI0_IRQHandler           [WEAK]

......

WWDG_IRQHandler

PVD_IRQHandler

TAMPER_IRQHandler

RTC_IRQHandler

FLASH_IRQHandler

RCC_IRQHandler

EXTI0_IRQHandler

...

B.

ENDP

ALIGN

;***********************************************************************

; 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

ALIGN

ENDIF

END

在上面的例子中,向量表被赋予了一个段名RESET。为了将向量表置于系统存储器映射的开头(地址为0x00000000),链接文件或命令行选项需要知道段的名字,以便链接器能够正确识别向量并对其进行地址映射。复位向量一般指向C启动代码的开头。
2. C启动代码
C启动代码用于设置像全局变量之类的数据,也会清零加载时未被初始化的内存区域。对于使用malloc等C函数的应用程序,C启动代码还需要初始化堆空间的控制变量。初始化完成后,启动代码跳转到main( )程序执行。
C启动代码由编译器/链接器自动嵌入到程序中,并且是和开发工具链相关的,而只使用汇编代码编程则可能不存在C启动代码。
3. 程序代码
用户指定的任务是由应用程序的指令完成的,除了指令以外,还有以下各类数据: 
 变量的初始值。函数或子程序中的局部变量需要初始化,这些初始值会在程序执行期间被赋给相应的变量。
 程序代码中的常量。应用程序中的常量数据有多种用法,如数据值、外设寄存器的地址和常量字符串等,这些数据在程序映像中一般作为数据块放在一起。
 应用程序可能也会包括其他的常量,比如查找表和图像数据,它们也被合并在程序映像中。
4. C库代码
当使用特定的C/C++库函数时,它们的库代码就会由链接器嵌入到程序映像中。另外,有些数据处理任务需要浮点数或除法运算,在进行这些运算时,C库代码也会被包含进来。
5.5C语言开发ARM应用
1. 数据类型
C语言支持多个“标准”数据类型,不过,数据类型的使用可能还与处理器的体系结构和C编译器相关。对于包括CortexM3在内的ARM处理器,所有的C编译器都支持如表51所示的数据类型。


表51CortexM3处理器支持的数据类型及长度



C和C99数据类型位数范围(有符号)范围(无符号)
char, int8_t, uint8_t8-128~1270~255
short, int16_t, uint16_t16-32768~327670~65535
int, int32_t, uint32_t32-2147483648~21474836470~4294967265
long32-2147483648~21474836470~4294967265
long long, int64_t, uint64_t64-263~263-10~264-1
float32-3.4028234×1038~3.4028234×1038
double
64
-1.7976931348623157×10308~
1.7976931348623157×10308
续表



C和C99数据类型位数范围(有符号)范围(无符号)
long double
64
-1.7976931348623157×10308~
1.7976931348623157×10308
pointers320x0~0xFFFFFFFF
enum8/16/32可能的最小数据类型
bool(C++), _Bool(C)8真或假
wchar_t160~65535

2. 用C语言操作外设
除了变量以外,微控制器的C应用程序通常需要操作外设。对于ARM CortexM3微控制器,外设寄存器被映射到系统存储器空间,可以通过指针访问它们。使用微控制器供应商提供的设备驱动可以简化开发任务,并且增强软件在不同平台间的可移植性。如果需要直接访问外设寄存器可以使用以下方法。
如果只是简单访问几个寄存器可以使用下面的方法将外设寄存器定义为指针: 

#define PERIPH_BASE((uint32_t)0x40000000)  //外设基地址

#define APB1PERIPH_BASEPERIPH_BASE

#define USART1_BASE(APB2PERIPH_BASE + 0x3800)

#define USART1((USART_TypeDef *) USART1_BASE)

#define USART2_BASE(APB1PERIPH_BASE + 0x4400)

#define USART2((USART_TypeDef *) USART2_BASE)

结构体USART_TypeDef的定义如下: 

typedef struct

{

__IO uint16_t SR;

uint16_t  RESERVED0;

__IO uint16_t DR;

uint16_t  RESERVED1;

__IO uint16_t BRR;

uint16_t  RESERVED2;

__IO uint16_t CR1;

uint16_t  RESERVED3;

__IO uint16_t CR2;

uint16_t  RESERVED4;

__IO uint16_t CR3;

uint16_t  RESERVED5;

__IO uint16_t GTPR;

uint16_t  RESERVED6;

} USART_TypeDef;

使用USART1发送一个字节的数据时,可以使用下面的语句: 

USART1->DR = mydata;

用同样的方法可以操作USART2。
从上述方法可以看出,同一个外设寄存器的结构体可以被多个外设实体共用,这样也使得程序维护变得容易。另外,由于立即数存储的减少,编译出的程序代码也会变小。
若进一步处理可以将外设操作封装成函数的形式,多个外设实体将其基指针作为参数进行函数调用。例如,利用串口发送一个字节数据,可以封装成如下的函数: 

void USART_SendData(USART_TypeDef* USARTx, uint16_t Data)

{

/* Check the parameters */

assert_param(IS_USART_ALL_PERIPH(USARTx));

assert_param(IS_USART_DATA(Data)); 



/* Transmit Data */

USARTx->DR = (Data & (uint16_t)0x01FF);

}

后面要介绍的STM32固件库函数就是基于这种思路设计的。
应该注意的是,在定义USART_TypeDef结构时,使用了__IO变量类型,该类型实质上是volatile的宏定义(该宏定义包含在core_m3.h文件中)。外设访问定义指针时,需要使用volatile关键字。volatile用于防止相关变量被优化。例如对外部寄存器的读写。对有些外部设备的寄存器来说,读写操作可能都会引发一定硬件操作,但是如果不加volatile,编译器会把这些寄存器作为普通变量处理,例如连续多次对同一地址写入,会被优化为只有最后一次写入。另一个使用场合是中断。如果一个全局变量,在中断函数和普通函数中都用到过,那么最好对这个变量加volatile修饰。否则在普通函数中,可能会仅从寄存器里读取这个变量以便加快速度,而不去实际地址读取该变量。
3. Cortex微控制器软件接口标准(CMSIS)
1) CMSIS简介
随着嵌入式系统软件复杂性的增加,软件代码的兼容性和可重用性变得更加重要。可重用的程序能够减少后续项目的开发时间,也就加快了产品推向市场的速度; 而软件的兼容性则有助于第三方软件组件的使用。
为了使软件产品具有高度的兼容性和可移植性,ARM公司与许多微控制器供应商和软件方案供应商共同努力,开发了一个通用的软件框架CMSIS,该框架适用于大多数的CortexM处理器以及CortexM微控制器产品。CMSIS针对处理器特性提供了标准化的操作函数。
CMSIS一般是作为微控制器厂商提供的设备驱动库的一部分来使用的。为了使用诸如NVIC和系统控制功能等处理器特性,CMSIS提供了一种标准化的软件接口。CMSIS对多种微控制器厂商都是统一的,并已被多种C编译器和开发工具(包括Keil MDK)所支持。
2) CMSIS的标准化
CMSIS为嵌入式软件提供了以下标准化的内容: 
 标准化的操作函数,用于访问NVIC、系统控制块(SCB),SysTick的中断控制和初始化。
 NVIC、SCB和SysTick寄存器的标准化定义,为了达到最佳的可移植性,应该使用这些标准化的操作函数; 不过,有些情况下,需要直接操作NVIC、SCB和SysTick的寄存器。这时,标准化的寄存器定义就能提高软件的可移植性。
 使用CortexM微控制器特殊指令的标准化函数。有些指令不能由普通的C代码生成,如果需要这些指令,则可以使用CMSIS提供的这类函数来实现; 否则,用户就不得不使用C编译器提供的内在函数或者嵌入汇编代码,这样做就会依赖于开发工具,并且降低代码的可移植性。
 系统异常处理的标准化命名。嵌入式操作系统往往需要系统异常,当系统异常都有了标准化的命名以后,在一个操作系统中支持不同的设备驱动库也就容易了。
 系统初始化函数的标准化命名。通用的系统初始化函数被命名为void SystemInit(void),这就使得软件开发人员在建立自己工程时花费的力气更小。
 为时钟频率信息建立标准化的变量。这个变量为SystemCoreClock(CMSIS v1. 30及之后),用于确定处理器的时钟频率。
CMSIS还提供以下内容: 
 设备驱动库的通用平台,这使得每个设备驱动库看起来都是一样的,也让初学者容易学习,并且方便软件移植。
 在将来的CMSIS的发行版中可能会纳入一套通用的通信访问函数,以便已经开发的中间件(middleware)无须移植就能在不同设备上重复使用。
CMSIS是为了满足基本操作的兼容性而开发的,微控制器供应商为了加强其软件解决方案可以增加函数接口,以免CMSIS限制嵌入式产品的功能和性能。
3) CMSIS的组织结构
CMSIS的组织结构如图54所示。



图54CMSIS的组织结构



CMSIS可以分为以下4层: 
(1) 核心外设访问层。
命名定义、地址定义以及访问核心寄存器和NVIC、SCB以及SysTick等核心外设的辅助功能。
(2) 中间件访问层。
 典型嵌入式系统访问外设的通用方法; 
 面向通信接口,包括UART、Ethernet和SPI等; 
 嵌入式软件能够在任何支持特定通信接口的Cortex微控制器上使用。
(3) 设备外设访问层(MCU相关)。
寄存器名称定义、地址定义以及访问外设的设备驱动代码。
(4) 外设的访问函数(MCU相关)。
可选的外设辅助函数。
4) CMSIS的使用
CMSIS被集成在微控制器供应商提供的设备驱动包中,如果使用设备驱动库进行软件开发,那么就已经在使用CMSIS了。
对于C程序代码,通常只需要包含微控制器供应商提供的设备驱动库中的头文件。这个头文件又包含了其他所有需要的头文件,包括CMSIS特性和外设驱动等。
也可以包含符合CMSIS的启动代码,它们可以是C代码也可以是汇编代码,CMSIS为不同开发工具链定制了各种版本的启动代码。
一个使用CMSIS包建立的简单工程如图55所示。



图55在工程中使用CMSIS


其中,<device>的名字由实际的微控制器设备决定(例如STM32F10x系列微控制器对应的文件名为system_stm32f10x.c)。当使用设备驱动库提供的头文件时,会自动包含其他所需的头文件(例如STM32F10x系列微控制器对应的文件名为system_ stm32f10x.h)。
一个使用CMSIS的简单例子如下: 

#include "stm32f10x.h"

void delay(u32 delaytime);

int main(void)

{

GPIO_InitTypeDef GPIO_InitStructure;



RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOD, ENABLE); 

GPIO_InitStructure.GPIO_Pin =  GPIO_Pin_8;

GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;        

GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;

GPIO_Init(GPIOD, &GPIO_InitStructure); 

while(1)

{

GPIO_SetBits(GPIOD, GPIO_Pin_8);

delay(3000000);

GPIO_ResetBits(GPIOD, GPIO_Pin_8);

delay(3000000);

}

}

void delay(u32 delaytime)

{

while(delaytime--);

}

5.6固件库
意法半导体(STMicroelectronics,以下简称ST)为了方便用户开发程序,提供了一套丰富的STM32固件库。使用固件库开发应用系统可以大大提高用户的开发效率。
5.6.1基于固件库开发和直接操作寄存器的区别
固件库就是函数的集合,固件库函数的作用是向下负责直接操作寄存器,向上提供用户函数调用的接口(API)。
在STC15单片机的学习和开发中,使用C语言编程时,通常的做法是直接操作寄存器,比如要控制P2口的状态,使用下面的代码直接操作寄存器: 

P2=0x11;

而在STM32的开发中,同样可以操作寄存器: 

GPIOx->BRR = 0x0011;

这种方法的缺点是,用户只有掌握每个寄存器的用法,才能正确使用STM32,而STM32的寄存器特别多,记起来很麻烦。为了简化编程,ST公司推出了官方固件库,该库是一个固件函数包(也称为驱动程序),由程序、数据结构和宏组成,包括对微控制器所有外设的定义和操作。
每个外设驱动都由一组函数组成,这组函数覆盖了该外设的所有操作。每个器件的开发都由一个通用API(Application Programming Interface,应用编程接口)驱动,API对该驱动程序的结构、函数和参数名称都进行了标准化。
固件库将寄存器底层操作都装起来,提供一整套API供开发者调用。大多数场合下,用户不需要知道操作的是哪个寄存器,只需要知道调用哪些函数即可。通过使用固件函数库,无须深入掌握细节,用户也可以轻松应用每一个外设。因此,使用固态函数库可以大大减少用户的程序编写时间,进而降低开发成本。并且,所有的驱动程序源代码都符合ANSIC标准,不受开发环境影响。
固态函数库通过校验所有库函数的输入值来实现实时错误检测。该动态校验提高了软件的鲁棒性。实时检测适合于用户应用程序的开发和调试,但会增加成本,可以在最终应用程序代码中移去,以优化代码大小和执行速度。由于固件库函数是通用的,并且包含了对所有外设的操作,这必定会影响程序代码的大小和执行速度,因此,对于代码大小和执行速度有严格要求的情况,可以考虑使用直接对寄存器操作来满足要求。
例如,上述控制BRR寄存器实现电平控制的功能要求,官方库封装了下面的API函数: 

void GPIO_ResetBits(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin)

{

GPIOx->BRR = GPIO_Pin;

}


有了上述函数,就不需要再直接去操作BRR寄存器了(操作BRR寄存器的工作在API函数内部完成),只需要知道GPIO_ResetBits()函数怎么使用即可。在对外设的工作原理有一定的了解之后,再去看固件库函数,基本上从函数名字就能看出这个函数的功能。
任何处理器,归根结底都是要对处理器的寄存器进行操作。因此,第6章给出了汇编语言、C语言直接操作寄存器和使用固态库函数3个版本的程序代码供读者参考。从第7章开始,只给出使用固态库函数操作外设的方法,汇编语言和C语言直接操作寄存器的方法请读者自行学习。
5.6.2STM32固件库
1. STM32固件库函数
STM32固件库就是函数的集合。ST官方库是根据CMSIS标准设计的。从图54可以看出,CMSIS应用程序的基本结构分为3个基本功能层。
(1) 核内外设访问层: ARM公司提供的访问,定义处理器内部寄存器地址以及功能函数。
(2) 中间件访问层: 定义访问中间件的通用API,由ARM公司提供。
(3) 外设访问层: 定义硬件寄存器的地址以及外设的访问函数。
从图54可以看出,CMSIS层在整个系统中处于中间层,向下负责与内核和各个外设直接打交道,向上提供实时操作系统用户程序调用的函数接口。芯片生产公司设计的库函数必须按照CMSIS规范来设计,这样才能保证系统有良好的可移植性。例如,在使用STM32芯片的时候首先要进行系统初始化,CMSIS规范规定,系统初始化函数名字必须为SystemInit,所以各个芯片公司写自己的库函数时就必须用SystemInit对系统进行初始化。CMSIS还对各个外设驱动文件的文件名字以及函数名字等规范化。如上述GPIO_ResetBits函数名字的定义就是遵循的CMSIS规范。
STM32固件库函数的命名规则如下: 
(1) PPP表示任一外设的缩写,如GPIO、ADC等。外设缩写如表52所示。


表52外设缩写表



缩写外 设 名 称缩写外 设 名 称
ADC模数转换器IWDG独立看门狗
BKP备份寄存器NVIC嵌套中断向量列表控制器
CAN控制器局域网模块PWR电源/功耗控制
CRCCRC计算单元RCC复位与时钟控制器
DAC数模转换器RTC实时时钟
DMA直接内存存取控制器SDIOSDIO接口
EXTI外部中断事件控制器SPI串行外设接口
FLASH闪存存储器SysTick系统嘀嗒定时器
FSMC灵活的静态存储器控制器TIM通用定时器
GPIO通用输入/输出端口TIM1高级控制定时器
I2CI2C总线接口USART通用同步异步接收发射端
I2SI2S总线接口WWDG窗口看门狗

(2) 系统、源程序文件和头文件命名都以“stm32f10x_”作为开头,例如,stm32f10x_conf.h。
(3) 常量仅被应用于一个文件的,定义于该文件中; 被应用于多个文件的,在对应头文件中定义。所有常量都由英文字母大写书写。
(4) 寄存器作为常量处理。它们的名字都以大写英文字母书写。
(5) 外设函数的命名以该外设的缩写加下画线为开头。每个单词的第一个字母都是大写英文字母,例如,SPI_SendData。在函数名中,只允许存在一个下画线,用以分隔外设缩写和函数名的其他部分。
(6) 名为PPP_Init的函数,其功能是根据PPP_InitTypeDef中指定的参数,初始化外设PPP,例如,TIM_Init。其中,PPP表示任一外设的缩写,如表52所示。
(7) 名为PPP_DeInit的函数,其功能为复位外设PPP的所有寄存器至默认值,例如,TIM_DeInit。
(8) 名为PPP_StructInit的函数,其功能为通过设置PPP_InitTypeDef结构中的各种参数来定义外设的功能,例如,USART_StructInit。
(9) 名为PPP_Cmd的函数,其功能为使能或者除能外设PPP,例如,SPI_Cmd。
(10) 名为PPP_ITConfig的函数,其功能为使能或者除能来自外设PPP某中断源,例如,RCC_ITConfig。
(11) 名为PPP_DMAConfig的函数,其功能为使能或者除能外设PPP的DMA接口,例如,TIM1_DMAConfig。用以配置外设功能的函数,总是以字符串“Config”结尾,例如GPIO_PinRemapConfig。
(12) 名为PPP_GetFlagStatus的函数,其功能为检查外设PPP某标志位被设置与否,例如,I2C_GetFlagStatus。
(13) 名为PPP_ClearFlag的函数,其功能为清除外设PPP标志位,例如,I2C_ClearFlag。
(14) 名为PPP_GetITStatus的函数,其功能为判断来自外设PPP的中断发生与否,例如,I2C_GetITStatus。
(15) 名为PPP_ClearITPendingBit的函数,其功能为清除外设PPP中断待处理标志位,例如,I2C_ClearITPendingBit。
2. 变量编码规则
在文件stm32f10x_type.h中定义了固件库函数中常用的变量。
1) 普通变量
固态函数库定义了24个普通变量类型,它们的类型和大小是固定的。

typedef signed long s32;

typedef signed short s16;

typedef signed char s8;

typedef signed long const sc32; /* Read Only */

typedef signed short const sc16; /* Read Only */

typedef signed char const sc8; /* Read Only */

typedef volatile signed long vs32;

typedef volatile signed short vs16;

typedef volatile signed char vs8;

typedef volatile signed long const vsc32; /* Read Only */

typedef volatile signed short const vsc16; /* Read Only */

typedef volatile signed char const vsc8; /* Read Only */

typedef unsigned long u32;

typedef unsigned short u16;

typedef unsigned char u8;

typedef unsigned long const uc32; /* Read Only */

typedef unsigned short const uc16; /* Read Only */

typedef unsigned char const uc8; /* Read Only */

typedef volatile unsigned long vu32;

typedef volatile unsigned short vu16;

typedef volatile unsigned char vu8;

typedef volatile unsigned long const vuc32; /* Read Only */

typedef volatile unsigned short const vuc16; /* Read Only */

typedef volatile unsigned char const vuc8; /* Read Only */

2) 布尔型变量
布尔型变量定义如下: 

typedef enum

{

FALSE = 0,

TRUE = !FALSE

} bool;

3) 标志位状态类型(FlagStatus type)
定义标志位类型的两个可能值为“设置”或“重置”(SET或RESET),定义如下: 

typedef enum

{

RESET = 0,

SET = !RESET

} FlagStatus;

4) 功能状态类型(FunctionalState type)
功能状态类型的两个可能值为“使能”或“除能”(ENABLE或DISABLE),定义如下: 

typedef enum

{

DISABLE = 0,

ENABLE = !DISABLE

} FunctionalState;

5) 错误状态类型(ErrorStatus type)
错误状态类型类型的两个可能值为“成功”或“出错”(SUCCESS或ERROR),定义如下: 

typedef enum

{

ERROR = 0,

SUCCESS = !ERROR

} ErrorStatus;

3. 外设
用户可以通过指向各个外设的指针访问各外设的控制寄存器。这些指针所指向的数据结构与各个外设的控制寄存器布局一一对应。
1) 外设控制寄存器结构
文件stm32f10x_map.h包含了所有外设控制寄存器的结构。例如,SPI寄存器结构的声明如下: 

typedef struct

{

vu16 CR1;

u16 RESERVED0;

vu16 CR2;

u16 RESERVED1;

vu16 SR;

u16 RESERVED2;

vu16 DR;

u16 RESERVED3;

vu16 CRCPR;

u16 RESERVED4;

vu16 RXCRCR;

u16 RESERVED5;

vu16 TXCRCR;

u16 RESERVED6;

} SPI_TypeDef;

其中,RESERVEDi(i为一个整数索引值)表示被保留区域。
2) 外设声明
文件stm32f10x.h包含了所有外设的声明。例如,SPI1外设的声明如下: 

#define PERIPH_BASE ((u32)0x40000000)

#define APB1PERIPH_BASE PERIPH_BASE

#define APB2PERIPH_BASE (PERIPH_BASE + 0x10000)

...

/* SPI1 Base Address definition*/

#define SPI1_BASE (APB2PERIPH_BASE + 0x3000)

...

#define SPI1 ((SPI_TypeDef *) SPI1_BASE)


4. 常用的固件函数库文件
常用的固件函数库文件如表53所示。


表53常用的固件库函数库文件描述



文件名描述
stm32f10x_conf.h
该头文件设置了所有使用到的外设,由不同的Define语句组成。用户可以在该文件中进行参数设置,可以利用模板使能或除能外设,可以修改外部振荡器的参数
stm32f10x.it.h该头文件包含了所有的中断处理程序的原型

stm32f10x_it.c

该源文件包含了所有的中断处理程序(如果未使用中断,则所有的函数体都为空)。用户可以加入自己的中断程序代码,对于指向同一个中断向量的多个不同中断请求,可以通过判断外设的中断标志位来确定到底是哪个中断源发生了中断
stm32f10x_lib.h主头文件,包含了其他头文件
stm32f10x_lib.c包括所有外设指针的定义、初始化等
stm32f10x.h该文件包含了外设存储器映像、寄存器数据结构和所有寄存器物理地址的声明
stm32f10x_type.h该文件包含所有其他文件使用的通用数据类型和枚举
stm32f10x_ppp.hPPP外设对应一个头文件,包含了该外设使用的函数原型、数据结构和枚举
stm32f10x_ppp.cPPP外设对应的源文件,包含了该外设使用的函数体

5.7习题
51简述ARM微控制器应用系统开发的一般过程。
52固件库函数和CMSIS的关系是怎样的?
53对照STM32F103VET6的存储器映射,查看5.4节给出的程序映像中断向量表各个中断的入口地址。