STM32库函数实现方法

一、概述

1、调用STM32库函数配置与直接配置寄存器

① 直接配置寄存器

  使用过51单片机的朋友都知道为了将IO口配置成某种特殊功能或者配置中断控制,我们先将需要如下步骤:  

根据需要配置功能计算值---->直接写入寄存器

② 调用STM32库函数配置

  定义一个配置结构体变量---->根据配置功能依次给这个结构体变量的成员赋值----->将结构体变量的地址传入库函数,并调用STM32库函数实现配置

  其中最后一个步骤的"调用STM32库函数",实际上就是将配置结果写入寄存器。

  前两个步骤是以一种高级的形式(将结构体变量的成员和它的值以利于人理解的方式定义)将我们希望的配置参数写入结构体变量(内存)中。然后再将将结构体变量的地址传入库函数(这些参数就传给STM32库函数),STM32库函数根据设置参数计算出要写入寄存器的值,最后写入到寄存器中,完成整个配置。

③ 调用STM32库函数配置与直接配置寄存器的对比

  直接配置寄存器需要计算写入值(人做的),然后直接写入。代码量要小,执行时间少,效率高,但是辛苦了人。

  调用STM32库函数配置,计算写入值以及写入寄存器都是库函数完成的,而人就像一个领导通过结构体告诉库函数我要怎样配置。可见人的工作量大大减小了,显然辛苦了机器,不过CPU速度很高,咱们不用担心。

2、STM32库函数实现的组成

STM32库函数实现方法

  STM32定义了配置需要的结构体,结构体变量都是见名知意的,而且用枚举定义了许多新的类型。枚举方法适合变量值比较少的情况,分别给每个值一个名字,便于用户识别和赋值。

3、声明

  以下阐述以配置GPIO实现流水灯程序为例进行说明,测试库函数是GPIO_Init(),

STM32库函数实现方法

  硬件电路如下:

STM32库函数实现方法

二、结构体

  GPIO_Init库函数目的就是对GPIOx设置成我们需要的参数值,参数值保存在GPIO_InitStruct。其中涉及到两个结构体,来看一下。

1、GPIO_TypeDef

  定义是在stm32f10x.h中

typedef struct
{
__IO uint32_t CRL;
__IO uint32_t CRH;
__IO uint32_t IDR;
__IO uint32_t ODR;
__IO uint32_t BSRR;
__IO uint32_t BRR;
__IO uint32_t LCKR;
} GPIO_TypeDef;

  看一下怎么利用这个结构体类型是怎么访问寄存器CRL、CRH?

#define PERIPH_BASE           ((uint32_t)0x40000000) /*!< Peripheral base address in the alias region */
#define APB2PERIPH_BASE (PERIPH_BASE + 0x10000)
#define GPIOC_BASE (APB2PERIPH_BASE + 0x1000)
#define GPIOC ((GPIO_TypeDef *) GPIOC_BASE)

  GPIOC最终的值是0x40000000+0x10000+0x1000,也就是0x4001100,而且注意这个值不是int类型,而是一个指向GPIO_TypeDef结构体类型的常量指针。

访问方法一:

  通过定义一个值等于GPIOC的指针变量,就可以访问到端口GPIOC的每一个寄存器。

GPIO_TypeDef * GPIOx = GPIOC;
GPIOx->CRH = 0xffffffff;

访问方法二:

GPIOC->CRH = 0xffffffff;

对应的反汇编代码:

