STM32筆記(12):撰寫程式從暫存器入門

前兩篇文章實做使用 CubeMX 與 Keil 編譯程式來控制 STM32,透過圖形的介面定義 CPU 暫存器相關的設定,讓 LED 燈閃爍或由按鍵控制。這個介面對於初學者或是快速開發很有幫助,但對於我這還沒入門且有一點點基礎的人來說,還不太夠,原因是只在抽象層定義,比較底層的控制,仍然需要有對 MCU 處理的概念後,才能發揮其效益。簡單來說。如果同樣功能用 CubeMX 開發會比底層使用暫存器的程式時間會縮短很多,但是同樣功能的兩種程式,執行速度一定是暫存器的程式較快,畢竟 CubeMX 會包進一些基本的函數庫在程式內,執行起來就會比較慢些,這兩者各有其優點。

本篇開始紀錄如何查閱手冊,找到控制對應的暫存器,讓暫存器藉由數值的存入或電位高低的變化,來控制其外部設備。在開始前先簡單瞭解微控制器的基本工作原理,以及如何使用暫存器作為與 STM32 溝通的方法。

所謂的為微控制器(Micro Controller unit, 簡稱 MCU),也就是硬體電路的大腦,MCU內部包含中央處理裝置 ( CPU )、記憶體及周邊相關裝置(如ADC、DAC、GPIO、PWM等)。當程式被載入執行時,會先存到記憶體中,並留下所分配的地址編號,當中央處理器執行指令時,則依序取得該位址內容後執行這些動作。

使用暫存器編寫程式,需要一直查詢使用手冊上的暫存器控制位址,才能寫出程式,與前兩個實作使用 CubeMX 及 Keil 的環境下完成程式完全不同,後者偏重在使用廠商開發的函數庫,只要會知道函數庫的用法即可開始編寫程式,其實函數庫也是將記憶體紀錄成比較容易識別的變數、結構(Structure)屬性等,方便記憶及容易閱讀。

我購買的開發板是 STM32F103C8T6 ,也有一片是 STM32F407的開發板,就以前者當作範例。由 STM32F103 晶片手冊得知其定址範圍從 0x00000000 到 0xFFFFFFFF,分成8個塊(Block0~Block7),每塊大小為512M,一共 4G 的空間大小。一共有3條匯流排總線:APB1、APB2、AHB。所謂的基地址是指該總線的位址均以這個位置往上增加。
匯流排名稱匯流排基地址
APB10x4000 0000
APB20x4001 0000
AHB0x4001 8000

STM32 各匯流排總線有自己的時鐘(震盪頻率),一個 GPIO 要能運作,需要設定其時鐘才能運作,其次設定該 GPIO 為輸出或輸入,最後設定為高電位或低電位,下圖是撰寫設定的邏輯與流程,分成三個部分:


瞭解上述如何設定控制 GPIO 的步驟與方法後,使用開發版上的一個 GPIO(PA4)為例,將 PA4 連接 LED,看看如何撰寫暫存器程式來控制亮或滅。過程中使用暫存器控制引腳輸出輸入,包括時鐘設定與設定暫存器位置等,瞭解其運作方式後,再進階到使用變數的定義來取代暫存器的位址,並寫成函數庫來讓程式變的容易閱讀。

[電路連接圖]

將 LED (-)的一腳接 PA4 ,另一腳(+)接電阻 220 歐姆,電阻另一端接 5V。

[手冊查閱與程式編寫]

(1) 找出GPIOx對應的時鐘,設定為 Enable
將 LED接在 PA4,表示使用 GPIOA 這個群組,如果是使用開發板預設的 LED,其接腳是 PC13,表示使用 GPIOC 這個群組。打開使用手冊,找到復位和時鐘控制(RCC)的起始位置,如下圖紅框處:
上圖的表再往下看,查找 GPIOA(GPIO端口A)的總線匯流排是哪一個?如下圖紅圈處,也請一併記下其起始位址:
上表 GPIOA 的位址為:0x40010008 ,其總線使用 APB2。再繼續查找 APB2 外設時鐘的暫存器為 RCC_APB2ENR ,再將這個位址設定為 Enable,也就是高電位,或是填入 1 的值,如下圖:
得到其偏移位置為0x18,表示設定時鐘的暫存器的位置全名為:0x40021018。程式寫法如下:
*(unsigned int*)0x40021018 =  *(unsigned int*)0x40021018 | (1<<2);
上面的運算式的寫法可以寫成:
*(unsigned int*)0x40021018 |= (1<<2);
意思是表示將 0001 往左移動2個位元,再和這個位址的內容做 OR 運算,讓第 3 個位元變成 1,亦即將 IOPA Enable。


(2) 配置 GPIOx 模式,設定 CRL 或 CRH 對應的 GPIO 為輸出/輸入
依照連接的 Port 找到對應的 GPIO 配置為低寄存器(GPIOx_CRL)或高寄存器(GPIOx_CRH),如果是 Port0 ~ Port7(例如PA0~PA7),則查閱配置為低寄存器(GPIOx_CRL)的章節 ;如為Port8~Port15(例如PA8~PA15),則查閱高寄存器(GPIOx_CRH)章節。以連接 PA4 為例,要看低寄存器(GPIOx_CRL)使用的偏移位址,如下表紅框處:

