小言_互联网的博客

STM32F103五分钟入门系列(二)GPIO的七大寄存器+GPIOx_LCKR作用和配置(全网找不到【狗头】)

314人阅读  评论(0)

学习板:STM32F103ZET6

自己想例子、去设计代码不易,求赞~

一、GPIO的寄存器

参考文件:《STM32中文参考手册》

每个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....

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寄存器。


转载:https://blog.csdn.net/Curnane0_0/article/details/116276876
查看评论
* 以上用户言论只代表其个人观点,不代表本网站的观点或立场