GPIO的工作模式
-
输入模式(模拟/浮空/上拉/下拉)
输入模式中,施密特触发器打开,输出被禁止,可以通过输入数据寄存器GPIOx_IDR读取I/O状态。
- 上拉/下拉:默认的电平由上拉或者下拉决定
- 浮空:输入的电平不确定,完全由外部的输入决定,一般接按键的时候用的是这个模式
- 模拟:用于ADC采集
-
输出模式
输出数据寄存器GPIOx_ODR可控制I/O输出高低电平
-
开漏:只有N-MOS管工作,输出寄存器可控制I/O输出高阻态或低电平,输出速度可配置,有2MHZ,10MHZ,50MHZ的选项,此处输出速度即I/O支持的高低电平状态最高切换频率,支持的频率越高,功耗越大,如果功耗要求不严格,把速度设置成最大即可
输出模式下施密特触发器打开,输入可用,通过输入数据寄存器GPIOx_IDR可读取I/O的实际状态
-
-
复用功能(推挽/开漏)
输出使能,输出速度可配置,可工作在开漏及推挽模式,但是输出信号源于其他外设,输出数据寄存器GPIOx_ODR无效,输入可用,通过输入数据寄存器可获取I/O实际状态,但一般直接用外设的寄存器来获取该数据信号
如果但从这些枚举值的十六进制来看,很难发现规律,转化成二进制之后,就比较容易发现规律。 bit4 用来区分端口是输入还是输出,0 表示输入,1 表示输出,bit2 和 bit3 对应寄存器的 CNFY[1:0] 位,是我们真正要写入到 CRL 和 CRH 这两个端口控制寄存器中的值。bit0 和 bit1 对应寄存器的 MODEY[1:0] 位,这里我们暂不初始化,在 GPIO_Init() 初始化函数中用来跟 GPIOSpeed 的值相加 即可实现速率的配置。有关具体的代码分析见 GPIO_Init() 库函数。其中在下拉输入和上拉输入 中我们设置 bit5 和 bit6 的值为 01 和 10 来以示区别。
开启外设时钟
设置完 GPIO 的引脚,控制电平输出,以为现在总算可以点亮 LED 了吧,其实还差最后一步。由 于 STM32 的外设很多,为了降低功耗,每个外设都对应着一个时钟,在芯片刚上电的时候这些 时钟都是被关闭的,如果想要外设工作,必须把相应的时钟打开
所有的GPIO都挂载到APB2总线上,具体的时钟由APB2外设时钟使能寄存器(RCC_APB2ENR)来控制
控制GPIO输出
对 GPIOx 的 BSRR 或 BRR 寄存器赋值,从而设置引脚为高 电平或低电平,操作 BSRR 或者 BRR 可以实现单独的操作某一位,有关这两个的寄存器说明见 图 BSRR 寄存器说明 和图 BRR 寄存器说明 。其中 GPIOx 是一个指针变量,通过函数的输入参数 我们可以修改它的值,如给它赋予 GPIOA、GPIOB、GPIOH 等结构体指针值,这个函数就可以控 制相应的 GPIOA、GPIOB、GPIOH 等端口的输出。
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模式配置 */
//把输入参数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)
{
//输出模式则要设置输出速度
currentmode |= (uint32_t)GPIO_InitStruct->GPIO_Speed;
}
/* GPIO CRL寄存器配置CRL寄存器控制着低8位IO */
//配置端口低8位,即 Pin0 - Pin7
if(((uint32_t)GPIO_InitStruct->GPIO_Pin & (uint32_t)0x00FF) != 0x00)
{
//先备份CRL寄存器的值
tmpreg = GPIOx->CRL;
//循环,从Pin0开始配对,找出具体的Pin
for(pinpos = 0x00;pinpos < 0x08;pinpos++)
{
//pos的值为1左移pinpos位
pos = ((uint32_t)0x01) << pinpos;
//令pos与输入参数GPIO_PIN作位与运算
currentpin = (GPIO_InitStruct->GPIO_Pin) & pos;
//若 currentpin = pos,则找到使用的引脚
if(currentpin == pos)
{
//pinpos 的值左移两位(乘4),因为寄存器中4个位配置一个引脚
pos = pinpos << 2;
//把控制这个引脚的4个寄存器位清零,其他寄存器位不变
pinmask = ((uint32_t)0x0F) << pos;
tmpreg &= ~pinmask;
//向寄存器写入将要配置的引脚的模式
tmpreg |= (currentmode << pos);
//判断是否为下拉输入模式
if(GPIO_InitStruct->GPIO_Mode == GPIO_Mode_IPD)
{
//下拉输入模式,引脚默认置0,对BRR寄存器写1对引脚置0
GPIOx->BRR = (((uint32_t)0x01) << pinpos);
}
else
{
//判断是否位上拉输入模式
if(GPIO_InitStruct->GPIO_Mode == GPIO_Mode_IPU)
{
//上拉输入模式,引脚默认值为1,对BSRR寄存器写1对引脚置1
GPIOx->BSRR = (((uint32_t)0x01) << pinpos);
}
}
}
}
//把前面处理后的暂存值写入到CRL寄存器之中
GPIOx->CRL = tmpreg;
}
- 先取得 GPIO_Mode 的值,判断 bit4 是 1 还是 0 来判断是输出还是输入。如果是输出则设置 输出速率,即加上 GPIO_Speed 的值,输入没有速率之说,不用设置。
- 配置 CRL 寄存器。通过 GPIO_Pin 的值计算出具体需要初始化哪个引脚,算出后,然后把 需要配置的值写入到 CRL 寄存器中,具体分析见代码注释。这里有一个比较有趣的是上/下 拉输入并不是直接通过配置某一个寄存器来实现的,而是通过写 BSRR 或者 BRR 寄存器来 实现。这让很多只看手册没看固件库底层源码的人摸不着头脑,因为手册的寄存器说明中 没有明确的指出如何配置上拉/下拉,具体见图上拉 _ 下拉寄存器说明 。
- 配置 CRH 寄存器过程同 CRL
而是通过写 BSRR 或者 BRR 寄存器来 实现。这让很多只看手册没看固件库底层源码的人摸不着头脑,因为手册的寄存器说明中 没有明确的指出如何配置上拉/下拉,具体见图上拉 _ 下拉寄存器说明 。
3. 配置 CRH 寄存器过程同 CRL