上圖第二個紅框處的 CNFx 有2個位元,由 MODE 來決定輸入或輸出,當 MODE 為 00 時表示輸入,其他三個值(00,01,11)分別表示不同輸出速度。CNF1 則表示不同的模式,如下表:
MODEx有 2 個位元,設定輸出的速度,如下表:

以 PA4 的設定為例,我們選擇 10MHz 的速度(MODE為01),採用推挽輸出方式(CNF 為 00),合併起來為 0001,也就是程式中要將 0001寫到這 4 個位元。除了上述 CNF 和 MODE 設定外,還要知道偏移位址 0x00,將這個值跟 GPIOA 的基地址相加,這表示 GPIOA_CRL 的完整存取位址為:0x40010800。要存取這 4 個位元,先要將其清為 0,再給新的值,否則邏輯運算會造成錯誤的結果。
*(unsigned int*)0x40010800 = *(unsigned int*)0x40010800 & ~(0x0F<<(4*4));  
*(unsigned int*)0x40010800 = *(unsigned int*)0x40010800 | (1<<16);  
第一行指令是將 0x0F(二進位為0b1111),先左移 16 位後,做 NOT 運算,變成 0000,再和位址的值進行 AND 運算,4 個 Bits 都變成 0。第二行是將要設定的值0001(CNF:00,MODE:01)左移 16 位,跟原來的值做 OR 運算,置於 CNF4 和 MODE4 的位置。上述兩行可以改寫成以下運算式:
*(unsigned int*)0x40010800 &= ~(0x0F<<(4*4)); 
*(unsigned int*)0x40010800 |= (1<<16);

(3) 透過 ODR、BRR 或 BSRR 等寄存器,設定為高或低電位
查閱GPIO_ODR偏移位置,如下圖:
將 PA4 對應的 ODR4 設定為低電位。  
*(unsigned int*)0x4001080C = *(unsigned int*)0x4001080C & ~(1<<4);
將 1 左移 4 位後,做 NOT 運算,變成 0,再和原來的值做 AND 運算,結果為 0。

以下是 main.c 完整的程式碼:
int main()
{
	*(unsigned int*)0x40021018 |=(1<<2);     //RCC_APB2ENR 時鐘
	*(unsigned int*)0x40010800 &= ~(0x0F<<(4*4));  //將GPIO_CLR的CNF4與MODE4(PA4)清為0 
	*(unsigned int*)0x40010800 |= (1<<16);    // 將GPIO_CLR的CNF4與MODE4(PA4)的4Bit填上0001,將0001左移16位
	*(unsigned int*)0x4001080C amp;=~(1<<4);  // 將GPIOC_ODR 輸出為低電位
} 
 
void SystemInit(void)
{	
}
程式中多了一個函數 SystemInit(void),這是 STM32 系統復位後首先會進入 SystemInit() 函數進行時鐘的設置,之後再進入主函數 main()。如果對於 SystemInit()的用法還不懂沒關係,之後的實作有遇到時,再來瞭解說明。程式的畫面如下:
各位有沒有發現,上圖左方在 main.c 下方,多了一個 startup_stm32f10x_md.s,這個是依據開發板的 Flash 容量大小來選擇,STM32 系列晶片的種類和對應的啟動文件如下表。STM32F103C8 開發板的 Flash 為 64k,選取“startup_stm32f10x_md.s”這個檔案。
總線名稱總線基地址
startup_stm32f10x_cl.s互聯型的器件,STM32F105xx,STM32F107xx
startup_stm32f10x_hd.sFLASH大於128,大容量的 STM32F101xx,STM32F102xx,STM32F103xx
startup_stm32f10x_hd_vl.sFLASH大於128,大容量的 STM32F100xx
startup_stm32f10x_ld.sFLASH小於64K,小容量的 STM32F101xx,STM32F102xx,STM32F103xx
startup_stm32f10x_ld_vl.sFLASH小於64K,小容量的 STM32F100xx
startup_stm32f10x_md.sFLASH=64 or 128,中容量的 STM32F101xx,STM32F102xx,STM32F103xx
startup_stm32f10x_md_vl.sFLASH=64 or 128,中容量的 STM32F100xx
startup_stm32f10x_xl.sFLASH=512K到1024K,超高密度的 STM32F101xx,STM32F102xx,STM32F103xx

對應的檔案,可以在官方提供的標準函數庫中找到,按以下路徑找尋啟動文件:STM32F10x_StdPeriph_Lib_V3.5.0\Libraries\CMSIS\CM3\DeviceSupport\ST\STM32F10x\startup\arm ,將startup_stm32f10x_md.s 複製到與 main.c 同文件夾中即可。經過編譯及上傳編譯結果到開發板,就完成這次的實作。

[結果]


[參考資料]

1 留言

張貼留言

較新的 較舊