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