0x0800037C F04F30FF  MOV      r0,#0xFFFFFFFF
0x08000380 LDR r1,[pc,#] ; @0x0800038C
0x08000382 STR r0,[r1,#0x04]
0x08000384 E7F0
0x08000386
0x08000388 0C14
0x0800038A
0x0800038C ASRS r0,r0,#
0x0800038E ANDS r1,r1,r0

  可见GPIOC是一个常量结构体指针,而它指向的结构体成员CRH的地址因此也是个常量0x4001 1004(编译器可以计算出来)。因此可以通过这个地址直接访问结构体POTRC的成员CRH。

  我认为这句话实际上等于:

*((volatile unsigned int *) 0x40011004) = 0xffffffff;

2、GPIO_InitTypeDef 

  定义在stm32f10x_gpio.h中

typedef struct
{
uint16_t GPIO_Pin; /*!< Specifies the GPIO pins to be configured.
This parameter can be any value of @ref GPIO_pins_define */ GPIOSpeed_TypeDef GPIO_Speed; /*!< Specifies the speed for the selected pins.
This parameter can be a value of @ref GPIOSpeed_TypeDef */ GPIOMode_TypeDef GPIO_Mode; /*!< Specifies the operating mode for the selected pins.
This parameter can be a value of @ref GPIOMode_TypeDef */
}GPIO_InitTypeDef;

  初始化GPIO的结构体类型--GPIO_InitTypeDef有三个成员,需要设置的引脚GPIO_Pin,需要设置的引脚速度GPIO_Speed,需要设置的引脚工作模式GPIO_Mode。这三个成员包含了配置GPIO的所有信息,而且这三个成员都是见名知意。当然不可能直接将这三个成员写入到配置寄存器中,需要转化成对应的寄存器值才能写入,这就是GPIO_Init所要干的活。

① GPIO_Pin

  GPIO_Pin的类型是uint16_t,那么它的取值表含义是什么呢?

#define GPIO_Pin_0                 ((uint16_t)0x0001)  /*!< Pin 0 selected */
#define GPIO_Pin_1 ((uint16_t)0x0002) /*!< Pin 1 selected */
#define GPIO_Pin_2 ((uint16_t)0x0004) /*!< Pin 2 selected */
#define GPIO_Pin_3 ((uint16_t)0x0008) /*!< Pin 3 selected */
#define GPIO_Pin_4 ((uint16_t)0x0010) /*!< Pin 4 selected */
#define GPIO_Pin_5 ((uint16_t)0x0020) /*!< Pin 5 selected */
#define GPIO_Pin_6 ((uint16_t)0x0040) /*!< Pin 6 selected */
#define GPIO_Pin_7 ((uint16_t)0x0080) /*!< Pin 7 selected */
#define GPIO_Pin_8 ((uint16_t)0x0100) /*!< Pin 8 selected */
#define GPIO_Pin_9 ((uint16_t)0x0200) /*!< Pin 9 selected */
#define GPIO_Pin_10 ((uint16_t)0x0400) /*!< Pin 10 selected */
#define GPIO_Pin_11 ((uint16_t)0x0800) /*!< Pin 11 selected */
#define GPIO_Pin_12 ((uint16_t)0x1000) /*!< Pin 12 selected */
#define GPIO_Pin_13 ((uint16_t)0x2000) /*!< Pin 13 selected */
#define GPIO_Pin_14 ((uint16_t)0x4000) /*!< Pin 14 selected */
#define GPIO_Pin_15 ((uint16_t)0x8000) /*!< Pin 15 selected */
#define GPIO_Pin_All ((uint16_t)0xFFFF) /*!< All pins selected */

  选中某一位(置1),就意味着要配置这一位。

② GPIO_Speed

  它的类型是一个枚举类型--GPIOSpeed_TypeDef

typedef enum
{
GPIO_Speed_10MHz = ,
GPIO_Speed_2MHz,
GPIO_Speed_50MHz
}GPIOSpeed_TypeDef;

  可见GPIO_Speed只有三种取值,这三种取值无疑是见名知意的。GPIO_Speed_10MHz = 1,编译器自动的会为剩余两种取值以合适的数字值取代:GPIO_Speed_2MHz=2,GPIO_Speed_50MHz=3。

  用枚举类型定义取值较少的类型带来的好处:

  • 见名知意
  • 便于检查

③ GPIO_Mode

  它的类型也是一个枚举类型--  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_Init函数根据这些值得到对应配置寄存器的值。

三、库函数

  GPIO_Init函数的功能是配置GPIOx,实际上就是配置GPIOx的六个寄存器。

  __IO uint32_t CRL;
__IO uint32_t CRH;
__IO uint32_t IDR;
__IO uint32_t ODR;
__IO uint32_t BSRR;
__IO uint32_t BRR;
__IO uint32_t LCKR;

  函数代码如下:

/**
* @brief Initializes the GPIOx peripheral according to the specified
* parameters in the GPIO_InitStruct.
* @param GPIOx: where x can be (A..G) to select the GPIO peripheral.
* @param GPIO_InitStruct: pointer to a GPIO_InitTypeDef structure that
* contains the configuration information for the specified GPIO peripheral.
* @retval None
*/
void GPIO_Init(GPIO_TypeDef* GPIOx, GPIO_InitTypeDef* GPIO_InitStruct)
{
uint32_t currentmode = 0x00, currentpin = 0x00, pinpos = 0x00, pos = 0x00;
uint32_t tmpreg = 0x00, pinmask = 0x00;
/* Check the parameters */
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)); /*---------------------------- GPIO Mode Configuration -----------------------*/
currentmode = ((uint32_t)GPIO_InitStruct->GPIO_Mode) & ((uint32_t)0x0F);
if ((((uint32_t)GPIO_InitStruct->GPIO_Mode) & ((uint32_t)0x10)) != 0x00)
{
/* Check the parameters */
assert_param(IS_GPIO_SPEED(GPIO_InitStruct->GPIO_Speed));
/* Output mode */
currentmode |= (uint32_t)GPIO_InitStruct->GPIO_Speed;
}
/*---------------------------- GPIO CRL Configuration ------------------------*/
/* Configure the eight low port pins */
if (((uint32_t)GPIO_InitStruct->GPIO_Pin & ((uint32_t)0x00FF)) != 0x00)
{
tmpreg = GPIOx->CRL;
for (pinpos = 0x00; pinpos < 0x08; pinpos++)
{
pos = ((uint32_t)0x01) << pinpos;
/* Get the port pins position */
currentpin = (GPIO_InitStruct->GPIO_Pin) & pos;
if (currentpin == pos)
{
pos = pinpos << ;
/* Clear the corresponding low control register bits */
pinmask = ((uint32_t)0x0F) << pos;
tmpreg &= ~pinmask;
/* Write the mode configuration in the corresponding bits */
tmpreg |= (currentmode << pos);
/* Reset the corresponding ODR bit */
if (GPIO_InitStruct->GPIO_Mode == GPIO_Mode_IPD)
{
GPIOx->BRR = (((uint32_t)0x01) << pinpos);
}
else
{
/* Set the corresponding ODR bit */
if (GPIO_InitStruct->GPIO_Mode == GPIO_Mode_IPU)
{
GPIOx->BSRR = (((uint32_t)0x01) << pinpos);
}
}
}
}
GPIOx->CRL = tmpreg;
}
/*---------------------------- GPIO CRH Configuration ------------------------*/
/* Configure the eight high port pins */
if (GPIO_InitStruct->GPIO_Pin > 0x00FF)
{
tmpreg = GPIOx->CRH;
for (pinpos = 0x00; pinpos < 0x08; pinpos++)
{
pos = (((uint32_t)0x01) << (pinpos + 0x08));
/* Get the port pins position */
currentpin = ((GPIO_InitStruct->GPIO_Pin) & pos);
if (currentpin == pos)
{
pos = pinpos << ;
/* Clear the corresponding high control register bits */
pinmask = ((uint32_t)0x0F) << pos;
tmpreg &= ~pinmask;
/* Write the mode configuration in the corresponding bits */
tmpreg |= (currentmode << pos);
/* Reset the corresponding ODR bit */
if (GPIO_InitStruct->GPIO_Mode == GPIO_Mode_IPD)
{
GPIOx->BRR = (((uint32_t)0x01) << (pinpos + 0x08));
}
/* Set the corresponding ODR bit */
if (GPIO_InitStruct->GPIO_Mode == GPIO_Mode_IPU)
{
GPIOx->BSRR = (((uint32_t)0x01) << (pinpos + 0x08));
}
}
}
GPIOx->CRH = tmpreg;
}
}

  其中的英文注释已经写的非常清晰,不在多余的注释了,主要的是列出它的流程图:

STM32库函数实现方法

四、总结库函数的实现

  STM32定义了许多结构体、宏、枚举类型,给用户提供了见名知意的设置参数方法。

  STM32库函数根据这些设置参数计算出要写入寄存器的值,最后写入到寄存器中,完成配置。

参考资料:《STM32库开发实战指南》

上一篇:面向对象 ---Java抽象类


下一篇:详细解析kafka之 kafka消费者组与重平衡机制