GPIO的初始化(以LED为例)
bsp_led.c文件:
GPIO_InitTypeDef GPIO_InitStructure;
/*开启LED相关的GPIO外设时钟*/
RCC_APB2PeriphClockCmd( LED1_GPIO_CLK | LED2_GPIO_CLK | LED3_GPIO_CLK, ENABLE);
/*选择要控制的GPIO引脚*/
GPIO_InitStructure.GPIO_Pin = LED1_GPIO_PIN;
/*设置引脚模式为通用推挽输出*/
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
/*设置引脚速率为50MHz */
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
/*调用库函数,初始化GPIO*/
GPIO_Init(LED1_GPIO_PORT, &GPIO_InitStructure);
/*选择要控制的GPIO引脚*/
GPIO_InitStructure.GPIO_Pin = LED2_GPIO_PIN;
/*调用库函数,初始化GPIO*/
GPIO_Init(LED2_GPIO_PORT, &GPIO_InitStructure);
/*选择要控制的GPIO引脚*/
GPIO_InitStructure.GPIO_Pin = LED3_GPIO_PIN;
/*调用库函数,初始化GPIOF*/
GPIO_Init(LED3_GPIO_PORT, &GPIO_InitStructure);
在bsp_led.h文件中预先宏定义增加可读性,方便修改
#define LED1_GPIO_PORT GPIOB /* GPIO端口 */
#define LED1_GPIO_CLK RCC_APB2Periph_GPIOB /* GPIO端口时钟 */
#define LED1_GPIO_PIN GPIO_Pin_5 /* 连接到SCL时钟线的GPIO */
初始化结构体参数
stm32f103x_gpio.h文件中
typedef struct
{
uint16_t GPIO_Pin;
GPIOSpeed_TypeDef GPIO_Speed;
GPIOMode_TypeDef GPIO_Mode;
}GPIO_InitTypeDef;
初始化结构体中有三个成员。其中GPIO_Speed和GPIO_Mode是枚举变量
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;
输出的四种模式配置时第四位都为1
typedef enum
{
GPIO_Speed_10MHz = 1,
GPIO_Speed_2MHz, //枚举常量2
GPIO_Speed_50MHz //枚举常量3
}GPIOSpeed_TypeDef;
GPIO_Pin有以下设置值:
16位整型,每一个端口的宏定义仅有一位是1,其他位都是0。
GPIOx->GPIO_Pin = GPIO_Pin_x
配置寄存器
位操作方式修改寄存器:
GPIO->CRL &=~(uint32_t)(1111<<4*3);#清空Pin3的四个控制位
GPIO->CRL |=~(uint32_t)(0011<<4*3);#配置Pin3的四个控制位
GPIO->CRL &=~(uint32_t)(1111<<4*4);#清空Pin4的四个控制位
GPIO->CRL |=~(uint32_t)(0011<<4*4);#配置Pin4的四个控制位
配置GPIOx低8位引脚的寄存器GPIOx_CRL。
四位一组共有八组,所以在清空Pin3的控制位时要使用(1111<<4*3) (乘4)
配置MODEy[1:0]:
00:输入模式(复位后的状态)
01:输出模式,最大速度10MHz
10:输出模式,最大速度2MHz
11:输出模式,最大速度50MHz
配置CNFy[1:0]:
if (MODEy[1:0] == 00)
00:模拟输入模式
01:浮空输入模式
10:上拉/下拉输入模式
11:保留
else
00:通用推挽输出模式
01:通用开漏输出模式
10:复用功能推挽输出模式
11:复用功能开漏输出模式
理解ST库的本质:
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);
#将GPIO_Mode的低4位暂存
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; #备份原CRL寄存器中的值
for (pinpos = 0x00; pinpos < 0x08; pinpos++)
{
pos = ((uint32_t)0x01) << pinpos; #1左移0-7位
/* Get the port pins position */
currentpin = (GPIO_InitStruct->GPIO_Pin) & pos;
if (currentpin == pos)
{
#表示pos引脚需要配置
pos = pinpos << 2; #pinpos的值从0-7
/* Clear the corresponding low control register bits */
pinmask = ((uint32_t)0x0F) << pos; #把控制这个引脚的4个寄存器位清零
tmpreg &= ~pinmask;#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; #将前面处理后的暂存值写入到CRL寄存器之中
}
/*---------------------------- 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 << 2;
/* 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;
}
}
GPIO_Init()的意义在于将用户输入的三个参数转换成:
GPIOx->CRL = 0x44333444; #某一个32位整数来配置寄存器
GPIO地址映射
stm32f103x.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;
Cortex-M3的地址映射:Cortex-M3y有32根地址线,所以它的寻址空间大小为4GB。ARM公司设计时,预先把这4GB的寻址空间大致的分配好了。它把从0x40000000至0x5FFFFFFF(512MB)的地址分配给片上外设。通过把片上外设的寄存器映射到这个地址区,就可以简单地以访问内存地方式,访问这些外设的寄存器,从而控制外设的工作。这样,片上外设可以使用C语言来操作。
类比C51单片机汇编语言:
MOV A, #X ;数据X->A
MOV DPTR, #7FFFH ;指向DAC0832(x),7FFFH也就成为了DAC0832(x)的地址
MOVX @DPTR, A ;该段代码使芯片引脚输出(01111111)B,使能外设
stm32f10x.h这个文件中的重要内容就是把STM32的所有寄存器进行地址映射。如同51单片机的<reg52.h>头文件一样,stm32f10x.h像一个大表格,我们在使用的时候就是通过宏定义进行类似查表操作,
#define GPIO_BASE (APB2PERIPH_BASE + 0x1000)
#define APB2PERIPH (PERIPH_BASE + 0x10000)
#define PERIPH_BASE ((uint32_t)0x40000000)
PERIPH_BASE的宏展开为0x40000000就是Cortex-M3分配给片上外设512MB寻址空间中的第一个地址,所以中文意思就是外设基地址。
APB2PERIPH_BASE的宏展开使外设基地址再加上偏移地址0x10000,即为0x40010000。STM32芯片有AHB总线、APB2总线和APB1总线,挂载在这些总线上的外设有特定的地址范围。
像GPIO、串口1、ADC及部分定时器是挂载在APB2的总线上,挂载到APB2总线上的外设地址空间从0x40010000至0x40013FFF地址。这里的第一个地址,也就是0x40010000,称为APB2PERIPH_BASE(APB2总线外设基地址)。