作者:自信且爱笑‘
GPIO的七大寄存器+GPIOx_LCKR作用和配置+编程小总结
- 一、GPIO的寄存器
- 1、端口配置低寄存器(GPIOx_CRL) (x=A..E)
- 1、详述
- 2、举例
- 2、端口配置高寄存器(GPIOx_CRH) (x=A..E)
- 3、端口输入数据寄存器(GPIOx_IDR)(x=A...E)
- 1、详述
- 2、举例
- 4、端口输出数据寄存器(GPIOx_ODR)(x=A...E)
- 1、详述
- 2、举例
- 5、端口位设置/清除寄存器(GPIOx_BSRR)(x=A...E)
- 1、详述
- 2、举例1
- 3、举例2
- 6、端口位清除寄存器(GPIOx_BRR)(x=A...E)
- 1、详述
- 2、举例
- 7、端口配置锁定寄存器(GPIOx_LCKR) (x=A..E)
- 1、详述
- 2、举例
- 二、总结
- 1、几种IO口输出类型(以PB5和PB10为例)
- 1、使用GPIOB_ODR寄存器
- 2、使用GPIOB_BSRR寄存器
- 3、使用GPIOB_BRR寄存器
- 4、小总结(比较重要)
一、GPIO的寄存器
每个GPIO端口有两个32位配置寄存器(GPIOx_CRL, GPIOx_CRH),两个32位数据寄存器(GPIOx_IDR和GPIOx_ODR),一个32位置位/复位寄存器(GPIOx_BSRR),一个16位复位寄存器(GPIOx_BRR)和一个32位锁定寄存器(GPIOx_LCKR)
1、端口配置低寄存器(GPIOx_CRL) (x=A…E)
1、详述
该寄存器是用来配置低位寄存器(PX0~PX7),为32位寄存器。对于GPIOX,从PX0 ~PX7共8 个IO口,32位寄存器的每四位配置一个IO口。用来配置GPIO的输入输出模式和输出时的speed。
对于每个IO口配置的四位,由两位的MODE和两位的CNF,其中MODE配置Speed,CNF配置是哪种输出模式
所以配置GPIO的步骤:
①判断是低8位IO还是高8位IO
②判断该IO对应的CNF和MODE值为多少
③编写配置函数:
GPIOA(B、C、D、E)——>CRL(或CRH)=0x....
- 1
2、举例
以按键为例
由原理图可知,KEY_UP按下后,PA0变为高电平,没有按下时,PA0处于悬空状态;KEY0、KEY1、KEY2按下后PE4、PE3、PE2分别变为低电平,未按下时,对应IO口处于悬空状态。所以按键的GPIO配置的模式可以是浮空输入。不过KEY_UP按下后为了更好的检测到高电平,可以采用上拉输入;KEY0、KEY1、KEY2按下后为了更好的检测到低电平,可以采用下拉输入。
KEY_UP的GPIO配置:
①PA0为低位,采用GPIOA_CRL
②上拉输入,所以CNF为10表示上拉/下拉输入,MODE为00,表示输入模式
代码:
GPIOA->CRL&=0xfffffff0;
GPIOA->CRL|=0x00000008;
第一行代码是为了让第0、1、2、3这四位置0,其它位不变;第二行代码是为了让第0、1、2、3这4位变为1000,其它位不变。
KEY0的GPIO配置:
①PE4为低4位,所以使用GPIOE_CRL寄存器
②采用下拉输入,所以CNF为10,输入模式,MODE为00
代码:
GPIOE->CRL&=0xfff0ffff;
GPIOE->CRL|=0x00080000;
第一行代码是为了将第16、17、18、19位置0,其它位保持不变;第二行代码是为了将第16、17、18、19位置1000,其它位保持不变。
2、端口配置高寄存器(GPIOx_CRH) (x=A…E)
与端口配置低寄存器(GPIOx_CRL)(x=A…E)类似,唯一不同的是对应PX8~PX15 IO口。
3、端口输入数据寄存器(GPIOx_IDR)(x=A…E)
1、详述
该寄存器为32位寄存器, 其中高16位保持不变,低16位依次对应PX0~PX15,该寄存器只能以16位的形式读出
那怎么获取某一位的值呢?可以用与运算,如想要知道第6位是不是输入了高电平,即检测第6位是否为1,只需与1111111110111111与运算,即与0xffbf进行与运算
代码:
uint16_t x;//定义一个16位的数
x=GPIOE->IDR&0xffbf;
if(x==0xffff)//高电平
....
if(x==0xffbf)//低电平
....
或者:
if((GPIOE->IDR&0xffbf)==GPIOE->IDR)//低电平
....
if((GPIOE->IDR&0xffbf)!=GPIOE->IDR)//高电平
....
总之,端口输入数据寄存器(GPIOx_IDR) 就是来判读各位的IO口是什么状态。
2、举例
例:按下开关KEY0后LED1亮,取消按下后LED1灭
为了方便代码粘贴,全部程序在主函数中编写
代码:
#include "sys.h"
#include"stm32f10x.h"
int main(void)
{
uint16_t x;//定义一个16位的数
//KEY0 PE4 CFN+MODE 1000
//LED0 PE5 CFN+MODE 0011
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOE , ENABLE);//时钟使能
// RCC->APB2ENR|=0x0040;
GPIOE->CRL&=0xff00ffff;
GPIOE->CRL|=0x00380000;//PE5与PE4一起配置
while(1)
{
x=GPIOE->IDR&0x0010;
if(x==0)//KEY0按下
{
GPIOE->ODR&=0xffdf;//PE5置0,点亮LED0
}
GPIOE->ODR|=0xFFFF;//恢复PE都为高电平
}
}
解释一下例子:
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOE , ENABLE);//时钟使能
上述代码是配置时钟,GPIO时钟配置都用RCC_APB2PeriphClockCmd()
GPIOE->CRL&=0xff00ffff;
GPIOE->CRL|=0x00380000;//PE5与PE4一起配置
上面代码是配置PE5(LED0)和PE4(KEY0)
PE4是KEY0,按下后PE4引脚变为低电平,未按下时PE4引脚为悬空,采用下拉输入,所以CFN+MODE为1000;PE5为LED0,低电平时点亮,高电平时熄灭,所以采用推挽输出,速度为50M,CFN+MODE为0011,两者都为低位IO,采用GPIOE_CRL寄存器配置。
第一行代码先将16~23位置0,其它位不变,第二行代码将16 ~23位置00111000,其它位不变,完成PE4和PE5的配置。
x=GPIOE->IDR&0x0010;
上述代码是为了检测KEY0是否按下,如果按下,则GPIOE_IDR的第4位变为0,此时与0x0010与运算后,值为0
检测到按下后,执行:
GPIOE->ODR&=0xffdf;//PE5置0,点亮LED0
上述代码的寄存器是接下来要总结的寄存器,就是将IO的某位软件置0或1输出,而GPIOx_IDR是外界原因置0或1来输入到芯片。
现在要点亮LED0,则GPIOE的第5位变为0,所以和0xffdf进行与运算就行了
需要注意的是,进行与运算和或运算时,改变的只能是相关的IO引脚,其它无关的IO引脚的电平值一定要保持不变
GPIOE->ODR|=0xFFFF;//恢复PE都为高电平
上述代码是为了将GPIOE全部引脚恢复原状,因为死循环中执行一次后,PE4和PE5都变为低电平,若不恢复原状,LED的引脚PE5一直处于低电平状态,灯会常亮,不再受KEY控制。
其实恢复PE5为高电平就行了,这行代码可改为:
GPIOE->ODR&=0xfef;
或者利用移位运算:
GPIOE->ODR=1<<5;
//GPIOE->ODR|=1<<5;//都可以
4、端口输出数据寄存器(GPIOx_ODR)(x=A…E)
1、详述
该寄存器与GPIOx_IDR类似,高16位也是保留位,就当做啥也没有,进行与运算和或运算时,只需和16位的数进行运算就行,从某种意义上讲,该寄存器与前面的GPIOx_IOR就是16位寄存器。
只要设置了某IO口的为输出模式(GPIOx_CRL、GPIOx_CRL)就可以利用该寄存器对该位进行置0或1。
前面第三部分的例子中也用到了该寄存器,现再举例说明
2、举例
例:控制蜂鸣器发声
打开原理图,找到蜂鸣器和芯片的连接图
由原理图可得:
①芯片PB8接蜂鸣器,所以配置GPIO时用到端口配置高位寄存器(GPIOB_CRH)
②当引脚输出高电平时,三极管基极电流变大,集电极电流也变大,蜂鸣器发声。
代码:(为了代码说明方便,将代码都写入到主函数)
#include "sys.h"
#include "stm32f10x.h"
#include "delay.h"
int main(void)
{
uint16_t x;//定义一个16位的数
//BEEP PB8 CFN+MODE 0011
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB , ENABLE);//时钟使能
// RCC->APB2ENR|=0x0008;
GPIOB->CRH&=0xfffffff0;
GPIOB->CRH|=0x00000003;//配置PB8
while(1)
{
GPIOB->ODR|=0x0100;
}
}
解释一下:
GPIOB->CRH&=0xfffffff0;
GPIOB->CRH|=0x00000003;//配置PB8
上述代码是配置GPIOB的第8引脚,用的端口配置高位寄存器,为通用推挽输出,速度为50M,所以CNF+MODE为0011
先将0~3位通过与运算置0,再通过或运算置0011。
GPIOB->ODR|=0x0100;
上述代码是使PB8输出高电平
当然可以将蜂鸣器关闭,只需:(还可以用GPIOB_BSRR寄存器、GPIOB_BRR寄存器)
GPIOB->ODR&=0xfeff;
或者:
GPIOB->ODR&=0xfffe<<8;
移位运算在ODR中不推荐使用,虽然操作简单,但是移位运算是将16或32位的数整体左移,高位会溢出,低位会补零,若GPIOx只有一个IO被用到,可以采用移位方法,但是多个IO被使用,整体左移后会对其它IO状态产生影响。
正因为移位运算在IO口复杂情况下会对IO口造成紊乱,所以引入BSRR和BRR寄存器,可以在这两个寄存器中去移位来操作ODR寄存器,从而操作对应IO。这俩个寄存器中移位时,补0和溢出0都不会对ODR相应位产生影响,从而避免紊乱!
5、端口位设置/清除寄存器(GPIOx_BSRR)(x=A…E)
1、详述
该寄存器是对GPIOx_ODR寄存器的操作,我们之前举例时,都是用GPIOx_ODR去和一个16位数进行与运算和或运算,在进行运算时,需要求这个16位数,比较麻烦。不过可以移位法,将第一位置1,然后左移一定的位数(<<)。GPIOx_BSRR可以直接对GPIOx_ODR寄存器的某位进行设置。唯一不同的用GPIOx_BSRR操作GPIOx_ODR寄存器时,不用考虑GPIOx_ODR寄存器的不相关位。
GPIOx_BSRR也是32位寄存器,其中低16位是对GPIOx_ODR寄存器16个IO位置1,高16位是对GPIOx_ODR寄存器16个IO位置0
注意的是,如果GPIOx_BSRR的高16位和低16位都对某一IO口进行了配置,则以GPIOx_BSRR寄存器的低16位的配置为优先级。(后面例子中会说明)
2、举例1
1、以(3、GPIOxIDR寄存器的例子说明):按下开关KEY0后LED1亮,取消按下后LED1灭
因为GPIOx_BSRR寄存器高16位进行了清零操作,低16位进行了置1操作,所以不应该把它和一个32位的数进行与运算和或运算,如:
#include "sys.h"
#include"stm32f10x.h"
int main(void)
{
uint16_t x;//定义一个16位的数
//KEY0 PE4 CFN+MODE 1000
//LED0 PE5 CFN+MODE 0011
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOE , ENABLE);//时钟使能
// RCC->APB2ENR|=0x0040;
GPIOE->CRL&=0xff00ffff;
GPIOE->CRL|=0x00380000;//PE5与PE4一起配置
while(1)
{
x=GPIOE->IDR&0x0010;
if(x==0)//KEY0按下
{
//GPIOE->ODR&=0xffdf;//PE5置0,点亮LED0
GPIOE->BSRR|=0x00200000;
}
// GPIOE->ODR|=0xFFFF;//恢复PE都为高电平
GPIOE->BSRR|=0x00000030;//设置PE5(00000020)与设置PE4(00000010)合并(7、6、5、4位:0011)
}
}
上面程序的代码:
GPIOE->BSRR|=0x00200000;
GPIOE->BSRR|=0x00000030;
是对GPIOE_BSRR和32位数进行位或运算,所以在设置低位BS4、BS5时,高位BR4、BR5也同时进行了设置,但是以低位设置为优先级
好好理解下图标注的地方!!!英文原话:Note: If both BSx and BRx are set, BSx has priority
还可以用位移方法:
完整准确代码:
#include "sys.h"
#include "stm32f10x.h"
int main(void)
{
uint16_t x;//定义一个16位的数
//KEY0 PE4 CFN+MODE 1000
//LED0 PE5 CFN+MODE 0011
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOE , ENABLE);//时钟使能
// RCC->APB2ENR|=0x0040;
GPIOE->CRL&=0xff00ffff;
GPIOE->CRL|=0x00380000;//PE5与PE4一起配置
while(1)
{
x=GPIOE->IDR&0x0010;
if(x==0)//KEY0按下
{
//GPIOE->ODR&=0xffdf;//PE5置0,点亮LED0
GPIOE->BSRR=1<<21;//亮灯
}
//GPIOE->ODR|=0xFFFF;//恢复PE都为高电平
GPIOE->BSRR|=1<<5;//灭灯
GPIOE->BSRR|=1<<4;//按键恢复悬空(没办法,只能设置为高电平)
/*也可以如下:(去掉或)*/
//GPIOE->BSRR=1<<5;
// GPIOE->BSRR=1<<4;
}
}
还可以搭配GPIOx_BRR寄存器实现,第6部分总结。
3、举例2
再把上面的控制蜂鸣器发声的程序用GPIOB_BSRR寄存器写一下:
代码:
#include "sys.h"
#include "stm32f10x.h"
#include "delay.h"
int main(void)
{
//BEEP PB8 CFN+MODE 0011
//RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB , ENABLE);//时钟使能
RCC->APB2ENR|=0x0008;
GPIOB->CRH&=0xfffffff0;
GPIOB->CRH|=0x00000003;//配置PB8
while(1)
{
GPIOB->BSRR=1<<8;
//或者:GPIOB->BSRR|=1<<8;
//或者:GPIOB->BSRR|=0x00000100;
}
}
6、端口位清除寄存器(GPIOx_BRR)(x=A…E)
1、详述
GPIOx_BRR寄存器也是32位寄存器,但是高16位被保留,所以可以把它当做是16位寄存器。它的作用是将对应的0~15 IO口清零。即当对应位为1时,对应IO口置0,当对应位为0时,对应IO口保持原来的状态。
编程时,只需:GPIO(A~E)=1<<m,即可将PXm置0。
2、举例
点亮LED
#include "sys.h"
#include "stm32f10x.h"
#include "delay.h"
int main(void)
{
//LED0 PB5 推挽50M 0011
//LED1 PE5 推挽50M 0011
//RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB |RCC_APB2Periph_GPIOE, ENABLE);//时钟使能GPIOA和GPIOE
RCC->APB2ENR|=0x0048;
//RCC->APB2ENR=1<<3 ;//使能GPIOB
//RCC->APB2ENR=1<<6 ;//使能GPIOE
GPIOB->CRL&=0xff0fffff;
GPIOB->CRL|=0x00300000;//配置PB5
GPIOE->CRL&=0xff0fffff;
GPIOE->CRL|=0x00300000;//配置PE5
while(1)
{
GPIOB->BRR=1<<5;//其实本来初始状态就是亮的,没必要再次点亮,,只是为了说明这个寄存器
GPIOE->BRR=1<<5;
}
}
上述程序中,死循环中的两行代码就是通过GPIOB_BRR和GPIOE_BRR分别操作GPIOB_ODR和GPIOE_ODR来分别控制PB5和PE5输出低电平。
7、端口配置锁定寄存器(GPIOx_LCKR) (x=A…E)
1、详述
端口配置锁定寄存器是为了锁住GPIO的配置,在下次系统复位前不让其工作(只要下次复位不执行该寄存器,就不会被锁了)。
注意:锁住的是端口配置寄存器CRL或CRH
之前总结过端口配置寄存器GPIOx_CRL和GPIOx_CRH,对于每个IO,在寄存器中对应4位,即控制输入输出模式的2位CFN,控制speed的2位MODE。当端口寄存器锁住某IO口后,对应的CRL或CRH中对应的4位就被锁住,此时不能配置该位的输入、输出模式,以及不能配置speed,此时该IO口就不能使用。
具体叙述一下:
首先第16位,即高16位的第1位为LCKK,要开启锁IO模式,必须先“开锁”,开锁密码:写1——>写0——>写1——>读0——>读1。最后的读1可省略,但其它“密码”顺序、内容都不能错。
开锁程序:(GPIOB为例)
花了好长时间才调试成功(狗头)
uint32_t t;
GPIOB->LCKR|=0x00010000;//LCKK写入1
GPIOB->LCKR&=0x0000ffff;//LCKK写入0
GPIOB->LCKR|=0x00010000;//LCKK写入1
t=GPIOB->LCKR;//LCKK读0
t=GPIOB->LCKR;//LCKK读出1
然后就是给某IO口上锁了,需要注意的是,只有第16位——>LCKK为0时,GPIOx_LCKR寄存器才可以被写入,某位写入1,则对应的IO口被锁住。
以PB5为例:
//开启锁定寄存器模式
GPIOB->LCKR&=0x0000ffff;//LCKK写入0
GPIOB->LCKR=1<<5;//锁定PB5
2、举例
例:同时配置LED0和LED1,但是LED0被GPIOB_LCKR寄存器锁住,观察两个LED能否都被点亮。
直接代码:
#include "sys.h"
#include "stm32f10x.h"
#include "delay.h"
int main(void)
{
uint32_t t;
delay_init();
//LED0 PB5
//LED1 PE5
//RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB |RCC_APB2Periph_GPIOE, ENABLE);//时钟使能GPIOA和GPIOE
RCC->APB2ENR|=0x0048;
//RCC->APB2ENR=1<<3 ;//使能GPIOB
//RCC->APB2ENR=1<<6 ;//使能GPIOE
/* 开锁*/
GPIOB->LCKR|=0x00010000;//LCKK写入1
GPIOB->LCKR&=0x0000ffff;//LCKK写入0
GPIOB->LCKR|=0x00010000;//LCKK写入1
t=GPIOB->LCKR;//LCKK读0
t=GPIOB->LCKR;//GPIOB_LCKR读出1
//开启锁定寄存器模式
GPIOB->LCKR&=0x0000ffff;//LCKK写入0
GPIOB->LCKR=1<<5;//锁定PB5
GPIOB->CRL&=0xff0fffff;
GPIOB->CRL|=0x00300000;//配置PB5
GPIOE->CRL&=0xff0fffff;
GPIOE->CRL|=0x00300000;//配置PE5
GPIOB->BSRR=1<<5;//熄灭LED0
delay_ms(10);
while(1)
{
GPIOB->BRR=1<<5;//点亮LED0
GPIOE->BRR=1<<5;
}
}
调试:
下载程序后,LED0一直熄灭,LED2一直亮。
分析:
程序开始时对GPIOB和GPIOE进行了时钟配置,接下来就是“开锁”,然后就是对PB5“上锁”,这些上面都总结了。
接下来配置PE5和PB5,然后把LED0熄灭(程序开始都清零,LED默认处于点亮状态),若LED0(PB5)的配置没有被锁住,则在死循环中LED0应该被点亮,调试时LED0应该常亮。
“开锁”时一定要注意:第0~15位的值不能被改变,所以写入0和1的时候要进行与运算和或运算。
二、总结
1、几种IO口输出类型(以PB5和PB10为例)
1、使用GPIOB_ODR寄存器
置1:
GPIOB->ODR|=0x0420;//PB10和PB5都输出高电平
GPIOB->ODR=1<<5;//只操作PB5置1
//GPIOB->ODR|=1<<5;
//注意同时设置PB5和PB10时不用移位
GPIOB->ODR=1<<10;//只操作PB10置1
//GPIOB->ODR|=1<<510;
//注意同时设置PB5和PB10时不用移位
置0:
GPIOB->ODR&=0xfbdf;//同时将PB5和PB10置0
GPIOB->ODR=0<<5;//只对PB5置0
// GPIOB->ODR&=0xfffe<<5;
GPIOB->ODR&=0xff2f;//只对PB5置0
GPIOB->ODR&=0xf4ff;//只对PB10置0
GPIOB->ODR=0<<10;//只对PB10置0
GPIOB->ODR&=0xfffe<<10;//只对PB10置0
注意:GPIOB只有一个引脚使用时,才能通过移位运算操作ODR寄存器
2、使用GPIOB_BSRR寄存器
置1:
GPIOB->BSRR|=0x00000420;//同时设置PB5和PB10为1
//GPIOB->BSRR=0x00000420;
注意:此时低位设置覆盖了高位设置
GPIOB->BSRR|=1<<5;//单独设置PB5为高电平
//GPIOB->BSRR=1<<5;
GPIOB->BSRR|=1<<10;//单独设置PB10为高电平
//GPIOB->BSRR=1<<10;
/*同时设置PB5和PB10为高电平*/
GPIOB->BSRR=1<<5;
GPIOB->BSRR&=0;//清零
GPIOB->BSRR=1<<10;
注意:上面代码必须清零,否则第三行代码移位时,会把原来第5位的1左移到第15位,对PB15也产生了影响!
置0:
不能和32位数进行与运算、或运算,否则低位设置会覆盖高位,导致要不PB5、PB10置1、要不保持原来的状态不变!
同时设置PB5和PB10
/*注意,一定要清零*/
GPIOB->BSRR|=1<<21;//设置PB5
GPIOB->BSRR&=0;//清零
GPIOB->BSRR|=1<<26;//设置PB10
只设置一个IO的话,就把上述代码第一行、第三行单独拿出来就行了
3、使用GPIOB_BRR寄存器
该寄存器只能置0
GPIOB->BRR|=0x0420;//同时设置PB5、PB10为0
//GPIOB->BRR=0x0420;
该寄存器是32位寄存器,但是高16位保留,所以可以当做16位寄存器来使用,和16位数与、或运算就行了。不过并不是所有单片机都可以这样,应该是这款单片机与、或运算时,是低位对齐,高位没对齐就补0。并不是所有单片机都这样,所以最好写成32位的形式。
/*同时操作PB5、PB10 为0*/
/*注意:一定要清零*/
GPIOB->BRR|=1<<5;//操作PB5为0
GPIOB->BRR&=0;//清0
GPIOB->BRR|=1<<10;//操作PB10为0
4、小总结(比较重要)
使用寄存器与、或运算比较麻烦,因为要算16、32位的那个数。采用移位法可以操作ODR来输出高低电平,但是如果IO占用复杂,移位法就会造成IO口紊乱,GPIOx只有一个IO口是,才可以用移位法控制ODR寄存器。
所以使用BSRR寄存器和BRR寄存器去解决移位时IO口紊乱的问题。但是BSRR寄存器高位和低位同时配置时,低位会覆盖高位的设置,所以推荐使用以下方法:
如果要控制IO输出高低电平、采用BSRR和BRR寄存器来设置ODR寄存器,进而控制对应IO口。置1时,采用BSRR进行低位操作;置0时,采用BRR寄存器。