解析 STM32 的库函数
意法半导体在推出 STM32 微控制器之初,也同时提供了一套完整细致的固件开发包,
里面包含了在 STM32 开发过程中所涉及到的所有底层操作。通过在程序开发中引入这样的
固件开发包,可以使开发人员从复杂冗余的底层寄存器操作中解放出来,将精力专注应用程
序的开发上,这便是 ST 推出这样一个开发包的初衷。
但这对于许多从 51/AVR 这类单片机的开发转到 STM32 平台的开发人员来说,势必有一
个不适应的过程。因为程序开发不再是从寄存器层次起始,而要首先去熟悉 STM32 所提供
的固件库。那是否一定要使用固件库呢?当然不是。 但 STM32 微控制器的寄存器规模可不
是常见的 8 位单片机可以比拟,若自己细细琢磨各个寄存器的意义,必然会消耗相当的时间,
并且对于程序后续的维护,升级来说也会增加资源的消耗。对于当前“时间就是金钱”的行
业竞争环境,无疑使用库函数进行 STM32 的产品开发是更好的选择。本文将通过一个简单
的例子对 STM32 的库函数做一个简单的剖析。

以最常用的 GPIO 设备的初始化函数为例,如下程序段一:
GPIO_InitTypeDef GPIO_InitStructure;
○1
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4; ○2
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; ○3
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; ○4
GPIO_Init(GPIOA , &GPIO_InitStructure); ○5

这是一个在 STM32 的程序开发中经常使用到的 GPIO 初始化程序段,其功能是将 GPIOA.4 口
初始化为推挽输出状态,并最大翻转速率为 50MHz。下面逐一分解:
 首先是○1 ,该语句显然定义了一个 GPIO_InitTypeDef 类型的变量,名为 GPIO_InitStructure,
则找出 GPIO_InitTypeDef 的原型位于“stm32f10x_gpio.h”文件,原型如下:
typedef struct
{
u16 GPIO_Pin;
GPIOSpeed_TypeDef GPIO_Speed;
GPIOMode_TypeDef GPIO_Mode;
}GPIO_InitTypeDef;
由此可知 GPIO_InitTypeDef 是一个结构体类型同义字,其功能是定义一个结构体,该结
构体有三个成员分别是 u16 类型的 GPIO_Pin、 GPIOSpeed_TypeDef 类型的 GPIO_Speed
和 GPIOMode_TypeDef 类 型 的 GPIO_Mode 。 继 续 探 查 GPIOSpeed_TypeDef 和
GPIOMode_TypeDef 类型,在“stm32f10x_gpio.h”文件中找到对 GPIOSpeed_TypeDef 的
定义:
typedef enum
{
GPIO_Speed_10MHz = 1,
GPIO_Speed_2MHz,
GPIO_Speed_50MHz
}GPIOSpeed_TypeDef;
则可知 GPIOSpeed_TypeDef 枚举类型同一只,其功能是定义一个枚举类型变量,该变量
可表示 GPIO_Speed_10MHz、 GPIO_Speed_2MHz 和 GPIO_Speed_50MHz 三个含义(其中
GPIO_Speed_10MHz 已经定义为 1,读者必须知道 GPIO_Speed_2MHz 则依次被编译器赋
予 2,而 GPIO_Speed_50MHz 为 3)。
同样也在“stm32f10x_gpio.h”文件中找到对 GPIOMode_TypeDef 的定义:
typedef enum
{
GPIO_Mode_AIN = 0x0,
GPIO_Mode_IN_FLOATING = 0x04,
GPIO_Mode_IPD = 0x28,
GPIO_Mode_IPU = 0x48,
GPIO_Mode_Out_OD = 0x14,
GPIO_Mode_Out_PP = 0x10,
GPIO_Mode_AF_OD = 0x1C,
GPIO_Mode_AF_PP = 0x18
}GPIOMode_TypeDef;
这同样是一个枚举类型同义字,其成员有 GPIO_Mode_AIN、 GPIO_Mode_AF_OD 等(也
可以轻易判断出这表示 GPIO 设备的工作模式)。
至此对程序段一的○1 解析可以做一个总结:
该行定义一个结构体类型的变量 GPIO_InitStructure,并且该结构体有 3 个成员,分别为
GPIO_Pin、 GPIO_Speed 和 GPIO_Mode,并且 GPIO_Pin 表示 GPIO 设备引脚 GPIO_Speed
表示 GPIO 设备速率和 GPIO_Mode 表示 GPIO 设备工作模式。
 接下来是○2 ,此句是一个赋值语句,把 GPIO_Pin_4 赋给 GPIO_InitStructure 结构体中的
