1 简介
STM32F10x标准外设库是一个完整的包,包含了所有标准外设的设备驱动,适用于STM32价值系列(高、中和低),互联系列,超高、高、中、低密度32-bit Flash的微处理器。
这个库是一个固件库包,包含了一系列程序、数据结构和宏定义,覆盖了STM32外设的所有功能。它包括了设备驱动的描述和对应外设的一系列示例。固件库使得用户不需要深入学习每个外设说明就可以使用,使用标准外设库有两个优点:它节省了编码时间同时减少了应用程序开发和集成的时间消耗。STM32F10x标准外设库完全符合Cortex微处理器软件接口标准。
2 架构
2.1 CMSIS标准
因为基于Cortex-M3的系列芯片采用的内核都是相同的,区别主要在于核外的片上外设的,这些差异却导致软件在同内核、不同外设的芯片上移植困难。为了解决不同芯片厂商生产的Cortex微控制器软件的兼容性问题,ARM与芯片厂商建立了CMSIS标准。
ARM® Cortex™ 微控制器软件接口标准 (CMSIS) 是 Cortex-M 处理器系列的与供应商无关的硬件抽象层。CMSIS 可实现与处理器和外设之间的一致且简单的软件接口,从而简化软件的重用,缩短微控制器开发人员新手的学习过程,并缩短新设备的上市时间。
ARM提供以下软件层,可用于多种编译器:
- 内核外设访问层(CPAL):包含了用来访问内核寄存器和核内外设的名称定义,地址定义和辅助函数。它也定义了用于RTOS内核的设备独立的接口。
芯片商提供以下软件层:
- 设备外设访问层(DPAL):提供设备所有外设定义。
- 外设访问函数(可选):提供额外的外设辅助函数。
CMSIS对Cortex-M微处理器系统定义:
- 一种访问外设寄存器的通用方法和一种定义异常向量的通用方法。
- 内核外设寄存器命名和内核异常向量命名。
- 应用于包含调试通道的RTOS内核的独立于设备的接口。
2.2 库目录分析
从ST官网下载最新的STM32F10x标准外设库,目前最新版本为V3.5.0。解压该zip文件,得到如下文件夹和文件:
_htmresc
Libraries
Project
Utilities
Release_Notes.html
stm32f10x_stdperiph_lib_um.chm
其中Libraries包含了库的源代码,Project包含了各个外设的使用示例和模板,Utilities是使用ST公司评估板的示例,stm32f10x_stdperiph_lib_um.chm是标准库的帮助文档。
2.3 标准外设库结构
标准外设库源代码结构如下图所示:
CMSIS文件夹提供了对STM32F10x系列芯片的Cortex-M3内核的支持。Documentation下有一个CMSIS_Core.html文件,该文件描述了CMSIS标准。CM3文件夹下的两个文件夹分别包括了核内外设访问层CPAL文件(core_cm3.h和core_cm3.c),STM32F10x系列MCU编写的设备外设访问层DPAL头文件stm32f10x.h,设备外设访问层系统文件DPALS(system_stm32f10x.h和system_stm32f10x.c)和根据4中不同编译环境编写的启动汇编代码startup文件。
2.3.1 CPAL文件
- 对Lint进行配置。
- 调用了“stdint.h",该文件有编译环境提供,对8位、16位、32位等整数类型的定义及其范围进行了规范。
- 只是寄存器的访问权限。CMSIS定义以下3种标识符来指定访问权限:_I(volatile const)、_O(volatile)和_IO(volatile)。其中_I用来指定只读权限,_O指定只写权限,_IO指定读写权限。
- 对CM3核内寄存器进行了定义。
- 对CM3硬件内存地址进行了映射。
- 硬件抽象层定义。
- 调试
- 安全机制:在嵌入式软件开发过程中,代码的安全性和健壮性一直是开发人员所关注的,因此CMSIS在这方面也作出了努力,所有的CMSIS代码都是基于MISRA-C2004标准。MIRSA-C2004指定了一系列安全机制用来保证驱动层软件的安全性,是嵌入式行业都应遵循的标准,对于不符合MISRA标准的编译器会提示错误或警告,这主要取决于开发者所使用的工具链。
2.3.2 DPAL
设备外设访问层包括了STM32F10x系列处理器所有外设寄存器的定义、位定义和不同容量STM32F10x的内存映射。
- 可以通过该文件配置如下内容:目标芯片、是否使用驱动文件、个别特殊的参数,如HSE的频率等。
- 定义了数据类型、结构体和所有外设的内存映射。
- 访问外设的宏。
- 中断异常的定义。
CMSIS对异常和中断标识符、中断处理函数名以及中断向量异常号都有严格的要求。异常和中断标识符需加后缀_IRQn。系统向量好必须为负值,而设备中断向量号是从0开始递增的。对异常处理函数以及普通中断处理函数名的定义也有所不同,系统异常处理函数名需加后缀_Handler,而普通中断处理函数名则加后缀_IRQHandler。这些异常中断处理函数被定义为weak属性,以便在其他文件中重新实现时不出现重复定义的错误,这些处理函数的地址用来填充中断异常向量表,并在启动代码中给以声明,例如:NMI_Handler、MemManage_Handler、WWDG_IRQHandler等等。
2.3.3 DPLAS
设备外设访问层系统文件提供了两个函数和一个全局变量。
- SystemInit():用来设置系统时钟(系统时钟源、PLL倍频因子、AHB/APBx的预分配及其FLASH),该函数在启动后的复位中被调用。
- SystemCoreClock:全局变量,系统内核时钟(HCLK)。
- SystemCoreClockUpdate():函数用来更新系统内核时钟,当系统内核时钟变化时必须执行该函数进行更新。
系统复位后,系统时钟采用的是内部高速时钟HSI(8MHz),然后通过SystemInit函数配置系统时钟,如果系统启动不成功,这SystemInit函数时钟配置不生效。HSE默认频率是8MHz,可在stm32f10x.h中修改“HSE_VALUE”的值来改变此值。
2.3.4 startup文件
该文件夹下根据4种不同编译环境编写了启动的汇编代码,主要完成STM32F10x系列芯片的系统所必要的初始化,主要包括:初始化堆栈指针SP、程序指针PC、中断向量配置、系统时钟配置。系统启动完毕后运行主程序。这些汇编文件是根据芯片的Flash容量来区分的,使用时需要注意。
3 标准外设驱动
3.1 缩写
缩写 | 外设 |
---|---|
ADC | 模数转换器 |
BKP | 备份寄存器 |
CAN | 控制器局域网模块 |
DMA | 直接内存存取控制器 |
EXTI | 外部中断事件控制器 |
FLASH | 闪存存储器 |
GPIO | 通用输入输出 |
I2C | 内部集成电路 |
IWDG | 独立看门狗 |
NVIC | 嵌套中断向量列表控制器 |
PWR | 电源/功耗控制 |
RCC | 复位与时钟控制器 |
RTC | 实时时钟 |
SPI | 串行外设接口 |
SysTick | 系统嘀嗒定时器 |
TIM | 通用定时器 |
TIM1 | 高级控制定时器 |
USART | 通用同步异步接收发射端 |
WWDG | 窗口看门狗 |
3.2 命名规范
标准外设驱动包含了所有外设的驱动函数,固态函数库应遵从以下命名规则:
- PPP 表示任一外设缩写,例如: ADC。
- 系统、源程序文件和头文件命名都以“stm32f10x_”作为开头,例如: stm32f10x_conf.h。
- 常量仅被应用于一个文件的,定义于该文件中;被应用于多个文件的,在对应头文件中定义。所有常量都由英文字母大写书写。
- 寄存器作为常量处理。他们的命名都由英文字母大写书写。在大多数情况下,他们采用与缩写规范与本用户手册一致。
- 外设函数的命名以该外设的缩写加下划线为开头。每个单词的第一个字母都由英文字母大写书写,例如:SPI_SendData。 在函数名中,只允许存在一个下划线,用以分隔外设缩写和函数名的其它部分。
- 名为 ***PPP_Init ***的函数,其功能是根据 ***PPP_InitTypeDef ***中指定的参数,初始化外设 PPP,例如 TIM_Init。
- 名为 ***PPP_DeInit ***的函数,其功能为复位外设 PPP 的所有寄存器至缺省值,例如 TIM_DeInit。
- 名为 ***PPP_StructInit ***的函数,其功能为通过设置 ***PPP_InitTypeDef ***结构中的各种参数来定义外设的功能,例如: USART_StructInit。
- 名为 ***PPP_Cmd ***的函数,其功能为使能或者失能外设 PPP,例如: SPI_Cmd。
- 名为 ***PPP_ITConfig ***的函数,其功能为使能或者失能来自外设 PPP 某中断源,例如: RCC_ITConfig。
- 名为 ***PPP_DMAConfig ***的函数,其功能为使能或者失能外设 PPP 的 DMA 接口,例如: TIM1_DMAConfig。
- 用以配置外设功能的函数,总是以字符串“Config”结尾,例如 GPIO_PinRemapConfig。
- 名为 ***PPP_GetFlagStatus ***的函数,其功能为检查外设 PPP 某标志位被设置与否,例如: I2C_GetFlagStatus。
- 名为 ***PPP_ClearFlag ***的函数,其功能为清除外设 PPP 标志位,例如: I2C_ClearFlag。
- 名为 ***PPP_GetITStatus ***的函数,其功能为判断来自外设 PPP 的中断发生与否,例如: I2C_GetITStatus。
- 名 为 PPP_ClearITPendingBit 的 函 数 , 其 功 能 为 清 除 外 设 PPP 中 断 待 处 理 标 志 位 , 例 如 :I2C_ClearITPendingBit。
3.3 编码规则
3.3.1 变量
固态函数库定义了 24 个变量类型,他们的类型和大小是固定的。在文件 ***stm32f10x.h ***中我们定义了这些变量:
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 */
3.3.2 布尔型
在文件 ***stm32f10x.h ***中,布尔形变量被定义如下:
typedef enum
{
FALSE = 0,
TRUE = !FALSE
} bool;
3.3.3 标识位状态类型
在文件 ***stm32f10x.h ***中,我们定义标志位类型( ***FlagStatus ***type)的 2 个可能值为“设置”与“重置”( SETor RESET)。
typedef enum
{
RESET = 0,
SET = !RESET
} FlagStatus;
3.3.4 功能状态类型
在文件 ***stm32f10x.h ***中,我们定义功能状态类型( ***FunctionalState ***type)的 2 个可能值为“使能”与“失能”( ***ENABLE ***or DISABLE)。
typedef enum
{
DISABLE = 0,
ENABLE = !DISABLE
} FunctionalState;
3.3.5 错误状态类型
在文件 ***stm32f10x.h ***中,我们错误状态类型类型( ***ErrorStatus ***type)的 2 个可能值为“成功”与“出错”( ***SUCCESS ***or ERROR)。
typedef enum
{
ERROR = 0,
SUCCESS = !ERROR
} ErrorStatus;
3.3.6 外设
通过外设结构体的指针可以访问外设的寄存器,每个外设都有它自己的结构体和指针,所有结构体都定义在stm32f10x.h中。
- 外设寄存器结构体
以SPI寄存器结构体的声明为例:
/**
* @brief Serial Peripheral Interface
*/
typedef struct
{
__IO uint16_t CR1;
uint16_t RESERVED0;
__IO uint16_t CR2;
uint16_t RESERVED1;
__IO uint16_t SR;
uint16_t RESERVED2;
__IO uint16_t DR;
uint16_t RESERVED3;
__IO uint16_t CRCPR;
uint16_t RESERVED4;
__IO uint16_t RXCRCR;
uint16_t RESERVED5;
__IO uint16_t TXCRCR;
uint16_t RESERVED6;
__IO uint16_t I2SCFGR;
uint16_t RESERVED7;
__IO uint16_t I2SPR;
uint16_t RESERVED8;
} SPI_TypeDef;
寄存器命名遵循上节的寄存器缩写命名规则。 RESERVEDi( i 为一个整数索引值)表示被保留区域。
- 外设声明
以下示例展示了SPI外设的声明:
#define PERIPH_BASE ((u32)0x40000000)
#define APB1PERIPH_BASE PERIPH_BASE
#define APB2PERIPH_BASE (PERIPH_BASE + 0x10000)
...
/* SPI2 Base Address definition*/
#define SPI2_BASE (APB1PERIPH_BASE + 0x3800)
外设寄存器按以下方式访问:
SPI1->CR1 = 0x0001;
每个外设都有若干寄存器专门分配给标志位用的,我们用专用的结构体定义这些寄存器。标志位的命名,同样遵循上节的外设缩写规范,以‘PPP_FLAG_’开始。对于不同的外设,标志位都被定义在相应的文件***stm32f10x_ppp.h ***中。
-
寄存器位
所有的外设寄存器位都作为常量定义在stm32f10x.h中,它们以首字母大写的格式定义:
PPP_<register_name>_<bit_name>
示例
#define SPI_CR2_RXDMAEN ((uint8_t)0x01) /*!<Rx Buffer DMA Enable */
#define SPI_CR2_TXDMAEN ((uint8_t)0x02) /*!<Tx Buffer DMA Enable */
#define SPI_CR2_SSOE ((uint8_t)0x04) /*!<SS Output Enable */
#define SPI_CR2_ERRIE ((uint8_t)0x20) /*!<Error Interrupt Enable */
#define SPI_CR2_RXNEIE ((uint8_t)0x40) /*!<RX buffer Not Empty Interrupt Enable */
#define SPI_CR2_TXEIE ((uint8_t)0x80) /*!<Tx buffer Empty Interrupt Enable */
3.2.7 位段
Cortex-M3存储映射包括两个位段存储域,别名域的每个字对应位段域的一个位,对别名域读-改-写一个字对位段域目标位有相同的影响。所有STM32F10x外设寄存器都映射到位段域,这样可以使得对某个寄存器的某些位执行集中修改从而减少和优化代码。
映射公式
下面的映射公式展示了别名域的一个字如何对应位段域的目标位,公式如下:
***bit_word_offset = (byte_offset x 32) + (bit_number + 4)
bit_word_addr = bit_band_base + bit_word_offset ***
注:bit_word_offset:指位段域中目标位在别名区中字的偏移。
bit_word_addr:指位段域中目标位在别名区中字的地址。
bit_band_base:指别名区起始地址。
byte_offset:指位段域中目标位在位段域中字节的偏移。
bit_number:指位段域中目标位在字节中的位的位置(0~7)。
示例
下面示例展示了RCC_CR寄存器的PLLON[24]位如何映射到别名区:
/* Peripheral base address in the bit-band region */
#define PERIPH_BASE ((u32)0x40000000)
/* Peripheral address in the alias region */
#define PERIPH_BB_BASE ((u32)0x42000000)
/* ----- RCC registers bit address in the alias region ------ */
#define RCC_OFFSET (RCC_BASE - PERIPH_BASE)
/* --- CR Register ---*/
/* Alias word address of PLLON bit */
#define CR_OFFSET (RCC_OFFSET + 0x00)
#define PLLON_BitNumber 0x18
#define CR_PLLON_BB (PERIPH_BB_BASE + (CR_OFFSET * 32) + (PLLON_BitNumber * 4))
/*To code a function which enables/disables the
PLL, the usual method is the following:*/
...
#define CR_PLLON_Set ((u32)0x01000000)
#define CR_PLLON_Reset ((u32)0xFEFFFFFF)
...
void RCC_PLLCmd(FunctionalState NewState)
{
if (NewState != DISABLE)
{
/* Enable PLL */
RCC->CR |= CR_PLLON_Set;
}
else
{
/* Disable PLL */
RCC->CR &= CR_PLLON_Reset;
}
}
/*Using bit-band access this function will be coded as follows:*/
void RCC_PLLCmd(FunctionalState NewState)
{
*(vu32 *) CR_PLLON_BB = (u32)NewState;
}
4 标准外设库使用
1.创建一个工程,设置工具链的启动文件(或者使用库中提供的模板工程,位于Project\STM32F10x_StdPeriph_Template目录下)
依赖于所使用的设备选择合适的启动文件:
- startup_stm32f10x_ld_vl.s: STM32低密度超值型设备
- startup_stm32f10x_ld.s: STM32低密度设备
- startup_stm32f10x_md_vl.s: STM32中密度超值型设备
- startup_stm32f10x_md.s: STM32中密度设备
- startup_stm32f10x_hd_vl.s: STM32高密度超值型设备
- startup_stm32f10x_hd.s: STM32高密度设备
- startup_stm32f10x_xl.s: STM32超高密度设备
- startup_stm32f10x_cl.s: STM32互联型设备
2.库的入口是stm32f10x.h(在Libraries/CMSIS/CM3/DeviceSupport/ST/STM32F10x目录下),用户必须在应用程序的main函数中包含并配置它:
- 选择使用的目标产品系列,注释/取消注释正确的定义:
#if !defined (STM32F10X_LD) && !defined (STM32F10X_LD_VL)\
&& !defined (STM32F10X_MD) && !defined (STM32F10X_MD_VL)\
&& !defined (STM32F10X_HD) && !defined (STM32F10X_XL)\
&& !defined (STM32F10X_CL)
/* #define STM32F10X_LD */ /*!< STM32F10X_LD: STM32 Low density devices */
/* #define STM32F10X_LD_VL */ /*!< STM32F10X_LD_VL: STM32 Low density Value Line devices */
/* #define STM32F10X_MD */ /*!< STM32F10X_MD: STM32 Medium density devices */
/* #define STM32F10X_MD_VL */ /*!< STM32F10X_MD_VL: STM32 Medium density Value Line devices */
/* #define STM32F10X_HD_VL */ /*!< STM32F10X_HD: STM32 High density Value line devices */
/* #define STM32F10X_HD */ /*!< STM32F10X_HD: STM32 High density devices */
/* #define STM32F10X_XL */ /*!< STM32F10X_CL: STM32 XL-density devices */
/* #define STM32F10X_CL */ /*!< STM32F10X_CL: STM32 Connectivity line devices */
#endif
- 用户必须选择使用或者不使用外设驱动
- 程序代码基于标准外设驱动API
- 取消注释#define USE_PERIPH_LIBRARY(默认)
- 在stm32f10x_config.h文件中,选择包含外设的头文件
- 使用外设的驱动API构建应用程序
- 根据标准外设库提供的丰富的外设驱动示例重用或修改加速开发
- 程序代码基于外设寄存器直接访问
- 注释#define USE_PERIPH_LIBRARY
- 使用外设寄存器结构和位定义构建应用程序
- 程序代码基于标准外设驱动API
3.添加system_stm32f10x.c(在Libraries/CMSIS/CM3/DeviceSupport/ST/STM32F10x目录下)文件到应用程序中,这个文件提供了设置STM32系统的函数:配置PLL,系统时钟和初始化内嵌的FLASH接口。此文件对系统时钟频率提供了多个选择,可以根据程序的需要从下面列表(覆盖了大部分应用程序的通用时钟频率)中选择合适的频率:
#if defined (STM32F10X_LD_VL) || (defined STM32F10X_MD_VL)
|| (defined STM32F10X_HD_VL)
/* #define SYSCLK_FREQ_HSE HSE_Value */
#define SYSCLK_FREQ_24MHz 24000000#
else
/* #define SYSCLK_FREQ_HSE HSE_Value */
/* #define SYSCLK_FREQ_24MHz 24000000 */
/* #define SYSCLK_FREQ_36MHz 36000000 */
/* #define SYSCLK_FREQ_48MHz 48000000 */
/* #define SYSCLK_FREQ_56MHz 56000000 */
#define SYSCLK_FREQ_72MHz 72000000
#endif
注意:此文件提供的系统时钟配置函数基于以下假设:
- 对于低密度超值型、低密度、中密度超值型、中密度、高密度超值型、高密度设备,使用外部8MHz晶振驱动系统时钟。
- 对于互联型设备,使用外部25MHz晶振驱动系统时钟。
如果使用了不同的晶振,必须改变在stm32f10x.h中HSE_VALUE的值。
5 外设初始化和配置
这一章主要描述如何一步一步使用外设驱动初始化和配置一个外设,外设使用PPP表示。
1.在main函数文件中声明一个PPP_InitTypeDef结构体,例如:PPP_InitTypeDef PPP_InitStructrue;
2.用结构体成员允许的值填充PPP_Inittypedef的值,可以有两种方式:
- 按照下面描述的步骤配置整个结构体:
PPP_InitStructure.member1 = val1;
PPP_InitStructure.member2 = val2;
/* where N is the number of the structure members */
PPP_InitStructure.memberN = valN;
前面的初始化步骤可以合成一行以优化代码大小:
PPP_InitTypeDef PPP_InitStructure = { val1, val2,.., valN}
- 仅配置结构体中一部分成员:在这种情况下首先使用PPP_StructInit(...)填充结构体的成员,然后在修改其中用户需要修改的成员即可,这样可以确保结构体中其他成员被初始化到合适的值(大部分情况下初始化成员的默认值):
PPP_StructInit(&PPP_InitStructure);
/*where X and Y are the members the user wants to configure*/
PP_InitStructure.memberX = valX;
PPP_InitStructure.memberY = valY;
3.调用PPP_Init(...)函数初始化外设。
PPP_Init(PPP, &PPP_InitStructure);
4.调用PPP_Cmd(...)函数使能外设。
PPP_Cmd(PPP, ENABLE);
然后就可以通过一系列外设专用的函数使用外设啦。
注意:
1.在配置外设前,必须调用下面中的一个使能外设的时钟:
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_PPPx, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_PPPx, ENABLE);
RCC_APB1PeriphClockCmd(RCC_APB1Periph_PPPx, ENABLE);
2.PPP_DeInit(...)函数可以设置PPP外设所有寄存器到默认值。
PPP_DeInit(PPP);
3.在配置了外设以后修改外设的配置,用户可以继续采用下面方式处理:
/* where X and Y are the only members that user wants to modify*/
PPP_InitStucture.memberX = valX;
PPP_InitStructure.memberY = valY;
PPP_Init(PPP, &PPP_InitStructure);