解析STM32的库函数

发布者:王岚枫最新更新时间:2021-02-22 来源: eefocus关键字: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。下面逐一分解:

l  首先是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 = ((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;
}


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);

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;
}


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(仅列出低八位引脚寄存器描述,高八位引脚类同):
[attach]65378[/attach]
该寄存器为32位,其中分为8份,每份4位,对应低八位引脚的设置。每一个引脚的设置字分为两部分,分别为CNF和MODE,各占两位空间。当MODE的设置字为0时,表示将对应引脚配置为输入模式,反之设置为输出模式,并有最大翻转速率限制。而当引脚配置为输出模式时,CNF配置字则决定引脚以哪种输出方式工作(通用推挽输出、通用开漏输出等)。通过对程序的阅读和分析不难发现,本文最初程序段中GPIO_InitStructure所传入参数的对寄存器的作用如下:
1、GPIO_Pin_4被宏替换为0x0010,对应图1可看出为用于选择配置GPIOx_CRL的[19:16]位,分别为CNF4[1:0]、MODE4[1:0]。
2、GPIO_Speed_50MHz为枚举类型,包含值0x03,被用于将GPIOx_CRL位中的MODE4[1:0]配置为b11(此处b意指二进制)。
3、GPIO_Mode亦为枚举类型,包含值0x10,被用于将GPIOx_CRL位中的MODE4[1:0]配置为b00。事实上GPIO_Mode的值直接影响寄存器的只有低四位,而高四位的作用可以从程序段二中看出,是用于判断此参数是否用于GPIO引脚输出模式的配置。


至此应不难知道STM32的固件库最后是怎样影响最底层的寄存器的。总结起来就是:固件库首先将各个设备所有寄存器的配置字进行预先定义,然后封装在结构或枚举变量中,待用户调用对应的固件库函数时,会根据用户传入的参数从这些封装好的结构或枚举变量中取出对应的配置字,最后写入寄存器中,完成对底层寄存器的配置。


可以看到,STM32的固件库函数对于程序开发人员来说是十分便利的存在,只需要填写言简意赅的参数就可以在完全不关心底层寄存器的前提下完成相关寄存器的配置,具有相当不错的通用性和易用性,也采取了一定措施保证库函数的安全性(主要引入了参数检查函数assert_param)。但同时也应该知道,通用性、易用性和安全性的代价是加大了代码量,同时增加了一些逻辑判断代码造成了一定的时间消耗,在对时间要求比较苛刻的应用场合需要评估使用固件库函数对程序运行时间所带来的影响。读者在使用STM32的固件库函数进行程序开发时,应该意识到这些问题。

关键字:STM32  库函数  微控制器 引用地址:解析STM32的库函数

上一篇:STM32F4XX的GPIO的寄存器配置
下一篇:STM32CubeMX之串口封装详解

推荐阅读最新更新时间:2024-11-11 13:09