成员 GPIO_Pin,可以在“stm32f10x_gpio.h”文件中找到对 GPIO_Pin_4 做的宏定义:
#define GPIO_Pin_4 ((u16)0x0010)
因此○2 的本质是将 16 位数 0x0010 赋给 GPIO_InitStructure 结构体中的成员 GPIO_Pin。
 ○3 语句和○2 相似将 GPIO_Speed_50MHz 赋给 GPIO_InitStructure 结构体中的成员
GPIO_Speed,但注意到此处 GPIO_Speed_50MHz 只是一个枚举变量,并非具体的某个值。
 ○4 语句亦和○2 语句类似,把 GPIO_Mode_Out_PP 赋给 GPIO_InitStructure 结构体中的成
员 GPIO_Mode,从上文可知 GPIO_Mode_Out_PP 的值为 0x10。
 ○5 是一个函数调用,即调用 GPIO_Init 函数,并提供给该函数 2 个参数,分别为 GPIOA
和&GPIO_InitStructure,其中&GPIO_InitStructure 表示结构体变量 GPIO_InitStructure 的
地址,而 GPIOA 则在“stm32f10x_map.h”文件中找到定义:
#ifdef _GPIOA
#define GPIOA ((GPIO_TypeDef *) GPIOA_BASE)
#endif
此 三 行 代 码 是 一 个 预 编 译 结 构 , 首 先 判 断 是 否 定 义 了 宏 _GPIOA 。 可 以 在
“stm32f10x_conf.h”中发现对_GPIOA 的定义为:
#define _GPIOA
这表示编译器会将代码中出现的 GPIOA 全部替换为((GPIO_TypeDef *) GPIOA_BASE)。从
该句的 C 语言语法可以判断出((GPIO_TypeDef *) GPIOA_BASE)的功能为将 GPIOA_BASE
强制类型转换为指向 GPIO_TypeDef 类型的结构体变量。 如此则需要找出 GPIOA_BASE
的含义,依次在“stm32f10x_map.h”文件中找到:
#define GPIOA_BASE (APB2PERIPH_BASE + 0x0800)
和:
#define APB2PERIPH_BASE (PERIPH_BASE + 0x10000)
还有:
#define PERIPH_BASE ((u32)0x40000000)
明显 GPIOA_BASE 表示一个地址,通过将以上 3 个宏展开可以得到:
GPIOA_BASE = 0x40000000 + 0x10000 + 0x0800
此处的关键便在于 0x40000000、 0x10000 和 0x0800 这三个数值的来历。 读者应该通过
宏名猜到了,这就是 STM32 微控制器的 GPIOA 的设备地址。通过查阅 STM32 微控制器
开发手册可以得知, STM32 的外设起始基地址为 0x40000000,而 APB2 总线设备起始地
址相对于外设基地址的偏移量为 0x10000, GPIOA 设备相对于 APB2 总线设备起始地址
偏移量为 0x0800。
对○5 句代码进行一个总结:调用 GPIO_Init 函数,并将 STM32 微控制器的 GPIOA 设备地
址和所定义的结构体变量 GPIO_InitStructure 的地址传入。
以上是对 GPIOA 初始化库函数的剖析,现继续转移到函数内部分析, GPIO_Init 函数原
型如程序段二:
void GPIO_Init(GPIO_TypeDef* GPIOx, GPIO_InitTypeDef* GPIO_InitStruct)
{
u32 currentmode = 0x00, currentpin = 0x00, pinpos = 0x00, pos = 0x00;
u32 tmpreg = 0x00, pinmask = 0x00;
/* 检查参数是否正确 */
assert_param(IS_GPIO_ALL_PERIPH(GPIOx));
assert_param(IS_GPIO_MODE(GPIO_InitStruct->GPIO_Mode));
assert_param(IS_GPIO_PIN(GPIO_InitStruct->GPIO_Pin));
/* 将工作模式暂存至 currentmode 变量中 */
currentmode = ((u32)GPIO_InitStruct->GPIO_Mode) & ((u32)0x0F);
/* 如果欲设置为任意一种输出模式,则再检查”翻转速率“参数是否正确 */
if ((((u32)GPIO_InitStruct->GPIO_Mode) & ((u32)0x10)) != 0x00)
{
assert_param(IS_GPIO_SPEED(GPIO_InitStruct->GPIO_Speed));
currentmode |= (u32)GPIO_InitStruct->GPIO_Speed;
}
/* 设置低八位引脚(即 pin0 ~ pin7) */
if (((u32)GPIO_InitStruct->GPIO_Pin & ((u32)0x00FF)) != 0x00)
{
/* 读出当前配置字 */
tmpreg = GPIOx->CRL;
for (pinpos = 0x00; pinpos < 0x08; pinpos++)
{
/* 获取将要配置的引脚号 */
pos = ((u32)0x01) << pinpos;
currentpin = (GPIO_InitStruct->GPIO_Pin) & pos;
if (currentpin == pos)
{
/* 先清除对应引脚的配置字 */
pos = pinpos << 2;
pinmask = ((u32)0x0F) << pos;
tmpreg &= ~pinmask;
/* 写入新的配置字 */
tmpreg |= (currentmode << pos);
/* 若欲配置为上拉 / 下拉输入,则需要配置 BRR 和 BSRR 寄存器 */
if (GPIO_InitStruct->GPIO_Mode == GPIO_Mode_IPD)
{
GPIOx->BRR = (((u32)0x01) << pinpos);
}
else
{
if (GPIO_InitStruct->GPIO_Mode == GPIO_Mode_IPU)
{
GPIOx->BSRR = (((u32)0x01) << pinpos);
}
}
}
}
/* 写入低八位引脚配置字 */
GPIOx->CRL = tmpreg;
}
/* 设置高八位引脚(即 pin8 ~ pin15), 流程和第八位引脚配置流程一致, 不再作解析 */
if (GPIO_InitStruct->GPIO_Pin > 0x00FF)
{
tmpreg = GPIOx->CRH;
for (pinpos = 0x00; pinpos < 0x08; pinpos++)
{
pos = (((u32)0x01) << (pinpos + 0x08));
currentpin = ((GPIO_InitStruct->GPIO_Pin) & pos);
if (currentpin == pos)
{
pos = pinpos << 2;
pinmask = ((u32)0x0F) << pos;
tmpreg &= ~pinmask;
tmpreg |= (currentmode << pos);
if (GPIO_InitStruct->GPIO_Mode == GPIO_Mode_IPD)
{
GPIOx->BRR = (((u32)0x01) << (pinpos + 0x08));
}
if (GPIO_InitStruct->GPIO_Mode == GPIO_Mode_IPU)
{
GPIOx->BSRR = (((u32)0x01) << (pinpos + 0x08));
}
}
}
GPIOx->CRH = tmpreg;
}
}
这段程序的流程是:首先检查由结构体变量 GPIO_InitStructure 所传入的参数是否正确,
然后对 GPIO 寄存器进行“保存——修改——写入”的操作,完成对 GPIO 设备的设置工作。
显然, 结构体变量 GPIO_InitStructure 所传入参数的目的是设置对应 GPIO 设备的寄存器。而
STM32 的参考手册对关于 GPIO 设备的设置寄存器的描述如下图 1 和表 1(仅列出低八位引
脚寄存器描述,高八位引脚类同):
图 1 GPIO 设备控制寄存器 GPIOx_CRL

