STM32F103VET6學習(2)

一.前言

這次主要寫的內容是對LED的點亮的程序的解釋,以及如何與數據手冊結合起來寫程序。程序會在下面再次附上。

二.正文

1.基址宏定義
STM32內的各部分的地址都是分配好的,我們直接利用就行。基址宏定義,即如下內容(這個可以從數據手冊獲得,不需要深究):

/*總線基地址*/
#define PERIPH_BASE ((unsigned int)0x40000000)
/*APB2總線基地址*/
#define APB2PERIPH_BASE (PERIPH_BASE + 0x00010000)
/*AHB總線基地址*/
#define AHBPERIPH_BASE (PERIPH_BASE + 0x00020000)
/*GPIO外設基地址*/
#define GPIOB_BASE (APB2PERIPH_BASE + 0X0C00)
/*RCC外設基地址·*/
#define RCC_BASE *(unsigned int*)(AHBPERIPH_BASE + 0X1000)

這在stm32f10x.h文件中是已經定義好的,我們不需要再次定義。
在此基礎上需要做的就是,定義我們要用的GPIO寄存器地址並把它強制轉換爲指針,這樣指針就可以指向我們寄存器想要指向的地址,從而實現想要實現的功能。後文不時出現的寄存器GPIOx中的x之所以是B,是因爲本次我們要點亮的LED在芯片上連接的都是PBx口,只看LED的話,這裏的x可以爲0、1和5,對比下圖就可以看明白:
芯片原理圖部分截圖
上圖是芯片的部分截圖,可以看到LED口連接了PBx口(x=0,1,5)。
LED
這是LED的原理圖,可以看到,LED爲共陽極接法(3.3V),因此要點亮LED(D3)就要從PBx口輸出低電平從而使其導通。圖片中的R,G,B分別代表Red,Green和Black,指的是顏色的三原色:紅色、綠色和黑色,這三者分別不同的取值組合可以產生任意已知的顏色。由於目前我知識水平有限,一會兒就主要說說點亮綠色和紅色的就好了(因爲黑色等於沒亮,所以就不做單獨顯示黑色的了)。下來繼續說寄存器。
如下代碼,是這幾個寄存器地址的定義:

#define GPIOB_CRL *(unsigned int*)(GPIOB_BASE + 0X00)
#define GPIOB_CRH *(unsigned int*)(GPIOB_BASE + 0X04)
#define GPIOB_ODR *(unsigned int*)(GPIOB_BASE + 0X0C)

寄存器的偏移地址都是在GPIO外設基地址的基礎上進行偏移的,偏移地址我們可以查閱數據手冊獲得:
GPIOB_CRL寄存器
由此知道,GPIOB_CRL的偏移地址爲0x00,因此有:

#define GPIOB_CRL *(unsigned int*)(GPIOB_BASE + 0X00)

GPIOB_CRH寄存器
由此知道,GPIOB_CRH的偏移地址爲0x04,因此有:

#define GPIOB_CRH *(unsigned int*)(GPIOB_BASE + 0X04)

GPIOB_ODR寄存器
由此知道,GPIOB_ODR寄存器的偏移地址爲0x0C,因此有:

#define GPIOB_ODR *(unsigned int*)(GPIOB_BASE + 0X0C)

當然,除了要知道這幾個寄存器,還得開啓GPIO外設時鐘,通過RCC_APB2ENR寄存器的配置來完成。如果想要外設工作就必須開啓相應的外設時鐘。所有的 GPIO 都掛載到 APB2 總線上,具體的時鐘由 APB2 外設時鐘使能寄存器(RCC_APB2ENR)來控制:
RCC_APB2ENR寄存器
由此知道,RCC_APB2ENR寄存器的偏移地址爲0x18,因此有:

#define RCC_APB2ENR *(unsigned int*)(RCC_BASE + 0X18)

如上就完成了這幾個關鍵要用的寄存器的定義。
2.點亮LED
(1)開啓外設時鐘

RCC_APB2ENR |= (1<<3);    //開啓時鐘

