6-3 自建库函数点亮LED灯led2

1 什么是STM32固件库

  • STM32标准函数库(固件库):是由ST公司为STM32微控制器提供的函数接口,开发者可以通过调用这些函数接口来配置STM32的寄存器,简化开发过程。

1.1 固件库的优点

  • 使用固件库可以减少底层寄存器操作的复杂性,开发效率显著提高;
  • 代码更具可读性,便于理解和维护;
  • 通过高层API进行开发,降低了因直接操作寄存器而导致的错误风险。

1.2固件库的结构

  • 库的结构:固件库位于寄存器操作与用户驱动层之间,负责处理与寄存器直接相关的配置,并为用户提供简化的配置接口,如下图所示:

2 构建库函数

2.1 修改寄存器地址封装

在上一篇文章中(http://ckun.fun/index.php/2024/12/02/led/),我们是直接操作寄存器的绝对地址,如果每个外设都这样,将会变的非常繁琐。

考虑到外设寄存器的地址是基于外设基地址的偏移地址,并且这些寄存器在外设基地址上是连续递增的(每个寄存器占32个字节),我们可以借鉴结构体的概念来简化这一过程。通过定义外设的结构体,结构体的地址即为外设的基地址,而结构体的成员则对应各个寄存器,成员的排列顺序与寄存器的顺序一致。这样,在操作寄存器时,只需知道外设的基地址,便可通过访问结构体的成员来操作所有寄存器。可参考如下代码:

/* stm32f4xx.h */

/* 片上外设基地址 */
#define PERIPH_BASE           ((unsigned int)0x40000000)

/* 总线基地址 */
#define AHB1PERIPH_BASE       (PERIPH_BASE + 0x00020000)

/* GPIO外设基地址 */
#define GPIOA_BASE            (AHB1PERIPH_BASE + 0x0000)
#define GPIOB_BASE            (AHB1PERIPH_BASE + 0x0400)
#define GPIOC_BASE            (AHB1PERIPH_BASE + 0x0800)
#define GPIOD_BASE            (AHB1PERIPH_BASE + 0x0C00)
#define GPIOE_BASE            (AHB1PERIPH_BASE + 0x1000)
#define GPIOF_BASE            (AHB1PERIPH_BASE + 0x1400)
#define GPIOG_BASE            (AHB1PERIPH_BASE + 0x1800)
#define GPIOH_BASE            (AHB1PERIPH_BASE + 0x1C00)

/* RCC外设基地址 */
#define RCC_BASE              (AHB1PERIPH_BASE + 0x3800)

// volatile表示易变的变量,防止编译器优化
#define __IO volatile
typedef unsigned int uint32_t;
typedef unsigned short uint16_t;

/* GPIO寄存器列表 */
typedef struct {
    __IO uint32_t MODER;   /* GPIO模式寄存器,地址偏移: 0x00 */
    __IO uint32_t OTYPER;  /* GPIO输出类型寄存器,地址偏移: 0x04 */
    __IO uint32_t OSPEEDR; /* GPIO输出速度寄存器,地址偏移: 0x08 */
    __IO uint32_t PUPDR;   /* GPIO上拉/下拉寄存器,地址偏移: 0x0C */
    __IO uint32_t IDR;     /* GPIO输入数据寄存器,地址偏移: 0x10 */
    __IO uint32_t ODR;     /* GPIO输出数据寄存器,地址偏移: 0x14 */
    __IO uint16_t BSRRL;   /* GPIO置位/复位寄存器低16位部分,地址偏移: 0x18 */
    __IO uint16_t BSRRH;   /* GPIO置位/复位寄存器高16位部分,地址偏移: 0x1A */
    __IO uint32_t LCKR;    /* GPIO配置锁定寄存器,地址偏移: 0x1C */
    __IO uint32_t AFR[2];  /* GPIO复用功能配置寄存器,地址偏移: 0x20-0x24 */
} GPIO_TypeDef;

/* RCC寄存器列表 */
typedef struct {
    __IO uint32_t CR;           /*!< RCC时钟控制寄存器,地址偏移: 0x00 */
    __IO uint32_t PLLCFGR;      /*!< RCC PLL配置寄存器,地址偏移: 0x04 */
    __IO uint32_t CFGR;         /*!< RCC时钟配置寄存器,地址偏移: 0x08 */
    __IO uint32_t CIR;          /*!< RCC时钟中断寄存器,地址偏移: 0x0C */
    __IO uint32_t AHB1RSTR;     /*!< RCC AHB1外设复位寄存器,地址偏移: 0x10 */
    __IO uint32_t AHB2RSTR;     /*!< RCC AHB2外设复位寄存器,地址偏移: 0x14 */
    __IO uint32_t AHB3RSTR;     /*!< RCC AHB3外设复位寄存器,地址偏移: 0x18 */
    __IO uint32_t RESERVED0;     /*!< 保留,地址偏移: 0x1C */
    __IO uint32_t APB1RSTR;     /*!< RCC APB1外设复位寄存器,地址偏移: 0x20 */
    __IO uint32_t APB2RSTR;     /*!< RCC APB2外设复位寄存器,地址偏移: 0x24 */
    __IO uint32_t RESERVED1[2];  /*!< 保留,地址偏移: 0x28-0x2C */
    __IO uint32_t AHB1ENR;      /*!< RCC AHB1外设时钟寄存器,地址偏移: 0x30 */
    __IO uint32_t AHB2ENR;      /*!< RCC AHB2外设时钟寄存器,地址偏移: 0x34 */
    __IO uint32_t AHB3ENR;      /*!< RCC AHB3外设时钟寄存器,地址偏移: 0x38 */
    /* RCC后面还有很多寄存器,此处省略 */
} RCC_TypeDef;

2.2 定义访问外设的结构体指针

以结构体的形式定义好了外设寄存器后,使用结构体前还需要给结构体的首地址赋值,才能访问到需要的寄存器。为方便操作,我们给每个外设都定义好指向它地址的结构体指针。

这些宏通过强制类型转换把外设的基地址转换成 GPIO_TypeDef 类型的指针,从而得到 GPIOA、GPIOB 等直接指向对应外设的指针,通过对结构体指针的操作,即可访问对应外设的寄存器。

/* 定义GPIOA-H寄存器结构体指针 */
#define GPIOA                ((GPIO_TypeDef *) GPIOA_BASE)
#define GPIOB                ((GPIO_TypeDef *) GPIOB_BASE)
#define GPIOC                ((GPIO_TypeDef *) GPIOC_BASE)
#define GPIOD                ((GPIO_TypeDef *) GPIOD_BASE)
#define GPIOE                ((GPIO_TypeDef *) GPIOE_BASE)
#define GPIOF                ((GPIO_TypeDef *) GPIOF_BASE)
#define GPIOG                ((GPIO_TypeDef *) GPIOG_BASE)
#define GPIOH                ((GPIO_TypeDef *) GPIOH_BASE)

/* 定义RCC外设寄存器结构体指针 */
#define RCC                  ((RCC_TypeDef *) RCC_BASE)

2.3 定义位操作函数

在“stm32f4xx_gpio.c”文件定义两个位操作函数,分别用于控制引脚输出高电平和低电平:

/**
  * 函数功能:设置引脚为高电平
  * 参数说明:GPIOx,该参数为GPIO_TypeDef类型的指针,指向GPIO端口的地址
  *            GPIO_Pin: 选择要设置的GPIO端口引脚,可输入宏GPIO_Pin_0-15,
  *                       表示GPIOx端口的0-15号引脚。
  */
void GPIO_SetBits(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin)
{
    /* 设置GPIOx端口BSRRL寄存器的第GPIO_Pin位,使其输出高电平 */
    /* 因为BSRR寄存器写0不影响, 
       GPIO_Pin只是对应位为1,其它位均为0,所以可以直接赋值 */
    
    GPIOx->BSRRL = GPIO_Pin;
}

/**
  * 函数功能:设置引脚为低电平
  * 参数说明:GPIOx,该参数为GPIO_TypeDef类型的指针,指向GPIO端口的地址
  *            GPIO_Pin: 选择要设置的GPIO端口引脚,可输入宏GPIO_Pin_0-15,
  *                       表示GPIOx端口的0-15号引脚。
  */
void GPIO_ResetBits(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin)
{
    /* 设置GPIOx端口BSRRH寄存器的第GPIO_Pin位,使其输出低电平 */
    /* 因为BSRR寄存器写0不影响, 
       GPIO_Pin只是对应位为1,其它位均为0,所以可以直接赋值 */
    
    GPIOx->BSRRH = GPIO_Pin;
}
  • 这两个函数体内都是只有一个语句,对 GPIOx 的 BSRRL 或 BSRRH 寄存器赋值,从而设置引脚为高电平或低电平。
  • 其中 GPIOx 是一个指针变量,通过函数的输入参数我们可以修改它的值,如给它赋予 GPIOA、GPIOB、GPIOH 等结构体指针值,这个函数就可以控制相应的 GPIOA、GPIOB、GPIOH 等端口的输出。
  • 利用这两个位操作函数,就可以方便地操作 GPIO 的引脚电平了,示例如下:
/* 控制 GPIOH 的引脚 10 输出高电平 */
GPIO_SetBits(GPIOH,(uint16_t)(1<<10));
/* 控制 GPIOH 的引脚 10 输出低电平 */
GPIO_ResetBits(GPIOH,(uint16_t)(1<<10));

/* 控制 GPIOH 的引脚 10、引脚 11 输出高电平,使用“|”同时控制多个引脚 */
GPIO_SetBits(GPIOH,(uint16_t)(1<<10)|(uint16_t)(1<<11));
/* 控制 GPIOH 的引脚 10、引脚 11 输出低电平 */
GPIO_ResetBits(GPIOH,(uint16_t)(1<<10)|(uint16_t)(1<<10));
  • 当然,使用以上函数输入参数,设置引脚号时,还是很不方便,且可读性差,为此我们把表示 16 个引脚号的操作数都定义成宏,如下所示:
/*GPIO引脚号定义*/
#define GPIO_Pin_0                 ((uint16_t)0x0001)  /*!< 选择Pin0 (1<<0) */
#define GPIO_Pin_1                 ((uint16_t)0x0002)  /*!< 选择Pin1 (1<<1)*/
#define GPIO_Pin_2                 ((uint16_t)0x0004)  /*!< 选择Pin2 (1<<2)*/
#define GPIO_Pin_3                 ((uint16_t)0x0008)  /*!< 选择Pin3 (1<<3)*/
#define GPIO_Pin_4                 ((uint16_t)0x0010)  /*!< 选择Pin4 */
#define GPIO_Pin_5                 ((uint16_t)0x0020)  /*!< 选择Pin5 */
#define GPIO_Pin_6                 ((uint16_t)0x0040)  /*!< 选择Pin6 */
#define GPIO_Pin_7                 ((uint16_t)0x0080)  /*!< 选择Pin7 */
#define GPIO_Pin_8                 ((uint16_t)0x0100)  /*!< 选择Pin8 */
#define GPIO_Pin_9                 ((uint16_t)0x0200)  /*!< 选择Pin9 */
#define GPIO_Pin_10                ((uint16_t)0x0400)  /*!< 选择Pin10 */
#define GPIO_Pin_11                ((uint16_t)0x0800)  /*!< 选择Pin11 */
#define GPIO_Pin_12                ((uint16_t)0x1000)  /*!< 选择Pin12 */
#define GPIO_Pin_13                ((uint16_t)0x2000)  /*!< 选择Pin13 */
#define GPIO_Pin_14                ((uint16_t)0x4000)  /*!< 选择Pin14 */
#define GPIO_Pin_15                ((uint16_t)0x8000)  /*!< 选择Pin15 */
#define GPIO_Pin_All               ((uint16_t)0xFFFF)  /*!< 选择全部引脚 */
  • 使用以上代码控制 GPIO,我们就不需要再看寄存器了,直接从函数名和输入参数就可以直观看出这个语句要实现什么操作
/* 控制 GPIOH 的引脚 10 输出高电平 */
GPIO_SetBits(GPIOH,GPIO_Pin_10);
/* 控制 GPIOH 的引脚 10 输出低电平 */
GPIO_ResetBits(GPIOH,GPIO_Pin_10);

/* 控制 GPIOH 的引脚 10、引脚 11 输出高电平,使用“|”,同时控制多个引脚 */
GPIO_SetBits(GPIOH,GPIO_Pin_10|GPIO_Pin_11);
/* 控制 GPIOH 的引脚 10、引脚 11 输出低电平 */
GPIO_ResetBits(GPIOH,GPIO_Pin_10|GPIO_Pin_11);

2.4 定义初始化结构体 GPIO_InitTypeDef

在定义位操作函数后,我们成功简化了控制GPIO输出电平的代码。然而,在控制GPIO输出电平之前,仍需对GPIO引脚进行初始化,这一过程涉及多个寄存器,因此我们希望能够以同样简化的方式实现GPIO的初始化。为此,我们可以将GPIO初始化所需的各种参数封装为一个结构体,声明一个名为GPIO_InitTypeDef的结构体类型。通过这种方式,我们能够集中管理与GPIO初始化相关的配置参数,使得初始化过程更加清晰和高效。

/**
  * GPIO初始化结构体类型定义
  */ 
typedef struct 
{
    uint32_t GPIO_Pin;              /*!< 选择要配置的GPIO引脚,可输入 GPIO_Pin_ 定义的宏 */

    uint8_t GPIO_Mode;     /*!< 选择GPIO引脚的工作模式,可输入 GPIOMode_TypeDef 定义的枚举值 */

    uint8_t GPIO_Speed;   /*!< 选择GPIO引脚的速率,可输入 GPIOSpeed_TypeDef 定义的枚举值 */

    uint8_t GPIO_OType;   /*!< 选择GPIO引脚输出类型,可输入 GPIOOType_TypeDef 定义的枚举值 */

    uint8_t GPIO_PuPd;     /*!< 选择GPIO引脚的上/下拉模式,可输入 GPIOPuPd_TypeDef 定义的枚举值 */
} GPIO_InitTypeDef;

2.5 定义引脚模式的枚举类型

上述GPIO_InitTypeDef 结构体很直接,美中不足的是在对结构体中各个成员赋值时还需要看具体哪个模式对应哪个数值,如 GPIO_Mode 成员的“输入/输出/复用/模拟”模式对应二进制值“00 、01、10、11”,我们不希望每次用到都要去查找这些索引值,所以使用 C 语言中的枚举语法定义这些参数,如下所示:

/*GPIO端口配置模式的枚举定义 */   
typedef enum
{ 
  GPIO_Mode_IN   = 0x00, /*!< 输入模式 */
  GPIO_Mode_OUT  = 0x01, /*!< 输出模式 */
  GPIO_Mode_AF   = 0x02, /*!< 复用模式 */
  GPIO_Mode_AN   = 0x03  /*!< 模拟模式 */
}GPIOMode_TypeDef;

/*GPIO输出类型枚举定义 */  
typedef enum
{ 
  GPIO_OType_PP = 0x00,	/*!< 推挽模式 */
  GPIO_OType_OD = 0x01	/*!< 开漏模式 */
}GPIOOType_TypeDef;


/*GPIO输出速率枚举定义 */  
typedef enum
{ 
  GPIO_Speed_2MHz   = 0x00, /*!< 2MHz   */
  GPIO_Speed_25MHz  = 0x01, /*!< 25MHz  */
  GPIO_Speed_50MHz  = 0x02, /*!< 50MHz  */
  GPIO_Speed_100MHz = 0x03  /*!<100MHz  */
}GPIOSpeed_TypeDef;

/*GPIO上/下拉配置枚举定义 */ 
typedef enum
{ 
  GPIO_PuPd_NOPULL = 0x00,/*浮空*/
  GPIO_PuPd_UP     = 0x01, /*上拉*/
  GPIO_PuPd_DOWN   = 0x02  /*下拉*/
}GPIOPuPd_TypeDef;
  • 有了这些枚举定义,我们的 GPIO_InitTypeDef 结构体也可以使用枚举类型来限定输入了;
  • 在这里,使用枚举类型可以对结构体成员起到限定输入的作用,只能输入相应已定义的枚举值,如下所示:
/**
  * GPIO初始化结构体类型定义
  */ 
typedef struct 
{
    uint32_t GPIO_Pin;              /*!< 选择要配置的GPIO引脚,可输入 GPIO_Pin_ 定义的宏 */

    GPIOMode_TypeDef GPIO_Mode;     /*!< 选择GPIO引脚的工作模式,可输入 GPIOMode_TypeDef 定义的枚举值 */

    GPIOSpeed_TypeDef GPIO_Speed;   /*!< 选择GPIO引脚的速率,可输入 GPIOSpeed_TypeDef 定义的枚举值 */

    GPIOOType_TypeDef GPIO_OType;   /*!< 选择GPIO引脚输出类型,可输入 GPIOOType_TypeDef 定义的枚举值 */

    GPIOPuPd_TypeDef GPIO_PuPd;     /*!< 选择GPIO引脚的上/下拉模式,可输入 GPIOPuPd_TypeDef 定义的枚举值 */
} GPIO_InitTypeDef;

2.6 定义 GPIO 初始化函数

  • 对初始化结构体赋值后,把它输入到 GPIO 初始化函数,由它来实现寄存器配置,如下所示:
/**
  * 函数功能:初始化引脚模式
  * 参数说明:
  *   GPIOx: 该参数为GPIO_TypeDef类型的指针,指向GPIO端口的地址
  *   GPIO_InitTypeDef: GPIO_InitTypeDef结构体指针,指向初始化变量
  */
void GPIO_Init(GPIO_TypeDef* GPIOx, GPIO_InitTypeDef* GPIO_InitStruct)
{
    uint32_t pinpos = 0x00, pos = 0x00, currentpin = 0x00;

    /*-- GPIO Mode Configuration --*/
    for (pinpos = 0x00; pinpos < 16; pinpos++)
    {
        /* 计算引脚号0-15 */
        pos = ((uint32_t)0x01) << pinpos;

        /* 检查GPIO_Pin中的pinpos位是否为1 */
        currentpin = (GPIO_InitStruct->GPIO_Pin) & pos;

        /* currentpin == pos时执行初始化 */
        if (currentpin == pos)
        {        
            /* 清空GPIOx端口的MODER寄存器中对应引脚的MODER位 */
            GPIOx->MODER  &= ~(3 << (2 * pinpos));
        
            /* 设置GPIOx端口的MODER寄存器,以配置为输入/输出/复用输出/模拟模式 */
            GPIOx->MODER |= (((uint32_t)GPIO_InitStruct->GPIO_Mode) << (2 * pinpos));

            /* 清空GPIOx端口的PUPDR寄存器中对应引脚的PUPDR位 */
            GPIOx->PUPDR &= ~(3 << (2 * pinpos));
        
            /* 设置GPIOx端口的PUPDR寄存器,以配置上拉/下拉模式 */
            GPIOx->PUPDR |= (((uint32_t)GPIO_InitStruct->GPIO_PuPd) << (2 * pinpos));		
		
            /* 若模式为"输出/复用输出",则设置速度与输出类型 */
            if ((GPIO_InitStruct->GPIO_Mode == GPIO_Mode_OUT) || (GPIO_InitStruct->GPIO_Mode == GPIO_Mode_AF))
            {
                /* 清空GPIOx端口的OSPEEDR寄存器中对应引脚的OSPEEDR位 */
                GPIOx->OSPEEDR &= ~(3 << (2 * pinpos));

                /* 设置GPIOx端口的OSPEEDR寄存器,以配置输出速度 */
                GPIOx->OSPEEDR |= ((uint32_t)(GPIO_InitStruct->GPIO_Speed) << (2 * pinpos));

                /* 清空GPIOx端口的OTYPER寄存器中对应引脚的OTYPER位 */
                GPIOx->OTYPER  &= ~(1 << (pinpos));

                /* 设置GPIOx端口的OTYPER寄存器,以配置为"推挽/开漏"输出类型 */
                GPIOx->OTYPER |= (uint16_t)(((uint16_t)GPIO_InitStruct->GPIO_OType) << (pinpos));
            }
        }
    }
}
  • GPIOx:指向要初始化的GPIO外设的指针。
  • GPIO_InitStruct:指向包含初始化参数的结构体的指针。
  • 利用for循环计算引脚号:
    • 函数通过循环遍历GPIO引脚(0到15),计算要初始化的引脚号;
    • 通过检查 GPIO_InitStruct 结构体成员中的 GPIO_Pin 字段,使用位运算计算出当前引脚的状态;
    • 计算得到的引脚号将存储在 pinpos 变量中,表示当前正在处理的GPIO引脚。
  • 配置寄存器:
    • 得到引脚号 pinpos 后,函数使用 GPIO_InitStruct 中的各个成员来配置相应的寄存器;
    • 首先清空与 pinpos 对应的寄存器位,以确保之前的配置不会影响新的设置;
    • 然后,根据初始化结构体中的成员值,设置寄存器的配置位:
      • GPIO_Mode 成员对应于MODER寄存器,用于配置引脚的工作模式(如输入、输出、复用等);
      • GPIO_PuPd 成员对应于PUPDR寄存器,用于配置上拉或下拉模式。

2.7 完整代码

2.7.1 stm32f4xx.h

/* 片上外设基地址 */
#define PERIPH_BASE           ((unsigned int)0x40000000)

/* 总线基地址 */
#define AHB1PERIPH_BASE       (PERIPH_BASE + 0x00020000)

/* GPIO外设基地址 */
#define GPIOA_BASE            (AHB1PERIPH_BASE + 0x0000)
#define GPIOB_BASE            (AHB1PERIPH_BASE + 0x0400)
#define GPIOC_BASE            (AHB1PERIPH_BASE + 0x0800)
#define GPIOD_BASE            (AHB1PERIPH_BASE + 0x0C00)
#define GPIOE_BASE            (AHB1PERIPH_BASE + 0x1000)
#define GPIOF_BASE            (AHB1PERIPH_BASE + 0x1400)
#define GPIOG_BASE            (AHB1PERIPH_BASE + 0x1800)
#define GPIOH_BASE            (AHB1PERIPH_BASE + 0x1C00)

/* RCC外设基地址 */
#define RCC_BASE              (AHB1PERIPH_BASE + 0x3800)

// volatile表示易变的变量,防止编译器优化
#define __IO volatile
typedef unsigned int uint32_t;
typedef unsigned short uint16_t;

/* GPIO寄存器列表 */
typedef struct {
    __IO uint32_t MODER;   /* GPIO模式寄存器,地址偏移: 0x00 */
    __IO uint32_t OTYPER;  /* GPIO输出类型寄存器,地址偏移: 0x04 */
    __IO uint32_t OSPEEDR; /* GPIO输出速度寄存器,地址偏移: 0x08 */
    __IO uint32_t PUPDR;   /* GPIO上拉/下拉寄存器,地址偏移: 0x0C */
    __IO uint32_t IDR;     /* GPIO输入数据寄存器,地址偏移: 0x10 */
    __IO uint32_t ODR;     /* GPIO输出数据寄存器,地址偏移: 0x14 */
    __IO uint16_t BSRRL;   /* GPIO置位/复位寄存器低16位部分,地址偏移: 0x18 */
    __IO uint16_t BSRRH;   /* GPIO置位/复位寄存器高16位部分,地址偏移: 0x1A */
    __IO uint32_t LCKR;    /* GPIO配置锁定寄存器,地址偏移: 0x1C */
    __IO uint32_t AFR[2];  /* GPIO复用功能配置寄存器,地址偏移: 0x20-0x24 */
} GPIO_TypeDef;

/* RCC寄存器列表 */
typedef struct {
    __IO uint32_t CR;           /*!< RCC时钟控制寄存器,地址偏移: 0x00 */
    __IO uint32_t PLLCFGR;      /*!< RCC PLL配置寄存器,地址偏移: 0x04 */
    __IO uint32_t CFGR;         /*!< RCC时钟配置寄存器,地址偏移: 0x08 */
    __IO uint32_t CIR;          /*!< RCC时钟中断寄存器,地址偏移: 0x0C */
    __IO uint32_t AHB1RSTR;     /*!< RCC AHB1外设复位寄存器,地址偏移: 0x10 */
    __IO uint32_t AHB2RSTR;     /*!< RCC AHB2外设复位寄存器,地址偏移: 0x14 */
    __IO uint32_t AHB3RSTR;     /*!< RCC AHB3外设复位寄存器,地址偏移: 0x18 */
    __IO uint32_t RESERVED0;     /*!< 保留,地址偏移: 0x1C */
    __IO uint32_t APB1RSTR;     /*!< RCC APB1外设复位寄存器,地址偏移: 0x20 */
    __IO uint32_t APB2RSTR;     /*!< RCC APB2外设复位寄存器,地址偏移: 0x24 */
    __IO uint32_t RESERVED1[2];  /*!< 保留,地址偏移: 0x28-0x2C */
    __IO uint32_t AHB1ENR;      /*!< RCC AHB1外设时钟寄存器,地址偏移: 0x30 */
    __IO uint32_t AHB2ENR;      /*!< RCC AHB2外设时钟寄存器,地址偏移: 0x34 */
    __IO uint32_t AHB3ENR;      /*!< RCC AHB3外设时钟寄存器,地址偏移: 0x38 */
    /* RCC后面还有很多寄存器,此处省略 */
} RCC_TypeDef;

/* 定义GPIOA-H寄存器结构体指针 */
#define GPIOA                ((GPIO_TypeDef *) GPIOA_BASE)
#define GPIOB                ((GPIO_TypeDef *) GPIOB_BASE)
#define GPIOC                ((GPIO_TypeDef *) GPIOC_BASE)
#define GPIOD                ((GPIO_TypeDef *) GPIOD_BASE)
#define GPIOE                ((GPIO_TypeDef *) GPIOE_BASE)
#define GPIOF                ((GPIO_TypeDef *) GPIOF_BASE)
#define GPIOG                ((GPIO_TypeDef *) GPIOG_BASE)
#define GPIOH                ((GPIO_TypeDef *) GPIOH_BASE)

/* 定义RCC外设寄存器结构体指针 */
#define RCC                  ((RCC_TypeDef *) RCC_BASE)

2.7.2 stm32f4xx_gpio.h

#include "stm32f4xx.h"

/*GPIO引脚号定义*/
#define GPIO_Pin_0                 ((uint16_t)0x0001)  /*!< 选择Pin0 (1<<0) */
#define GPIO_Pin_1                 ((uint16_t)0x0002)  /*!< 选择Pin1 (1<<1)*/
#define GPIO_Pin_2                 ((uint16_t)0x0004)  /*!< 选择Pin2 (1<<2)*/
#define GPIO_Pin_3                 ((uint16_t)0x0008)  /*!< 选择Pin3 (1<<3)*/
#define GPIO_Pin_4                 ((uint16_t)0x0010)  /*!< 选择Pin4 */
#define GPIO_Pin_5                 ((uint16_t)0x0020)  /*!< 选择Pin5 */
#define GPIO_Pin_6                 ((uint16_t)0x0040)  /*!< 选择Pin6 */
#define GPIO_Pin_7                 ((uint16_t)0x0080)  /*!< 选择Pin7 */
#define GPIO_Pin_8                 ((uint16_t)0x0100)  /*!< 选择Pin8 */
#define GPIO_Pin_9                 ((uint16_t)0x0200)  /*!< 选择Pin9 */
#define GPIO_Pin_10                ((uint16_t)0x0400)  /*!< 选择Pin10 */
#define GPIO_Pin_11                ((uint16_t)0x0800)  /*!< 选择Pin11 */
#define GPIO_Pin_12                ((uint16_t)0x1000)  /*!< 选择Pin12 */
#define GPIO_Pin_13                ((uint16_t)0x2000)  /*!< 选择Pin13 */
#define GPIO_Pin_14                ((uint16_t)0x4000)  /*!< 选择Pin14 */
#define GPIO_Pin_15                ((uint16_t)0x8000)  /*!< 选择Pin15 */
#define GPIO_Pin_All               ((uint16_t)0xFFFF)  /*!< 选择全部引脚 */

/*GPIO端口配置模式的枚举定义 */   
typedef enum
{ 
  GPIO_Mode_IN   = 0x00, /*!< 输入模式 */
  GPIO_Mode_OUT  = 0x01, /*!< 输出模式 */
  GPIO_Mode_AF   = 0x02, /*!< 复用模式 */
  GPIO_Mode_AN   = 0x03  /*!< 模拟模式 */
}GPIOMode_TypeDef;

/*GPIO输出类型枚举定义 */  
typedef enum
{ 
  GPIO_OType_PP = 0x00,    /*!< 推挽模式 */
  GPIO_OType_OD = 0x01    /*!< 开漏模式 */
}GPIOOType_TypeDef;


/*GPIO输出速率枚举定义 */  
typedef enum
{ 
  GPIO_Speed_2MHz   = 0x00, /*!< 2MHz   */
  GPIO_Speed_25MHz  = 0x01, /*!< 25MHz  */
  GPIO_Speed_50MHz  = 0x02, /*!< 50MHz  */
  GPIO_Speed_100MHz = 0x03  /*!<100MHz  */
}GPIOSpeed_TypeDef;

/*GPIO上/下拉配置枚举定义 */ 
typedef enum
{ 
  GPIO_PuPd_NOPULL = 0x00,/*浮空*/
  GPIO_PuPd_UP     = 0x01, /*上拉*/
  GPIO_PuPd_DOWN   = 0x02  /*下拉*/
}GPIOPuPd_TypeDef;

/**
  * GPIO初始化结构体类型定义
  */ 
typedef struct 
{
    uint32_t GPIO_Pin;              /*!< 选择要配置的GPIO引脚,可输入 GPIO_Pin_ 定义的宏 */

    uint8_t GPIO_Mode;     /*!< 选择GPIO引脚的工作模式,可输入 GPIOMode_TypeDef 定义的枚举值 */

    uint8_t GPIO_Speed;   /*!< 选择GPIO引脚的速率,可输入 GPIOSpeed_TypeDef 定义的枚举值 */

    uint8_t GPIO_OType;   /*!< 选择GPIO引脚输出类型,可输入 GPIOOType_TypeDef 定义的枚举值 */

    uint8_t GPIO_PuPd;     /*!< 选择GPIO引脚的上/下拉模式,可输入 GPIOPuPd_TypeDef 定义的枚举值 */
} GPIO_InitTypeDef;

void GPIO_SetBits(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);
void GPIO_ResetBits(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);
void GPIO_Init(GPIO_TypeDef* GPIOx, GPIO_InitTypeDef* GPIO_InitStruct);

2.7.3 stm32f4xx_gpio.c

#include "stm32f4xx_gpio.h"

/**
  * 函数功能:设置引脚为高电平
  * 参数说明:GPIOx,该参数为GPIO_TypeDef类型的指针,指向GPIO端口的地址
  *            GPIO_Pin: 选择要设置的GPIO端口引脚,可输入宏GPIO_Pin_0-15,
  *                       表示GPIOx端口的0-15号引脚。
  */
void GPIO_SetBits(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin)
{
    /* 设置GPIOx端口BSRRL寄存器的第GPIO_Pin位,使其输出高电平 */
    /* 因为BSRR寄存器写0不影响, 
       GPIO_Pin只是对应位为1,其它位均为0,所以可以直接赋值 */
    
    GPIOx->BSRRL = GPIO_Pin;
}

/**
  * 函数功能:设置引脚为低电平
  * 参数说明:GPIOx,该参数为GPIO_TypeDef类型的指针,指向GPIO端口的地址
  *            GPIO_Pin: 选择要设置的GPIO端口引脚,可输入宏GPIO_Pin_0-15,
  *                       表示GPIOx端口的0-15号引脚。
  */
void GPIO_ResetBits(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin)
{
    /* 设置GPIOx端口BSRRH寄存器的第GPIO_Pin位,使其输出低电平 */
    /* 因为BSRR寄存器写0不影响, 
       GPIO_Pin只是对应位为1,其它位均为0,所以可以直接赋值 */
    
    GPIOx->BSRRH = GPIO_Pin;
}

/**
  * 函数功能:初始化引脚模式
  * 参数说明:
  *   GPIOx: 该参数为GPIO_TypeDef类型的指针,指向GPIO端口的地址
  *   GPIO_InitTypeDef: GPIO_InitTypeDef结构体指针,指向初始化变量
  */
void GPIO_Init(GPIO_TypeDef* GPIOx, GPIO_InitTypeDef* GPIO_InitStruct)
{
    uint32_t pinpos = 0x00, pos = 0x00, currentpin = 0x00;

    /*-- GPIO Mode Configuration --*/
    for (pinpos = 0x00; pinpos < 16; pinpos++)
    {
        /* 计算引脚号0-15 */
        pos = ((uint32_t)0x01) << pinpos;

        /* 检查GPIO_Pin中的pinpos位是否为1 */
        currentpin = (GPIO_InitStruct->GPIO_Pin) & pos;

        /* currentpin == pos时执行初始化 */
        if (currentpin == pos)
        {        
            /* 清空GPIOx端口的MODER寄存器中对应引脚的MODER位 */
            GPIOx->MODER  &= ~(3 << (2 * pinpos));
        
            /* 设置GPIOx端口的MODER寄存器,以配置为输入/输出/复用输出/模拟模式 */
            GPIOx->MODER |= (((uint32_t)GPIO_InitStruct->GPIO_Mode) << (2 * pinpos));

            /* 清空GPIOx端口的PUPDR寄存器中对应引脚的PUPDR位 */
            GPIOx->PUPDR &= ~(3 << (2 * pinpos));
        
            /* 设置GPIOx端口的PUPDR寄存器,以配置上拉/下拉模式 */
            GPIOx->PUPDR |= (((uint32_t)GPIO_InitStruct->GPIO_PuPd) << (2 * pinpos));        
        
            /* 若模式为"输出/复用输出",则设置速度与输出类型 */
            if ((GPIO_InitStruct->GPIO_Mode == GPIO_Mode_OUT) || (GPIO_InitStruct->GPIO_Mode == GPIO_Mode_AF))
            {
                /* 清空GPIOx端口的OSPEEDR寄存器中对应引脚的OSPEEDR位 */
                GPIOx->OSPEEDR &= ~(3 << (2 * pinpos));

                /* 设置GPIOx端口的OSPEEDR寄存器,以配置输出速度 */
                GPIOx->OSPEEDR |= ((uint32_t)(GPIO_InitStruct->GPIO_Speed) << (2 * pinpos));

                /* 清空GPIOx端口的OTYPER寄存器中对应引脚的OTYPER位 */
                GPIOx->OTYPER  &= ~(1 << (pinpos));

                /* 设置GPIOx端口的OTYPER寄存器,以配置为"推挽/开漏"输出类型 */
                GPIOx->OTYPER |= (uint16_t)(((uint16_t)GPIO_InitStruct->GPIO_OType) << (pinpos));
            }
        }
    }
}

2.7.4 main.c

#include "stm32f4xx_gpio.h" 

void Delay(uint32_t nCount); 

/**
  * 主函数,使用封装好的函数来控制LED灯
  */
int main(void)
{    
    GPIO_InitTypeDef InitStruct;

    /* 开启 GPIOH 时钟,使用外设时都要先开启它的时钟 */
    RCC->AHB1ENR |= (1 << 7);

    /* LED 端口初始化 */

    /* 初始化PH10引脚 */
    /* 选择要控制的GPIO引脚 */                                                               
    InitStruct.GPIO_Pin = GPIO_Pin_10;
    /* 设置引脚模式为输出模式 */
    InitStruct.GPIO_Mode = GPIO_Mode_OUT;
    /* 设置引脚的输出类型为推挽输出 */
    InitStruct.GPIO_OType = GPIO_OType_PP;
    /* 设置引脚为上拉模式 */
    InitStruct.GPIO_PuPd = GPIO_PuPd_UP;
    /* 设置引脚速率为2MHz */   
    InitStruct.GPIO_Speed = GPIO_Speed_2MHz;
    /* 调用库函数,使用上面配置的GPIO_InitStructure初始化GPIO */
    GPIO_Init(GPIOH, &InitStruct);    

    /* 使引脚输出低电平, 点亮LED1 */
    GPIO_ResetBits(GPIOH, GPIO_Pin_10);

    /* 延时一段时间 */
    Delay(0xFFFFFF);    
    
    /* 使引脚输出高电平,关闭LED1 */
    GPIO_SetBits(GPIOH, GPIO_Pin_10);
    
    /* 初始化PH11引脚 */
    InitStruct.GPIO_Pin = GPIO_Pin_11;
    GPIO_Init(GPIOH, &InitStruct);
    
    /* 使引脚输出低电平,点亮LED2 */
    GPIO_ResetBits(GPIOH, GPIO_Pin_11);

    while (1);
}

// 简单的延时函数,让CPU执行无意义指令,消耗时间
// 具体延时时间难以计算,以后我们可使用定时器精确延时
void Delay(uint32_t nCount)     
{
    for (; nCount != 0; nCount--);
}

// 函数为空,目的是为了骗过编译器不报错
void SystemInit(void)
{    
}

发表评论