Embedded Systems Bare-Metal Programming Ground Up (STM32) — Chap. 1 — Constructing Peripheral
Registers from Memory Addresses
剛好前陣子Udemy上面的課程在特價,想說還蠻划算的,就買了幾堂課,連結如下:
https://www.udemy.com/course/embedded-systems-bare-metal-programming/
想說趁著去德國前留在台灣的這段日子,好好的來繼續精進一下自己。剛好內容也是我蠻有興趣的,課程簡介影片有提到,在跟完整個課程以後,可以學會如何正確地從datasheet裡面extract出所需的資訊,以及在不借助任何Library的情況下,可以自行利用CubeIDE開發出像是GPIO、UART、ADC、Timer、I2C、SPI…等等的Driver。
大學期間其實就有接觸過STM的開發板,但是對於其內部運作的方式、以及Library一直都不是很了解,藉由這個機會,能讓自己有個好的基礎,我認為是蠻好的。
因此想說能藉此機會順便把每堂課的重點做個摘錄,希望日後,如果我自己忘記了,也能回來看一下我的這段學習歷程。
課程開始!!!!
開始前必需準備好的東西:
- 下載好STM32CubeIDE
- 針對自己的開發板,下載以下文件:Reference Manual、datasheet、user guide
首先最開始,看到User guide,並搜尋在文件裡找LED的章節,可以看到如下的內容。
基本上,課程一開始,我們希望能夠控制開發版上的User Led,我所使用的是STM32F429ZI-Nucleo,其上面有三個Led分別為LD1、LD2、LD3。首先我們必須要知道這些Led所對應的Port以及Pin分別是多少。由文件我們可以得知:
- LD1: PortA Pin5
- LD2: PortB Pin7
- LD3: PortB Pin14(我選的是這一顆)
選定好要控制哪個Port哪個Pin後,我們來到Datasheet,搜尋Memory Mapping,可以知道這塊MCU對於其內部記憶體的分配情況。並可以找到我們想要控制的Port B被歸類在AHB1這個Bus裡面。
搜尋Block diagram可以找到如上的圖,可以大概知道整體的運作流程。
有了這些資訊,我們便可以開始寫程式了!!!
通過Memory Mapping(1),可以知道我們的這些Peripheral分配到的記憶體位址在哪裡,可以看到是從0x4000 0000這裡開始的,接著,由於我們的Port B是在AHB1這個BUS上,因此我們也要知道AHB1是從哪裡開始,由Momery Mapping(2)可以知道,他是從0x4002 0000開始,為增加Code的易讀性,這邊透過宣告一個AHB1的Offset,最後將PERIPH_BASE透過Shift一個AHB1的Offset,來找到AHB1_PERIPH_BASE的位址。
同理,也可以定出GPIOB_BASE以及RCC_BASE。對了,剛才忘了說明,為了要讓我們可以得到Clock Access to GPIOB,我們必須要利用RCC這個Module去ENABLE他。
為了要控制LD3,我們必須要設定這個PIN對應到Register。一般來說,要控制一個Pin,我們會需要設定Direction Register(GPIOx_MODER)以及Data Register(GPIOx_ODR、GPIOx_IDR…)。所有這些有關Register的資訊,我們可以從Reference Manual裡面找到。
這邊我要控制的LD3是PortB的Pin14,因此我們必須要先設定RCC的AHB1_RCCENR。可以看到上圖內容都有所謂的Address Offset,我們可以利用的些我們先前設定的那些Base的地址,經過Shift這些Offset以後,存取到我們想要的地址。這其實有點像是,我們以公寓為例好了,每棟公寓有其對應的地址,像是XX市XX區X號這樣(Base),而公寓裡面可能又有XXXX號房(Register),而每個Register都有其對應到的記憶體空間,那就是你的房子對應到的坪數的感覺,我自己感覺是這樣啦哈哈哈哈!
因此,根據Reference Manual,我們可以完成我們的程式碼。
// Where is the led connected?
// Port: B
// Pin: 14
#define PERIPH_BASE (0x40000000UL) // unsigned long
#define AHB1_PERIPH_OFFSET (0x00020000UL)
#define AHB1_PERIPH_BASE (PERIPH_BASE + AHB1_PERIPH_OFFSET)
#define GPIOB_OFFSET (0x00000400UL)
#define GPIOB_BASE (AHB1_PERIPH_BASE + GPIOB_OFFSET)
#define RCC_OFFSET (0x00003800UL)
#define RCC_BASE (AHB1_PERIPH_BASE + RCC_OFFSET)
#define AHB1_EN_R_OFFSET (0x30UL)
#define RCC_AHB1_EN_R (*(volatile unsigned int *)(RCC_BASE + AHB1_EN_R_OFFSET))
#define MODE_R_OFFSET (0x00UL)
#define GPIOB_MODE_R (*(volatile unsigned int *)(GPIOB_BASE + MODE_R_OFFSET))
#define OD_R_OFFSET (0x14UL)
#define GPIOB_OD_R (*(volatile unsigned int *)(GPIOB_BASE + OD_R_OFFSET))
#define GPIOB_EN (1U<<1) // 0b 0000 0000 0000 0000 0000 0000 0000 0010
#define PIN14 (1U<<14)
#define LED_3 PIN14
int main(void)
{
// 1. Enable the clock access to the GPIOB
RCC_AHB1_EN_R |= GPIOB_EN;
// 2. Set PIN14 as output pin
GPIOB_MODE_R |= (1U<<28);
GPIOB_MODE_R &= ~(1U<<29);
while(1)
{
// 3. Set PB14 HIGH
GPIOB_OD_R |= LED_3;
// for(int i = 0; i < 1e6; i++){}
// Toggle PB14
//GPIOA_OD_R ^= LED_PIN;
}
}
其中,對於#define RCC_AHB1_EN_R (*(volatile unsigned int *)(RCC_BASE + AHB1_EN_R_OFFSET)),我當初也是不太懂他的意思(當初學c指標都還給老師…)
簡單來講Volatile是一種是C語言中的修飾詞,網路上有找到有人講的蠻簡單明瞭的,可以參考這裡:https://anal02.pixnet.net/blog/post/117485340-%5Bc%5D-volatile-%E7%9A%84%E7%94%A8%E6%B3%95%E5%92%8C%E7%94%A8%E6%84%8F
簡單講,用了Volatile之後我們可以確保我們對於記憶體的讀寫是正確的。因為我們現在使用的Register是隨時可能被改動的,若我們在Programming時沒有正確的直接從Register的記憶體中拿東西,而是從暫存器中拿,有時候是會出問題的。
再來想講這條程式碼一堆*的部分哈哈哈哈!因為我真的很久沒有寫指標了,因此想說順便來複習一下,順便寫在這裡。
簡單來講,當我們今天寫簡單的程式碼如下:
簡單來說,當我們今天宣告一個unsigned int a = 10時,我們會在記憶體位址為0x7fffbab6338c上創建一個整數型別變數量值為10。接著我們宣告一個整數型別指標變數unsigned int *ptra = &a,就是在記憶體位址為0x7fffbab63390上創建一個指整數指標型別變數值為0x7fffbab6338c。這個的流程大概是這樣,基本上記得一個要點即可:&是取變數的地址、*是取地址中的Value。
可以看到我們print出來的結果,前兩行應該沒有問題,第三行的ptra為一整數行別的指標變數,其value為一個地址。第四行*&a,我們對a取其地址,再取其地址下的Value(繞口令?!)。第五行&ptra,我們對ptra取其地址。基本上,藉由簡單的程式碼以及上面的示意圖應該能很好理解指標的概念,我也算是複習完了。
於是,回到我們剛剛的那一行#define RCC_AHB1_EN_R (*(volatile unsigned int *)(RCC_BASE + AHB1_EN_R_OFFSET)),這樣,這一行程式碼應該就很好懂了,其實他就是,將一個unsigned long行別的值,強制轉為unsigned int *型別的值,也就是轉為地址,再接著對這個地址取其值。因此,RCC_AHB1_EN_R這個Macro就代表著實際這個位址的Register的value,我們可以夠過main()裡面依照Reference Manual對裡面的bit做更改,可以控制LD3。
以上,就是我在第一堂課學到的東西。