预置知识: 开时钟
STM32 每一个片上外设资源都有自己的时钟,这些时钟被一个叫做RCC的外设统一管理,
所以,每一个片上外设想要应用第一件事就是: 开时钟!!!!!
根据系统结构图,GPIO都在APB2总线上
![]()
所以,我们要用void RCC_APB2PeriphClockCmd(uint32_t RCC_APB2Periph, FunctionalState NewState)
函数操作开启GPIO的时钟:
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);//使能或者失能APB2外设时钟
一. GPIO基础知识、寄存器及库函数

二. (重要)GPIO位带操作
如果我们想读取某个推挽输出GPIO引脚现在的状态,怎么做呢?
我们可以读取它的ODR寄存器状态, 比如GPIOB的0脚可以这样读:
GPIOB->ODR ^= GPIO_Pin_0;
由于我们不喜欢寄存器操作的方式, 现在我们想把它封装起来, 怎么封装呢?
这里我们用到一个位带别名区(可以理解为一大片寄存器), 位带区把每个GPIO寄存器的每个位(每个引脚)的状态映射到了自己的寄存器中. 我们根据GPIO的寄存器地址+位(引脚)偏移,可以算出去这个寄存器的哪个映射地址中可以读到这个位(引脚)的状态, 我们称之为地址转换计算。计算公式推导过程忽略, 直接记住结果:
地址转换计算公式
((addr & 0xF0000000)+0x02000000+((addr & 0x00FFFFFF)<<5)+(bitnum<<2))
在程序中, 我们定义这样一个宏:
#define BITBAND(addr, bitnum) *(unsigned int*)((addr & 0xF0000000)+0x02000000+((addr & 0x00FFFFFF)<<5)+(bitnum<<2))
当我们想获取GPIOB的ODR寄存器的某个位的状态时:
#define GPIOB_ODR_Addr (GPIOB_BASE + 0X0C) //找到GPIOB的基地址
#define GPIOB_ODR_bitnum 0 //想读GPIOB的ODR寄存器的第0位
#define BITBAND(addr, bitnum) *(unsigned int*)((addr & 0xF0000000)+0x02000000+((addr & 0x00FFFFFF)<<5)+(bitnum<<2)) //位带操作地址转换公式
当函数调用时:
BITBAND(GPIOB_ODR_Addr,GPIOB_ODR_bitnum)=0; //写位
a= BITBAND(GPIOB_ODR_Addr,GPIOB_ODR_bitnum); //读位
事实上,并非只有GPIO. 位带区分为外设位带区和SRAM位带区(两者的地址计算方法还不一样). 它们为STM32提供了按位读取的基础.就像51单片机里的sbit
这里举一个位带操作的完整例子:
main.c
#include "stm32f10x.h"
#include "led.h"
#include "key.h"
#ifndef BITBAND
#define BITBAND(addr, bitnum) *(unsigned int*)((addr & 0xF0000000)+0x02000000+((addr & 0x00FFFFFF)<<5)+(bitnum<<2)) //位带操作地址转换公式
#endif /*BITBAND*/
int a;
int main(void)
{
LED_GPIO_Config();
KEY1_GPIO_Config();
KEY2_GPIO_Config();
LED_G(OFF);//其实这里也可以改成LED_G_Out_Sbit=1
LED_R(OFF);
LED_B(OFF);
while(1)
{
if(KEY1_In_Sbit)
{
while(KEY1_In_Sbit);
LED_G_Out_Sbit=!LED_G_Out_Sbit;
}
if(KEY2_In_Sbit)
{
while(KEY2_In_Sbit);
LED_R_Out_Sbit=!LED_R_Out_Sbit;
}
}
}
led.h
#ifndef __LED_H
#define __LED_H
#include "stm32f10x.h"
#define LED_G_GPIO GPIOB
#define LED_G_Pin GPIO_Pin_0
#define LED_G_GPIO_ODR_Addr (GPIOB_BASE + 0X0C) //绿灯位带操作:找到GPIOB的基地址
#define LED_G_ODR_bitnum 0 //绿灯位带操作:想读GPIOB的ODR寄存器的第0位
#define LED_G_Out_Sbit BITBAND(LED_G_GPIO_ODR_Addr,LED_G_ODR_bitnum) //将绿灯的位定义出来了
#define LED_R_GPIO GPIOB
#define LED_R_Pin GPIO_Pin_5
#define LED_R_GPIO_ODR_Addr (GPIOB_BASE + 0X0C) //红灯位带操作:找到GPIOB的基地址
#define LED_R_ODR_bitnum 5 //红灯位带操作:想读GPIOB的ODR寄存器的第5位
#define LED_R_Out_Sbit BITBAND(LED_R_GPIO_ODR_Addr,LED_R_ODR_bitnum) //将宏灯的位定义出来了
#define LED_B_GPIO GPIOB
#define LED_B_Pin GPIO_Pin_1
#define LED_B_GPIO_ODR_Addr (GPIOB_BASE + 0X0C) //蓝灯位带操作:找到GPIOB的基地址
#define LED_B_ODR_bitnum 1 //蓝灯位带操作:想读GPIOB的ODR寄存器的第1位
#define LED_B_Out_Sbit BITBAND(LED_B_GPIO_ODR_Addr,LED_B_ODR_bitnum) //将蓝灯的位定义出来了
#define ON 1
#define OFF 0
#define LED_G(a) if(a) GPIO_ResetBits(LED_G_GPIO,LED_G_Pin); else GPIO_SetBits(LED_G_GPIO,LED_G_Pin);// 这里用了一个条件定义
#define LED_R(a) if(a) GPIO_ResetBits(LED_R_GPIO,LED_R_Pin); else GPIO_SetBits(LED_R_GPIO,LED_R_Pin);// 这里用了一个条件定义
#define LED_B(a) if(a) GPIO_ResetBits(LED_B_GPIO,LED_B_Pin); else GPIO_SetBits(LED_B_GPIO,LED_B_Pin);// 这里用了一个条件定义
void LED_GPIO_Config(void);
#endif /*__LED_H*/
key.h
#ifndef __KEY_H
#define __KEY_H
#include "stm32f10x.h"
#define KEY1_GPIO GPIOA
#define KEY1_GPIO_Pin GPIO_Pin_0
#define KEY1_GPIO_IDR_Addr (GPIOA_BASE + 0X08) //key1位带操作:找到GPIOA的IDR基地址
#define KEY1_ODR_bitnum 0 //key1位带操作:想读GPIOA的IDR寄存器的第0位
#define KEY1_In_Sbit BITBAND(KEY1_GPIO_IDR_Addr,KEY1_ODR_bitnum) //将蓝灯的位定义出来了
#define KEY2_GPIO GPIOC
#define KEY2_GPIO_Pin GPIO_Pin_13
#define KEY2_GPIO_IDR_Addr (GPIOC_BASE + 0X08) //key2位带操作:找到GPIOC的IDR基地址
#define KEY2_ODR_bitnum 13 //key2位带操作:想读GPIOC的IDR寄存器的第13位
#define KEY2_In_Sbit BITBAND(KEY2_GPIO_IDR_Addr,KEY2_ODR_bitnum) //按位操作KEY2
#define KEY_ON 1
#define KEY_OFF 0
void KEY1_GPIO_Config(void);
void KEY2_GPIO_Config(void);
#endif
led.c和 key.c 都只有GPIO初始化函数, 这里省略了
三. GPIO库函数
1. GPIO初始化函数:GPIO_Init
GPIO初始化用以下函数

参数1:可以看到,初始化时,需要一个GPIO_TypeDef类型的指针GPIOx
参数2:需要一个GPIO_InitTypeDef类型的指针GPIO_InitStruct
这个GPIO_InitTypeDef如何定义呢?

定义这个结构体,需要对以下参数进行初始化
- 参数2的参数1: GPIO_Pin: 可选值

- 参数2的参数2: GPIO_Speed

- 参数2的参数3:

例子:
GPIO_InitTypeDef GPIO_InitStructure; //创建GPIO_InitTypeDef类型的结构体GPIO_InitStructure
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;//最高输出速率50MHz
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;//推挽输出
GPIO_Init(GPIOB, &GPIO_InitStructure);//初始化外设GPIOx寄存器
2. GPIO位输出1函数:GPIO_SetBits

3. GPIO位输出0函数:GPIO_ResetBits

4. GPIO读取位输入 GPIO_ReadInputDataBit

5. GPIO读取位输出 GPIO_ReadOutputDataBit

6. GPIO整体写端口:

7. GPIO整体读端口:

网友评论