其中的"<<"是左移運算符,表示將1左移3次。什麼意思?用二進制來看,1就是1,將1左移3位,就變成了1000,對比下圖該寄存器的位配置來看:
寄存器
看圖片中的解釋也能明白。我們要用的是PB口,因此就需要配置IOPB這一位了,該位高有效,因此給它賦1 。其餘位,保持不變。要保持的原因是我們實現我們想要的功能就好了,但是不要更改原來的配置,以避免不必要的麻煩。
保持不變的方法,就是使用“或”運算,有1爲1,全0爲0.
因此纔有了 " |= " , 即:

RCC_APB2ENR = RCC_APB2ENR | (1<<3)

這與剛纔的代碼的功能是完全一樣的,只不過上一個更加簡潔。在之後的幾句重要代碼也採用類似寫法,不再作解釋。
(2)以點亮LED(D3)爲綠色爲例,來闡述該步驟。

先把LED的原理圖在此附在下面,看着方便。
LED
第一,先知道綠色LED對應的端口是PB0口,不明白就看圖。
第二,清空控制PB0的端口位,之後再對它寫入,從而實現要讓它在這個口該實現的功能。
首先看看這步要用的是哪個寄存器,是這個:GPIOB_CRL
爲啥?數據手冊中是這樣的描述的:
數據手冊描述
可以看到要配置的就是端口配置低寄存器,也就是CRL,“低”就體現在了L上面。其他的配置就是輔助的配置,等下說。
寄存器

  • 先看0-31這32個數,它代表的是32位。每個GPIO引腳佔用四位,通俗的說,就是PB0佔4位,爲0~3位;PB1佔4位,爲4 - 7位;…以此類推。從這裏也明白了PB0包含了MODE0和CNF0,PB1包含了MODE1和CNF1,…,同樣,以此類推,這個一會兒還有重用。
  • 小字rw表示讀寫權限爲可讀可寫,如果只有r或w,則表示只讀或只可寫入。
  • 每個引腳的四位由CNFx[1:0]和MODEx[1:0]這倆組成(x爲0,1,2,…,15。8-15是CRH中的)。CNF是用來定義輸入的模式的,MODE是定義輸出的模式以及輸出的速度(這個不用記,用的話查手冊就好了),在0~31這32位不同的位CNF和MODE有不同的規定,看下圖就明白了:
    CNF和MODE位

由此,我們回到最初的話題,要點亮綠色LED,而綠色連接的是PB0口,而PB0對應的是CNF0[1:0]和MODE0[1:0]這兩個位。

剛剛也說過了,要清空這四位(現在應該明白爲啥是這4位了),那就讓這四位爲0,同樣,保留其他位的原先配置不變。在這裏因爲要強行將PB0的四位清零,所以就不能用 或運算符了,不然如果原來是1,就沒法清零了。
先取值爲1111(二進制),化成十六進制就是0x0F,然後我們讓他左移(4*0)位,然後再進行取反,之後再與原來的GPIOB_CRL進行與運算。下來解釋這樣做的原因:
取值爲1111,之後進行的取反運算會把它變成0000,再進行按位的與運算時不就把這四位強制清零了麼?然後再說這個左移(4*0)位。這裏的4是因爲每個PBx口有四位,0表示在當前的位置,也就是相當於沒有左移,那爲啥還要這樣寫呢?在這兒我順便也就把程序的改寫說了。我目前控制的是PB0口,如果我要控制爲紅色LED,且與查原理圖可以知道對應的是PB5口。有了(4*0)這個基礎,
我只需要把0改成5,也就是(4x5),相當於把這4位總體左移了5下,每下是4位,也就是移動了20位,看下面的二進制解釋:

左移前是PB0:
0000 0000 0000 0000 0000 1111
左移後得到的PB5:
1111 0000 0000 0000 0000 0000

可以很明白的看到,1111的每一位被左移了20位。
這樣如果我們保留與PB0相同的配置(輸入模式、速度等),只需要更改這個左移的次數就可以換到對應的LED上了,很方便。
因此清空PB0端口位的代碼如下:

GPIOB_CRL &= ~( 0X0000F << (4*0));   //清空PB0口的端口位

明白了這個,下來就繼續說對MODE0[1:0]和CNF0[1:0]配置的值所體現的功能。

