這個系列將介紹 STM32 裸機編程的基礎知識,以便更好地理解 STM32Cube、Keil 等框架和 IDE 是如何工作的。本指南完全從頭開始,只需要編譯器和芯片數據手冊,而不依賴任何其它軟件工具和框架。
這個系列涵蓋了以下話題:
- 存儲和寄存器
- 中斷向量表
- 啟動代碼
- 鏈接腳本
- 使用
make
進行自動化構建 - GPIO 外設和閃爍 LED
- SysTick 定時器
- UART 外設和調試輸出
printf
重定向到 UART- 用 Segger Ozone 進行調試
- 系統時鐘配置
- 實現一個帶設備儀表盤的 web 服務器
我們將使用 Nucleo-F429ZI 開發板 (淘寶購買) 貫穿整個指南的實踐,每個章節都有一個相關的完整小項目可以實戰。最后一個 web 服務器項目非常完整,可以作為你自己項目的框架,因此這個示例項目也提供了其他開發板的適配:
對其他板子的適配支持還在進行中,可以提交 issue 來建議適配你正在用的板子。
工具配置
為繼續進行,需要以下工具:
- ARM GCC, https://launchpad.net/gcc-arm-embedded - for compiling and linking
- GNU make, http://www.gnu.org/software/make/ - for build automation
- ST link, https://github.com/stlink-org/stlink - for flashing
Mac 安裝
打開終端,執行:
$ /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
$ brew install gcc-arm-embedded make stlink
Linux (Ubuntu) 安裝
打開終端,執行:
$ sudo apt -y install gcc-arm-none-eabi make stlink-tools
Windows 安裝
scoop install gcc-arm-none-eabi make stlink
驗證安裝:
- 下載這個倉庫,解壓到
C:\\
- 打開命令行,執行:
cd C:\\bare-metal-programming-guide-main\\step-0-minimal
make
數據手冊
- STM32F429 MCU datasheet
- Nucleo-F429ZI board datasheet
微控制器介紹
微控制器(microcontroller,uC 或 MCU)是一個小計算機,典型地包含 CPU、RAM、存儲固件代碼的 Flash,以及一些引腳。其中一些引腳為 MCU 供電,通常被標記為 VCC 和 GND。其他引腳通過高低電壓來與 MCU 通信,最簡單的通信方法之一就是把一個 LED 接在引腳上:LED 一端接地,另一端串接一個限流電阻,然后接到 MCU 信號引腳。在固件代碼中設置引腳電壓的高低就可以使 LED 閃爍:
存儲和寄存器
MCU 的 32 位地址空間按區分割。例如,一些存儲區被映射到特定的地址,這里是 MCU 的片內 flash,固件代碼指令在這些存儲區讀和執行。另一些區是 RAM,也被映射到特定的地址,我們可以讀或寫任意值到 RAM 區。
從 STM32F429 數據手冊的 2.3.1 節,我們可以了解到 RAM 區從地址 0x20000000 開始,共有 192KB。從 2.4 節我們可以了解到 flash 被映射到 0x08000000,共 2MB,所以 flash 和 RAM 的位置像這樣:
從數據手冊中我們也可以看到還有很多其它存儲區,它們的地址在 2.3 節”Memory Map” 給出,例如:”GPIOA” 區從地址 0x40020000 開始,長度為 1KB。
這些存儲區被關聯到 MCU 芯片內部不同的外設電路上,以特殊的方式控制外設引腳的行為。一個外設存儲區是一些 32 位寄存器的集合,每個寄存器有 4 字節的空間,在特定的地址,控制著外設的特定功能。通過向寄存器寫入值,或者說向特定的地址寫一個 32 位的值,我們就可以控制外設的行為。通過讀寄存器的值,我們就可以得到外設的數據或配置。
MCU 通常有許多不同的外設,其中比較簡單的就是 GPIO(General Purpose Input Output,通用輸入輸出),它允許用戶將 MCU 引腳設為輸出模式,然后置 “高” 或置 “低”;或者設置為輸入模式,然后讀引腳電壓的 “高” 或 “低”。還有 UART 外設,可以使用串行協議通過兩個引腳收發數據。還有許多其它外設。
在 MCU 中,一個相同外設通常會有多個 “實例”,比如 GPIOA、GPIOB 等等,它們控制著 MCU 引腳的不同集合。類似地,也有 UART1、UART2 等等,可以實現多通道。在 Nucleo-F429 上,有多個 GPIO 和 UART 外設。
例如,GPIOA 外設起始地址為 0x40020000,我們可以從數據手冊 8.4 節找到 GPIO 寄存器的描述,上面說 GPIOA_MODER
寄存器偏移為 0,意味著它的地址是 0x40020000 + 0
,寄存器地址格式如下:
數據手冊顯示 MODER 這個 32 位寄存器是由 16 個 2 位的值組成。因此,一個 MODER 寄存器控制 16 個物理引腳,0-1 位控制引腳 0,2-3 位控制引腳 1,以此類推。這個 2 位的值編碼了引腳模式:’00’代表輸入,’01’代表輸出,’10’代表替代功能 —— 在其它部分進行描述,’11’代表模擬引腳。因為這個外設命名為’GPIOA’,所以對應引腳名為’A0’、’A1’,等等。對于外設’GPIOB’,引腳則對應叫’B0’、’B1’,等等。
如果我們向 MODER 寄存器寫入 32 位的值’0’,就會把從 A0 到 A15 這 16 個引腳設為輸入模式:
* (volatile uint32_t *) (0x40020000 + 0) = 0; // Set A0-A15 to input mode
通過設置獨立的位,我們就可以把特定的引腳設為想要的模式。例如,下面的代碼將 A3 設為輸出模式:
* (volatile uint32_t *) (0x40020000 + 0) &= ~(3 < < 6); // CLear bit range 6-7
* (volatile uint32_t *) (0x40020000 + 0) |= 1 < < 6; // Set bit range 6-7 to 1
我來解釋下上面的位操作。我們的目標是把控制 GPIOA 外設引腳 3 的位,也就是 6-7,設為特定值,在這里是 1。這個需要 2 步,首先,我們必須將 6-7 位的當前值清除,也就是清’0’,因為這兩位可能已經有值;然后,我們再將 6-7 設為期望值。
所以,第一步,我們先把 6-7 位清’0’,怎么做呢?4 步:
- 使一個數有連續的 N 位’1’
- 1 位用 1:
0b1
- 2 位用 3:
0b11
- 3 位用 7:
0b111
- 4 位用 15:
0b1111
- 以此類推,對于 N 位,數值應為
2^N - 1
。對于 2 位,數值為3
,或者寫為二進制0b00000000000000000000000000000011
- 1 位用 1:
- 將數字左移位。如果我們需要設置位 X-Y,則將數字左移 X 位。在我們的例子中,左移 6 位:
(3 << 6)
,得到0b00000000000000000000000011000000
- 取反:0 變 1,1 變 0:
~(3 << 6)
, 得到0xb11111111111111111111111100111111
- 現在,將寄存器值與我們的數字進行邏輯” 與” 操作,6-7 位與’0’后會變 0,其它位與’1’后不變,這就是我們想要的:
REG &= ~(3 << 6)
。注意,保持其它位的值不變是重要的,我們并不想改變其它位的配置。
一般地,如果我們想將 X-Y 位清除,或者說設為 0,這樣做:
PERIPHERAL- >REGISTER &= ~(NUMBER_WITH_N_BITS < < X);
最后,我們把那些位設為我們想要的值,則需要把想要的值左移 X 位,然后與寄存器當前值進行邏輯” 或” 運算:
PERIPHERAL- >REGISTER |= VALUE < < X;
現在,你應該明白了,下面的兩行代碼將把 GPIOA MODER 寄存器的 6-7 位設為 1,即輸出模式:
* (volatile uint32_t *) (0x40020000 + 0) &= ~(3 < < 6); // CLear bit range 6-7
* (volatile uint32_t *) (0x40020000 + 0) |= 1 < < 6; // Set bit range 6-7 to 1
還有一些寄存器沒有被映射到 MCU 外設,而是被映射到了 ARM CPU 的配置和控制。例如,有一個”Reset and clock control” 單元(RCC),在數據手冊第 6 節有描述,這些寄存器用來配置系統時鐘和一些其它的事情。
評論
查看更多