學習板:STM32F103ZET6
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的暫存器
參考檔案:《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://www.uj5u.com/qita/282376.html
標籤:其他