位 31:30
27:26
23:22
19:18
15:14
11:10
7:6
3:2
在输入模式(MODE[1:0]=00):
00:模拟输入模式
01:浮空输入模式(复位后的状态)
10:上拉/下拉输入模式
11:保留
在输出模式(MODE[1:0]>00):
00:通用推挽输出模式
01:通用开漏输出模式
10:复用功能推挽输出模式
11:复用功能开漏输出模式
位 29:28
25:24
21:20
17:16
13:12
9:8
5:4
1:0
MODEy[1:0] :端口 x 的模式位(y = 0„7) (Port x mode bits)
软件通过这些位配置相应的 I/O 端口,请参考表 17 端口位配置表。
00:输入模式(复位后的状态)
01:输出模式,最大速度 10MHz
10:输出模式,最大速度 2MHz
11:输出模式,最大速度 50MHz

表 1 GPIO 设备控制寄存器 GPIOx_CRL 描述
该寄存器为 32 位,其中分为 8 份, 每份 4 位,对应低八位引脚的设置。每一个引脚的
设置字分为两部分,分别为 CNF 和 MODE,各占两位空间。当 MODE 的设置字为 0 时,表
示将对应引脚配置为输入模式,反之设置为输出模式,并有最大翻转速率限制。而当引脚配
置为输出模式时, CNF 配置字则决定引脚以哪种输出方式工作(通用推挽输出、通用开漏输
出等)。 通过对程序的阅读和分析不难发现, 本文最初程序段中 GPIO_InitStructure 所传入参
数的对寄存器的作用如下:
 GPIO_Pin_4 被宏替换为 0x0010,对应图 1 可看出为用于选择配置 GPIOx_CRL 的[19:16]
