一、定义外设的各基地址,参考存储器映射
// 由存储器的映射可知,片上外设基地址0x4000 0000
#define PERIPH_BASE ((unsigned int)0x4000000)
// APB2 总线的基地址
#define APB2PERIPH_BASE (PERIPH_BASE + 0x10000)
// AHB 总线基地址
#define AHBPERIPH_BASE (PERIPH_BASE + 0x20000)
// GPIO 外设基地址,由系统框图可知,GPIO挂靠在APB2总线上
#define GPIOA_BASE (APB2PERIPH_BASE + 0x0800)
#define GPIOB_BASE (APB2PERIPH_BASE + 0x0C00)
#define GPIOC_BASE (APB2PERIPH_BASE + 0x1000)
#define GPIOD_BASE (APB2PERIPH_BASE + 0x1400)
#define GPIOE_BASE (APB2PERIPH_BASE + 0x1800)
#define GPIOF_BASE (APB2PERIPH_BASE + 0x1C00)
#define GPIOG_BASE (APB2PERIPH_BASE + 0x2000)
// RCC 外设基地址 在AHB总线上
#define RCC_BASE (AHBPERIPH_BASE + 0x1000)
F103系统框图
F103存储器映射
二、定义GPIO与RCC寄存器结构体
#define __IO volatile //volatile 表示易变的变量,防止编译器的优化
typedef unsigned int uint32_t //一个int型可看做四个字节,32位
typedef unsigned short uint16_t
// 定义GPIO寄存器结构体
typedef struct{
__IO uint32_t CRL; // 端口配置低寄存器, 地址偏移 0X00
__IO uint32_t CRH; // 端口配置高寄存器, 地址偏移 0X04
__IO uint32_t IDR; // 端口数据输入寄存器, 地址偏移 0X08
__IO uint32_t ODR; // 端口数据输出寄存器, 地址偏移 0X0C
__IO uint32_t BSRR; // 端口位设置/清除寄存器,地址偏移 0X10
__IO uint32_t BRR; // 端口位清除寄存器, 地址偏移 0X14
__IO uint32_t LCKR; // 端口配置锁定寄存器, 地址偏移 0X18
}GPIO_typeDef;
// 定义时钟CRR结构体
typedef struct{
__IO uint32_t CR; // 时钟控制寄存器, 地址偏移 0X00
__IO uint32_t CFGR; // 时钟配置寄存器, 地址偏移 0X04
__IO uint32_t CIR; // 时钟中断寄存器, 地址偏移 0X08
__IO uint32_t APB2RSTR; // APB2外设复位寄存器,地址偏移 0X0C
__IO uint32_t APB1RSTR; // APB1外设复位寄存器,地址偏移 0X10
__IO uint32_t AHBENR; // AHB外设时钟使能寄存器,地址偏移 0X14
__IO uint32_t APR2ENR; // APB2外设时钟使能寄存器,地址偏移 0X18
__IO uint32_t ApR1ENR: //APB1外设时钟使能寄存器, 地址偏移 0X1C
__IO uint32_t BDCR; //备份域控制寄存器, 地址偏移 0X20
__IO uint32_t CSR; //控制/状态寄存器, 地址偏移 0X24
}RCC_TypeDef;
在stm32中,程序的存储器,数据存储器,寄存器和输入输出端口被组织在一个4GB(4294967296)的线性空间中,即从地址0x0000 0000 到 地址0xFFFF FFFF。且被分为8个block。一个block的大小为512MB,注意,这里的存储单位是一个字节。而GPIO中一个寄存器的存储空间是4个字节,上面的七个寄存器的存储空间是连续的。一个uint32_t刚好也为4个字节。因此只需要给结构体传入GPIO的基地址即可。
// GPIOA_BASE只是一串数字,将它转换成GPIO_TypeDef类型指针
#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 RCC ((RCC_TypeDef *) RCC_BASE)
三、利用寄存器使PB0输出低电平
int main(void)
{
// 开启GPIOB的时钟
RCC -> APB2ENR |= (1<<3);
// 清空控制PB0的端口位
GPIO -> CRL &= ~( 0x0F << (4*0));
// 配置 PB0 为通用的推挽输出,速度为10M
GPIOB -> CRL |= (1<<4*0);
// PB0输出低电平
GPIOB -> ODR |= (0<<0);
while(1);
}
// 对某一位赋值先清零,再|=,某一位清零用&=再取反,这样可避免对其他位置造成影响
四、构造库函数
定义GPIO初始化结构体
typedef struct{
uint16_t GPIO_Pin; // 选择要配置的 GPIO 引脚
uint16_t GPIO_Speed; // 选择 GPIO 引脚的速率
uint16_t GPIO_Mode; // 选择 GPIO 引脚的工作模式
} GPIO_InitTypeDef;
枚举类型定义
typedef enum{
GPIO_Speed_10MHz = 1, // 10MHZ (01)b
GPIO_Speed_2MHz, // 2MHZ (10)b
GPIO_Speed_50MHz // 50MHZ (11)b
} GPIOSpeed_TypeDef;
// 枚举会自动递增,即1,2,3(01,10,11)
typedef enum{
GPIO_Mode_AIN = 0x0,// 模拟输入 (0000 0000)b
GPIO_Mode_IN_FLOATING = 0x04, // 浮空输入 (0000 0100)b
GPIO_Mode_IPD = 0x28, // 下拉输入 (0010 1000)b
GPIO_Mode_IPU = 0x48, // 上拉输入 (0100 1000)b
GPIO_Mode_Out_OD = 0x14, // 开漏输出 (0001 0100)b
GPIO_Mode_Out_PP = 0x10, // 推挽输出 (0001 0000)b
GPIO_Mode_AF_OD = 0x1C, // 复用开漏输出 (0001 1100)b
GPIO_Mode_AF_PP = 0x18 // 复用推挽输出 (0001 1000)b
} GPIOMode_TypeDef;
// 定义枚举类型是为了来限定输入参数
定义引脚号
/*GPIO 引脚号定义 */
#define GPIO_Pin_0 (uint16_t)0x0001) // 0000 0000 0000 0001
#define GPIO_Pin_1 ((uint16_t)0x0002) // 0000 0000 0000 0010
#define GPIO_Pin_2 ((uint16_t)0x0004) // 0000 0000 0000 0100
#define GPIO_Pin_3 ((uint16_t)0x0008) // 0000 0000 0000 1000
#define GPIO_Pin_4 ((uint16_t)0x0010) // 0000 0000 0001 0000
#define GPIO_Pin_5 ((uint16_t)0x0020) // 0000 0000 0010 0000
#define GPIO_Pin_6 ((uint16_t)0x0040) // 0000 0000 0100 0000
#define GPIO_Pin_7 ((uint16_t)0x0080) // 0000 0000 1000 0000
#define GPIO_Pin_8 ((uint16_t)0x0100) // 0000 0001 0000 0000
#define GPIO_Pin_9 ((uint16_t)0x0200) // 0000 0010 0000 0000
#define GPIO_Pin_10 ((uint16_t)0x0400) // 0000 0100 0000 0000
#define GPIO_Pin_11 ((uint16_t)0x0800) // 0000 1000 0000 0000
#define GPIO_Pin_12 ((uint16_t)0x1000) // 0001 0000 0000 0000
#define GPIO_Pin_13 ((uint16_t)0x2000) // 0010 0000 0000 0000
#define GPIO_Pin_14 ((uint16_t)0x4000) // 0100 0000 0000 0000
#define GPIO_Pin_15 ((uint16_t)0x8000) // 1000 0000 0000 0000
#define GPIO_Pin_All ((uint16_t)0xFFFF) // 1111 1111 1111 1111
GPIO初始化函数
/**
* @brief Initializes the GPIOx peripheral according to the specified
* parameters in the GPIO_InitStruct.
* @根据GPIO_InitStruct中的特定参数初始化GPIO外设
* @param GPIOx: where x can be (A..G) to select the GPIO peripheral.
* @参数中的x可以用来选择GPIO外设
* @param GPIO_InitStruct: pointer to a GPIO_InitTypeDef structure that
* contains the configuration information for the specified GPIO peripheral.
*指向GPIO_InitTypeDef结构体的指针,包含了指定的GPIO外设的配置信息
* @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;
/*---------------------------- GPIO Mode Configuration -----------------------*/
//输入参数GPIO_Mode的低四位暂存在currentmode中
currentmode = ((uint32_t)GPIO_InitStruct->GPIO_Mode) & ((uint32_t)0x0F);
//bit4 是1表示输出,bit4是0表示输入
//判断bit4是1还是0,即首先判断的输入还是输出模式
if ((((uint32_t)GPIO_InitStruct->GPIO_Mode) & ((uint32_t)0x10)) != 0x00)
{
/* 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)
{
// 备份CRL寄存器的值
tmpreg = GPIOx->CRL;
// 循环,找到具体对应的是哪一个管脚pin
for (pinpos = 0x00; pinpos < 0x08; pinpos++)
{
pos = ((uint32_t)0x01) << pinpos;
/* Get the port pins position */
// 得到具体管脚pin位置
currentpin = (GPIO_InitStruct->GPIO_Pin) & pos;
//如果currentpin=pos ,则找到相应的管脚
if (currentpin == pos)
{
// pinpos 的值左移两位 (乘以 4), 因为寄存器中 4 个位配置一个引脚
pos = pinpos << 2;
/* Clear the corresponding low control register bits */
// 把控制这个引脚的 4 个寄存器位清零,其它寄存器位不变
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)
{
// 下拉输入模式, 引脚默认置 0, 对 BRR 寄存器写 1 对引脚置 0
GPIOx->BRR = (((uint32_t)0x01) << pinpos);
}
else
{
// 判断是否为上拉输入模式
/* Set the corresponding ODR bit */
if (GPIO_InitStruct->GPIO_Mode == GPIO_Mode_IPU)
{
// 上拉输入模式, 引脚默认值为 1, 对 BSRR 寄存器写 1 对引脚
置 1
GPIOx->BSRR = (((uint32_t)0x01) << pinpos);
}
}
}
}
// 把前面处理后的暂存值写入到 CRL 寄存器之中
GPIOx->CRL = tmpreg;
}
/*---------------------------- GPIO CRH Configuration ------------------------*/
/* Configure the eight high port pins */
// 配置端口高 8 位,即 Pin8~Pin15
if (GPIO_InitStruct->GPIO_Pin > 0x00FF)
{
// 先备份 CRH 寄存器的值
tmpreg = GPIOx->CRH;
// 循环,从 Pin8 开始配对,找出具体的 Pin
for (pinpos = 0x00; pinpos < 0x08; pinpos++)
{
pos = (((uint32_t)0x01) << (pinpos + 0x08));
/* Get the port pins position */
// pos 与输入参数 GPIO_PIN 作位与运算
currentpin = ((GPIO_InitStruct->GPIO_Pin) & pos);
//若 currentpin=pos, 则找到使用的引脚
if (currentpin == pos)
{
//pinpos 的值左移两位 (乘以 4), 因为寄存器中 4 个位配置一个引脚
pos = pinpos << 2;
/* Clear the corresponding high control register bits */
//把控制这个引脚的 4 个寄存器位清零,其它寄存器位不变
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)
{
// 下拉输入模式, 引脚默认置 0, 对 BRR 寄存器写 1 可对引脚置 0
GPIOx->BRR = (((uint32_t)0x01) << (pinpos + 0x08));
}
/* Set the corresponding ODR bit */
if (GPIO_InitStruct->GPIO_Mode == GPIO_Mode_IPU)
{
// 上拉输入模式, 引脚默认值为 1, 对 BSRR 寄存器写 1 可对引脚置1
GPIOx->BSRR = (((uint32_t)0x01) << (pinpos + 0x08));
}
}
}
// 把前面处理后的暂存值写入到 CRH 寄存器之中
GPIOx->CRH = tmpreg;
}
}
GPIO引脚工作模式真值表
- 当bit4为1时,工作模式为输出,为0时,工作模式为输入。而输入不需要设置速率
- 配置上下拉的工作模式是通过写BSRR或者BRR寄存器来实现的
端口输出函数
void GPIO_SetBits(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin)
{
GPIOx->BSRR = GPIO_Pin;
}
void GPIO_ResetBits(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin)
{
GPIOx->BRR = GPIO_Pin;
}
用库函数实现端口输出
#define RCC_APB2ENR *(unsigned int*)(RCC_BASE+0x18)
int main(void) {
// 定义一个 GPIO_InitTypeDef 类型的结构体
GPIO_InitTypeDef GPIO_InitStructure;
// 开启 GPIO 端口时钟
RCC_APB2ENR |= (1<<3);
// 选择要控制的 GPIO 引脚
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
// 设置引脚模式为通用推挽输出
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
// 设置引脚速率为 50MHz
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
// 调用库函数,初始化 GPIO 引脚
GPIO_Init(GPIOB, &GPIO_InitStructure);
// 使引脚输出低电平, 点亮 LED1
GPIO_ResetBits(GPIOB,GPIO_Pin_0);
while (1)
{
GPIO_ResetBits(GPIOB,GPIO_Pin_0);
// 延时一段时间
Delay(0xFFFF);
// 使引脚输出高电平,关闭 LED1
GPIO_SetBits(GPIOB,GPIO_Pin_0);
// 延时一段时间
Delay(0xFFFF);
}
}
void Delay(int i)
{
i--;
}