接着前天的内容,今天要试着写一下设置引脚为高电平的函数:
//函数功能:设置引脚为高电平,使用置位的寄存器
//参数说明:GPIOx:该参数为GPIO_TypeDef类型的结构体指针,指向GPIO端口的地址
// GPIO_Pin:表示某个端口的引脚号
//Example:GPIO_SetBits(GPIOH,10)
void GPIO_SetBits(GPIO_TypeDef *GPIOx,uint16_t GPIO_Pin)
{
GPIOx->BSRRL = (1<<GPIO_Pin);
}
//函数功能:设置引脚为低电平,使用置位的寄存器
//参数说明:GPIOx:该参数为GPIO_TypeDef类型的结构体指针,指向GPIO端口的地址
// GPIO_Pin:表示某个端口的引脚号
//Example:GPIO_ResetBits(GPIOH,10)
void GPIO_ResetBits(GPIO_TypeDef *GPIOx,uint16_t GPIO_Pin)
{
GPIOx->BSRRH = (1<<GPIO_Pin);
}
为了使主函数看起来更加简洁,我们新增两个文件,即stm32f4xx_gpio.c与stm32f4xx_gpio.h,写入以上内容,将各种寄存器配置写入stm32f4xx.h,为了防止出现函数的重复定义,我们在后缀为.h的文件中加入#ifndef:
#ifndef __STM32F4XX_H
#define __STM32F4XX_H
#define PERIPH_BASE ((unsigned int)0x40000000)
#define AHB1PERIPH_BASE (PERIPH_BASE+0x00020000)
#define GPIOH_BASE (AHB1PERIPH_BASE+0x00001C00)
#define GPIOA_BASE (AHB1PERIPH_BASE+0x00000000)
#define GPIOH_MODER *(unsigned int *)(GPIOH_BASE+0X00)
#define GPIOH_OTYPER *(unsigned int *)(GPIOH_BASE+0X04)
#define GPIOH_OSPEEDR *(unsigned int *)(GPIOH_BASE+0X08)
#define GPIOH_PUPDR *(unsigned int *)(GPIOH_BASE+0X0C)
#define GPIOH_IDR *(unsigned int *)(GPIOH_BASE+0X10)
#define GPIOH_ODR *(unsigned int *)(GPIOH_BASE+0X14)
#define GPIOH_BSRR *(unsigned int *)(GPIOH_BASE+0X18)
#define GPIOH_LCKR *(unsigned int *)(GPIOH_BASE+0X1C)
#define GPIOH_AFRL *(unsigned int *)(GPIOH_BASE+0X20)
#define GPIOH_AFRH *(unsigned int *)(GPIOH_BASE+0X24)
typedef unsigned int uint32_t;
typedef unsigned short uint16_t;
typedef struct
{
uint32_t MODER;
uint32_t OTYPER;
uint32_t OSPEEDR;
uint32_t PUPDR;
uint32_t IDR;
uint32_t ODR;
uint16_t BSRRL;
uint16_t BSRRH;
uint32_t LCKR;
uint32_t AFR[2];
}GPIO_TypeDef;
#define GPIOH ((GPIO_TypeDef *)GPIOH_BASE)
#define GPIOA ((GPIO_TypeDef *)GPIOA_BASE)
#define RCC_BASE (AHB1PERIPH_BASE+0x00003800)
#define RCC_AHB1ENR *(unsigned int *)(RCC_BASE+0X30)
#endif /* __STM32F4XX_H */
#ifndef __STM32F4XX_GPIO_H
#define __STM32F4XX_GPIO_H
#include "stm32f4xx.h"
void GPIO_SetBits(GPIO_TypeDef *GPIOx,uint16_t GPIO_Pin);
void GPIO_ResetBits(GPIO_TypeDef *GPIOx,uint16_t GPIO_Pin);
#endif /* __STM32F4XX_GPIO_H */
为什么需要加入#ifndef...来防止重复声明呢,因为在执行时,我们第一次已经定义了"stm32f4xx.h"的这个头文件,所以它在第二次调用的时候,不会再定义一次。宏定义前的下划线可以去掉,加下划线是为了区分其他的宏定义,防止重复定义。
为了表示得更直观,我们把输入的 端口数10改为GPIO_Pin_10,所以需要在文件中添加如下:
#define GPIO_Pin_0 ((uint16_t)(0x0001))
#define GPIO_Pin_1 ((uint16_t)(0x0002))
#define GPIO_Pin_2 ((uint16_t)(0x0004))
#define GPIO_Pin_3 ((uint16_t)(0x0008))
#define GPIO_Pin_4 ((uint16_t)(0x0010))
#define GPIO_Pin_5 ((uint16_t)(0x0020))
#define GPIO_Pin_6 ((uint16_t)(0x0040))
#define GPIO_Pin_7 ((uint16_t)(0x0080))
#define GPIO_Pin_8 ((uint16_t)(0x0100))
#define GPIO_Pin_9 ((uint16_t)(0x0200))
#define GPIO_Pin_10 ((uint16_t)(0x0400))
#define GPIO_Pin_11 ((uint16_t)(0x0800))
#define GPIO_Pin_12 ((uint16_t)(0x1000))
#define GPIO_Pin_13 ((uint16_t)(0x2000))
#define GPIO_Pin_14 ((uint16_t)(0x4000))
#define GPIO_Pin_15 ((uint16_t)(0x8000))
#define GPIO_Pin_ALL ((uint16_t)(0xFFFF))
//此时调用置位寄存器时,可直接:
GPIOx->BSRRH = GPIO_Pin_4;
写出了自己可以操作置高低电平的函数之后,我们要实现GPIO各种初始化的函数,整个GPIO的初始化顺序如下:
-
确定引脚号 -
确定要输入还是输出 -
配置是上拉还是下拉 -
确定是推挽还是开漏 -
配置输出的速度 -
最后就开始操作ODR或者是BS的寄存器
按照上面的思路,我们需要定义下面的结构体,结构体的顺序是由上方的思路进行排序:
//GPIO初始化结构体
typedef struct
{
uint32_t GPIO_Pin; //引脚口
GPIOMode_TypeDef GPIO_Mode; //输出还是输出
GPIOOType_TypeDef GPIO_OType; //推挽还是开漏
GPIOSpeed_TypeDef GPIO_Speed; //输出速度
GPIOPuPd_TypeDef GPIO_PuPd; //上拉还是下拉
}GPIO_InitTypeDef;
我们再使用枚举来定义以上的结构体:
?
typedef enum
{
GPIO_Mode_IN = 0x00, /* 输入(复位状态) */
GPIO_Mode_OUT = 0x01, /* 通用输出模式 */
GPIO_Mode_AF = 0x02, /* 复用功能模式 */
GPIO_Mode_AN = 0x03, /* 模拟模式 */
}GPIOMode_TypeDef;
剩下的寄存器也如以上的操作:
typedef enum
{
GPIO_OType_PP = 0x00, /* 推挽输出*/
GPIO_OType_OD = 0x01, /* 开漏输出*/
}GPIOOType_TypeDef;
typedef enum
{
GPIO_Speed_2MHZ = 0x00, /* 2MHZ */
GPIO_Speed_25MHZ = 0x01, /* 25MHZ */
GPIO_Speed_50MHZ = 0x02, /* 50MHZ */
GPIO_Speed_100MHZ = 0x03, /* 100MHZ */
}GPIOSpeed_TypeDef;
typedef enum
{
GPIO_PuPd_NOPULL = 0x00, /*浮空 */
GPIO_PuPd_UP = 0x01, /*上拉*/
GPIO_PuPd_DOWN = 0x02, /*下拉 */
}GPIOPuPd_TypeDef;
做好了上述准备后,我们需要使用以上枚举或者是结构体来代替以下的代码:
/* PH10配置为输出 */
GPIOH->MODER &= ~(3<<2*10);
GPIOH->MODER |= (1<<2*10);
/* 配置为上拉 */
GPIOH->PUPDR &= ~(3<<2*10);
GPIOH->PUPDR |= (1<<2*10);
/* 设置PH10为推挽输出 */
GPIOH->OTYPER &= ~(1<<10);
/* PH10速度为50M */
GPIOH->OSPEEDR &= ~(3<<2*10);
GPIOH->OSPEEDR |= (2<<2*10);
结果如下:
#define LED_R_GPIO_PIN GPIO_Pin_10
#define LED_R_GPIO_PORT GPIOH
#define LED_G_GPIO_PIN GPIO_Pin_11
#define LED_G_GPIO_PORT GPIOH
#define LED_B_GPIO_PIN GPIO_Pin_12
#define LED_B_GPIO_PORT GPIOH
void LED_GPIO_Config(void)
{
GPIO_InitTypeDef GPIO_Init_struct;
//先把除了引脚口外的其他东西设置好
GPIO_Init_struct.GPIO_Mode = GPIO_Mode_OUT;
GPIO_Init_struct.GPIO_Speed = GPIO_Speed_50MHZ;
GPIO_Init_struct.GPIO_PuPd = GPIO_PuPd_UP;
GPIO_Init_struct.GPIO_Pin = LED_R_GPIO_PIN;
GPIO_Init( LED_R_GPIO_PORT,&GPIO_Init_struct );
GPIO_SetBits(LED_R_GPIO_PORT,LED_R_GPIO_PIN);
GPIO_Init_struct.GPIO_Pin = LED_G_GPIO_PIN;
GPIO_Init( LED_G_GPIO_PORT,&GPIO_Init_struct );
GPIO_SetBits(LED_G_GPIO_PORT,LED_G_GPIO_PIN);
GPIO_Init_struct.GPIO_Pin = LED_B_GPIO_PIN;
GPIO_Init( LED_B_GPIO_PORT,&GPIO_Init_struct );
GPIO_SetBits(LED_B_GPIO_PORT,LED_B_GPIO_PIN);
}
我们需要一个子函数来将我们配置的结构体成员配置给寄存器:
?
//要将我们配置的结构体成员写入寄存器中,相当于GPIO初始化函数
//Example:GPIO_Init(GPIOH,&GPIO_InitStruct);
void GPIO_Init(GPIO_TypeDef *GPIOx,GPIO_InitTypeDef *GPIO_InitStruct )
{
uint32_t pinpos =0,\ //用于位置自加
pos=0,\ //用于显示当前位置
currentpos=0; //用于找到与设置的引脚口相同的位置
//首先要找到引脚号
for( pinpos = 0x00; pinpos < 16; pinpos++ )
{
pos = ((uint16_t)0x01) << pinpos;
//通过找到当前值与设置值都为1时,相与则为1,相对于找到了当前设置的引脚
currentpos = pos & GPIO_InitStruct->GPIO_Pin;
if ( currentpos == pos )
{
//模式寄存器初始化为0,这种由两个位控制的,有多种取值的,先清0,再转换
GPIOx->MODER &= ~(3 << (2*pinpos));
//设置为输出/输入模式
GPIOx->MODER |= ( ((uint32_t)GPIO_InitStruct->GPIO_Mode) << (2*pinpos) );
//上拉/下拉寄存器清零
GPIOx->PUPDR &= ~(3<<2*pinpos);
//设置为上拉/下拉模式
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 &= ~(3<<2*pinpos);
//速度寄存器置1
GPIOx->OSPEEDR |= (((uint32_t)GPIO_InitStruct->GPIO_Speed) <<(2*pinpos));
}
//推挽/开漏模式清零
GPIOx->OTYPER &= ~(1<<pinpos);
//设置推挽/开漏模式
GPIOx->OTYPER |= (((uint16_t)GPIO_InitStruct->GPIO_OType)<<pinpos);
}
}
}
到现在终于把写库的流程大概摸索清楚了...
|