位,分别为 CNF4[1:0]、 MODE4[1:0]。
 GPIO_Speed_50MHz 为枚举类型,包含值 0x03, 被用于将 GPIOx_CRL 位中的 MODE4[1:0]
配置为 b11(此处 b 意指二进制)。
 GPIO_Mode 亦为枚举类型,包含值 0x10, 被用于将 GPIOx_CRL 位中的 MODE4[1:0]配置
为 b00。 事实上 GPIO_Mode 的值直接影响寄存器的只有低四位,而高四位的作用可以
从程序段二中看出,是用于判断此参数是否用于 GPIO 引脚输出模式的配置。
至此应不难知道 STM32 的固件库最后是怎样影响最底层的寄存器的。 总结起来就是:
固件库首先将各个设备所有寄存器的配置字进行预先定义,然后封装在结构或枚举变量中,
待用户调用对应的固件库函数时,会根据用户传入的参数从这些封装好的结构或枚举变量中
取出对应的配置字,最后写入寄存器中,完成对底层寄存器的配置。
可以看到, STM32 的固件库函数对于程序开发人员来说是十分便利的存在,只需要填
写言简意赅的参数就可以在完全不关心底层寄存器的前提下完成相关寄存器的配置,具有相
当不错的通用性和易用性, 也采取了一定措施保证库函数的安全性(主要引入了参数检查函
数 assert_param)。但同时也应该知道,通用性、易用性和安全性的代价是加大了代码量,
同时增加了一些逻辑判断代码造成了一定的时间消耗,在对时间要求比较苛刻的应用场合需
要评估使用固件库函数对程序运行时间所带来的影响。 读者在使用 STM32 的固件库函数进
行程序开发时,应该意识到这些问题。