在此,要操控的是1:0口(>00),從上表我們就知道,如果要用通用推輓輸出模式,則需要CNF0[1:0]=00;設置輸出模式的速度爲10MHz,則MODE0[1:0]=01。因此配置該寄存器的值就是00 01。
綜合考慮的因素,我們就可以寫出GPIOB_CRL的配置的代碼:

GPIOB_CRL |= (1<<4*0);     //配置PB0口,推輓,10MHz

再做個解釋:0001和1是同樣的值,都是1; * (乘法的星號)運算符的優先級高於左移運算符<<,所以這裏沒有給4x0帶上括號。配置時,要保留原來的配置,而且剛纔已經把PB0的端口位清空爲0了,所以可以使用或運算符來配置。4x0的原因和之前相同,不再贅述。

3. 配置輸出低電平
爲何輸出低電平,開始就說過了,是因爲LED共陽接法,所以陰極接的PB0要輸出低電平,這樣這個發光二極管纔可以導通。
這一步要用的寄存器當然就是數據輸出寄存器了,Output Data Register,也就是ODR,即GPIOB_ODR。下面看看它的配置規則。
ODR
可以看到,ODR是低16位有效,每一位對應一個GPIO引腳。我們要配置I/O0,也就是ODR0這一位,在這裏我沒有先去清空,原因就是我要寫入的是低電平0,使用與運算符(有0爲0,全1爲1),這樣就可以保留原來的配置並且也寫入了0,和之前說的沒有矛盾哦,看代碼:

GPIOB_ODR &= ~(1<<0);   //PB0輸出低電平

解釋:配置的ODR0在第一位(也是最低位),所以直接給它賦值1,左移0位,然後取反,得到的就是0了,而其他位全部都是1,這樣進行與運算時,其他位的配置就可以完全保留了。這裏的左移0位同樣也是有原因的,而且與之前的(4*0)是相同的道理,如果我要點亮PB5口的紅色的LED,只需要將0改成5,也就是:

左移前:1
取反後:0(其他位都爲1)

左移5次後:10000
按位取反後:01111

這樣就很明瞭了,進行與運算之後同樣可以把其他端口原來的配置保留了,不與之前說的原則相矛盾。
最後再加上一句代碼讓程序處在死循環中,這樣燈就會一直點亮:

while(1);

4. 全部代碼
註釋我就不再寫了,前面解釋的很清楚,讀者看着也可以給自己解釋

#include"stm32f10x.h"
#define GPIOB_CRL *(unsigned int*)(GPIOB_BASE + 0X00)
#define GPIOB_CRH *(unsigned int*)(GPIOB_BASE + 0X04)
#define GPIOB_ODR *(unsigned int*)(GPIOB_BASE + 0X0C)
#define RCC_APB2ENR *(unsigned int*)(RCC_BASE + 0X18)

int main(void)
{
		RCC_APB2ENR |= (1<<3); 
		
		/*點亮綠色的LED*/
		GPIOB_CRL &= ~( 0X0000F << (4*0)); 
		GPIOB_CRL |= (1<<4*0);  
		GPIOB_ODR &= ~(1<<0);     
/*
		//這段是點亮紅色LED的代碼
		GPIOB_CRL &= ~(0x0F<<(4*5));   
		GPIOB_CRL |= (1<<4*5);     
		GPIOB_ODR &= ~(1<<5);    
*/
	while(1);
}
void SystemInit(void)
{
	/*由於在彙編語言裏有這個函數,爲了避免調用時找不到而報錯,
	就在這裏象徵性的寫這個函數來騙過編譯器不報錯*/
}

5 .LED效果圖
(1)綠色LED:
效果圖
(2)紅色LED:
效果圖

三.總結

首先,這次的內容我覺得自己還是吃的很透的,雖說做得很簡單,就是一個簡單的點亮發光二極管,但是其中包含了我對寄存器的認識與應用,以及寄存器的位的配置還有“移位”思想、配置讀取與保留等等,具體的我都在上面的內容中有反映出來。這次覺得很充實,高興!
打字過程中難免會有錯別字,加上可能有的其他理論問題,歡迎大家指出,共同進步,謝謝!至此,本次內容,完結!

發佈了11 篇原創文章 · 獲贊 13 · 訪問量 5619
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章