STM32 获取寄存器的地址
如果需要使用TX1的DMA传输,就会涉及到配置DMA的外设地址。在这里,外设地址应该是USART1的DR寄存器的地址。可是如何获取该寄存器的地址呢? 现在实测了3种方式,都可以实现。 方法1: 直接查询手册中寄存器映像(map),USART1的DR地址为0x40013804 方法2: 手册中查看,DR寄存器的偏移地址为0X04,那么USART1的DR地址可以写成(USART1_BASE + 0X04)。 其中USART1_BASE在stm32f10xb.h中有定义 #define USART1_BASE (APB2PERIPH_BASE + 0x00003800U) 方法3: 如果我们要修
[单片机]
51单片机利用IIC总线对LM75A温度进行读取
#include INTRINS.H #include ..\config\c8051f350.h #include ..\config\const.h #include ..\driver\system.h #include ..\driver\other.h #define IIC_WRITE 0 #define IIC_READ 1 #define VREF 24380 static unsigned long sysclk=24500000; sbit SDA=P0^0; sbit SCL=P0^1; sbit led=P1^2; void Delay_us(unsigned int times){ unsigned i
[单片机]
单片机与控制实验(4)——步进电机原理及应用
一、实验目的和要求   了解步进电机的工作原理,学习用单片机的步进电机控制系统的硬件设计方法,掌握定时器和中断系统的应用,熟悉单片机应用系统的设计与调试方法。 二、实验设备   单片机测控实验系统   步进电机控制实验模块   Keil开发环境   STC-ISP程序下载工具 三、实验内容   编制MCS-51程序使步进电机按照规定的转速和方向进行旋转,并将已转动的步数显示在数码管上。   步进电机的转速分为两档,当按下S1开关时,加速旋转,速度从10转/分加速到60转/分。当松开开关时,减速旋转,速度恢复为10转/分。当按下S2开关时,按照逆时针旋转;当松开时,按照顺时针旋转。   本程序要求使用定时器中断来实现,不准
[单片机]
PIC单片机特点及不足之处解析
PIC单片机 PIC单片机系列是美国微芯公司(Microship)的产品,共分三个级别,即基本级、中级、高级,是当前市场份额增长最快的单片机之一,CPU采用RISC结构,分别有33、35、58条指令,属精简指令集,同时采用Harvard双总线结构,运行速度快,它能使程序存储器的访问和数据存储器的访问并行处理,这种指令流水线结构,在一个周期内完成两部分工作,一是执行指令,二是从程序存储器取出下一条指令,这样总的看来每条指令只需一个周期,这也是高效率运行的原因之一,此外PIC单片机之所以成为一时非常热的单片机不外乎以下特点: 特点 1、具有低工作电压、低功耗、驱动能力强等特点。PIC系列单片机的I/O口是双向的,其输出电路为CM
[单片机]
PIC<font color='red'>单片机</font>特点及不足之处解析
高控制/运算效能助力 MCU实现智能照护机器人
高龄化社会来临,使得独居老人照护问题日益受到重视,因此研究人员利用具备高控制和运算能力的 微控制器 (MCU),开发出可随时跟随独居老人并具备跌倒侦测、紧急通知和网路互动等功能的智慧照护机器人,以提升居家照护品质。 台湾目前已逐渐迈入高龄化社会,多数子女工作在外,无法全心全意照顾老人,因此孤独在家中的老人照护就显得相当重要。有人选择聘请看护在家照料,抑或决定送至安养中心,但这些方法费用过高,而且子女也无法直接照顾亲人,显得较不放心。为有效解决此问题,可照护及关怀老人生活的电子装置遂日益受到重视,透过装置的监控及互动功能,不仅可让老人在家即能了解在外亲人的消息,而子女也能随时从中得到家中父母的健康与生活资讯。 老人关怀照护系
[单片机]
高控制/运算效能助力 <font color='red'>MCU</font>实现智能照护机器人
STM32时钟问题(重点)
STM32上电默认时钟内部8MHZ,经过库函数SystemInit的初始化,设置成启用外部晶振模式,并设为系统时钟为PLL倍频后的时钟:72MHZ. * SYSCLK 72MHz * AHB 72MHz * PCLK1 36MHz * PCLK2 72MHz * PLL 72MHz 但是用户可以自己选择使用不同时钟,下面给出了几个函数。 可以配置成内部时钟或者外部时钟。 /* * 寄存器的方式设置系统时钟: * 输入PLL倍频因子,输入PLL的倍频值2—16倍频(注意:不同的芯片有不同的倍频因子) * HCLK = PLLCLK=SYSCLK=P2CLK=P1CLK*2=ADCCLK*2=TIMCLK=U
[单片机]
单片机在污水处理系统中的应用
引言 目前,污水处理厂运用集散控制系统模型可以最大限度提高污水处理厂运行可靠性,提高出水水质,降低能耗和工人劳动强度,达到提高经济效益的目的。可编程计算机控制器(prograrnrnable ComputerController,简称PCC)以其可靠性高、编程方便、耐恶劣环境、功能强大等特性已成为工业控制领域中增长速度最迅猛的工业控制设备,它能很好地解决工业控制领域普遍关心的可靠、安全、灵活、方便、经济等问题。 随着工业以太网技术、现场总线技术的发展,由现场总线与工业以太网构建的 一网到底 工业控制网络系统,使得工厂的高层管理人员能直接获得工业现场的控制信息,实现工厂管理与生产现场的无缝集成。根据污水处理行业的特点,设
[单片机]
<font color='red'>单片机</font>在污水处理系统中的应用
大联大品佳集团推出基于Infineon产品的汽车照明通用单片机解决方案
2021年11月4日,致力于亚太地区市场的领先半导体元器件分销商--- 大联大控股 宣布,其旗下品佳推出基于英飞凌(Infineon)CK8CKIT-044评估板的低成本汽车照明通用单片机解决方案。 图示1-大联大品佳基于Infineon产品的低成本汽车照明通用单片机方案的展示板图 如今,汽车已成为人们生活中必不可少的交通工具,其安全问题也受到社会极大的关注。在汽车所有的组成部分中,尾灯对于汽车安全性的重要性不言而喻,它的存在大幅度减少了交通事故的发生。因此,如何设计尾灯的各种功能以最大化其价值,是各大厂商亟需解决的问题。随着技术的发展,当今的汽车尾灯不仅外观多变,而且功能也有了很大的提高,而这主要的功劳则要归功于尾灯控制
[汽车电子]
大联大品佳集团推出基于Infineon产品的汽车照明通用<font color='red'>单片机</font>解决方案
小广播
设计资源 培训 开发板 精华推荐

最新单片机文章
何立民专栏 单片机及嵌入式宝典

北京航空航天大学教授,20余年来致力于单片机与嵌入式系统推广工作。

换一换 更多 相关热搜器件

 
EEWorld订阅号

 
EEWorld服务号

 
汽车开发圈

电子工程世界版权所有 京B2-20211791 京ICP备10001474号-1 电信业务审批[2006]字第258号函 京公网安备 11010802033920号 Copyright © 2005-2024 EEWORLD.com.cn, Inc. All rights reserved