解析 STM32 的库函数的更多相关文章

  1. 解析STM32的库函数

    意法半导体在推出STM32微控制器之初,也同时提供了一套完整细致的固件开发包,里面包含了在STM32开发过程中所涉及到的所有底层操作.通过在程序开发中引入这样的固件开发包,可以使开发人员从复杂冗余的底 ...

  2. 从库函数解析STM32地址映射

    STM32的存储映射是靠基地址和地址偏移实现的. 32位的M3有4GB的寻址空间,其中用于片上外设的有512MB,基地址为0x40000000. M3各外设基地址,包括片上外设.片上静态RAM和FLA ...

  3. 解析stm32的时钟

    STM32 时钟系统  http://blog.chinaunix.net/uid-24219701-id-4081961.html STM32的时钟系统 ***   http://www.cnblo ...

  4. stm32 中库函数、结构体、地址的强制类型转换、相应特殊功能寄存器之间的关系

    以一个挂接在APB2上的外设函数使能为例 A : RCC_APB2PeriphClockCmd():时钟使能函数 1 RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFI ...

  5. stm32 dac库函数解读

    1.简述: 12位数字输入,电压输出,DAC可以配置为8位或12位模式.有2个输出通道.在双DAC模式下,两个通道可以独立地工作. 特殊功能: 噪声波形生成,三角波形生成,外部触发转换,双DAC同时或 ...

  6. STM32F10xx CAN BUS相关库文件"stm32f10x_can.c"内的库函数解析

    一.背景: 还是继续CAN通信,要节省开发时间,使用库函数可大大降低开发周期,并且还能确保寄存器的配置几 乎是万无一失,所以,在此就STM32F10xx的CAN操作库函数的使用做个简析. STM32有 ...

  7. STM32 IAP 在线升级详解(转)

    源:http://blog.csdn.net/yx_l128125/article/details/12992773 (扩展-IAP主要用于产品出厂后应用程序的更新作用,考虑到出厂时要先烧写IAP   ...

  8. 【转载】STM32 IAP 在线升级详解

      (扩展-IAP主要用于产品出厂后应用程序的更新作用,考虑到出厂时要先烧写IAP  再烧写APP应用程序要烧写2次增加工人劳动力基础上写了“STM32 IAP+APP ==>双剑合一”链接稍后 ...

  9. 【玩转单片机系列002】 如何使用STM32提供的DSP库进行FFT

    前些日子,因为需要在STM32F103系列处理器上,对采集的音频信号进行FFT,所以花了一些时间来研究如何高效并精确的在STM32F103系列处理器上实现FFT.在网上找了很多这方面的资料做实验并进行 ...

随机推荐

  1. 2018-01-19 Xtext试用: 5步实现一个(中文)JVM语言

    续上文Xtext试用: 快速实现简单领域专用语言(DSL). 基于官方教程: Five simple steps to your JVM language 达成如下语言: 它被Quan6JvmMode ...

  2. 【工具相关】Web-Sublime Text2-通过Package Control安装插件

    一,Sublime Text2--->Preferences--->Package Control-->输入install---> 下方就会提示“Package Control ...

  3. Jenkins报错'Gradle build daemon disappeared unexpectedly'的问题解决

    在将项目集成到 Jenkins 后,经常会出现不稳定的构建,Jenkins 控制台输出的错误信息为:Gradle build daemon disappeared unexpectedly (it m ...

  4. 浅谈Java多线程同步机制之同步块(方法)——synchronized

    在多线程访问的时候,同一时刻只能有一个线程能够用 synchronized 修饰的方法或者代码块,解决了资源共享.下面代码示意三个窗口购5张火车票: package com.jikexueyuan.t ...

  5. java代码代替xml实现图片

    1.使用StateListDrawable替换selector public static StateListDrawable getSelector(Drawable normalDrawable, ...

  6. 使用VSTS的Git进行版本控制(六)——拉取请求

    使用VSTS的Git进行版本控制(六)--拉取请求 在将代码合并到主干之前,拉取请求让团队对特性分支的更改提供反馈.审阅人可以通过建议修改留下评论,并投票批准或拒绝代码. 任务1:在Visual St ...

  7. Python实现批量梯度下降算法

    # -*- coding: UTF-8 -*- import numpy as npimport math # 定义基础变量learning_rate = 0.1n_iterations = 1000 ...

  8. python pip 使用时错误: Patal error in launcher:Unable to create process using '"'

    当前我的电脑配置是64位, 装有python2.7 和python 3.6 两个版本 在使用pip install mysqlclient 的时候,出现了  Patal error in launch ...

  9. android ninja【转】

    Android7.0 Ninja编译原理 引言 使在Android N的系统上,初次使用了Ninja的编译系统.对于Ninja,最初的印象是用在了Chromium open source code的编 ...

  10. VRS——备忘

    1.所有版本VRS,注释掉m_ChisauarePRN参数 2.注释掉所有的MODULE_GNSSMonitor_ID,暂时用不到.但是会造成basestation崩掉. n.RegisterStat ...