并非所有處理器都有庫函數,而隻有在STM32才擁有庫函數以友善的調用庫函數去書寫代碼。真正操作處理器的本質還是寄存器,隻有了解如何利用寄存器去運作程式,去學習其他處理器就會比較容易上手了
程式在位址中跳轉,而位址以寄存器的方式進行分布。是以今天先嘗試使用位址來操作寄存器寫入程式。具體的位址分布可根據文檔《STM32F103xE資料手冊》及《STM32F10x參考手冊》進行查詢
預備知識
1、main()函數及編譯的預處理工作
編譯器在編譯時總是從main.c入口函數開始運作(初學暫未能遇到從其他.c開始編譯的類型),然後去讀取在預處理階段(include)的其他.h檔案,将其他.h檔案中的宏、變量、函數等進行檢查觀測是否出現重複定義或者錯誤,最後将其内容掃描進.c檔案中運作。其他.h檔案中包含的函數一般寫入其對應的.c檔案中加以子產品化調用
2、volatile關鍵字
volatile關鍵字告訴編譯器從寄存器位址取值而不是在緩存器裡取值,使用volatile關鍵字聲明變量的值,可以保證對特殊位址的穩定通路而不至于被改變
3、GPIO
GPIO标準:标準I/O口承受5V電壓;18MHz翻轉速度;每個GPIO口分為7個端口;可對位設定及清零
端口:每個端口配置兩個配置寄存器、兩個資料寄存器、一個置位/複位寄存器、一個複位寄存器、一個鎖定寄存器
端口位配置
4、GPIO庫函數
5、GPIO寄存器
有了以上的基礎,那麼先簡單的編寫一個點亮GPIOA端口1,2,3,4管腳的led燈程式吧
——首先在main.c檔案中包含stm32f10x.h頭檔案,這個檔案中包含了各種外設庫函數的定義,并且在.c檔案中寫下入口函數,具體如下
#include "stm32f10x.h"
int main(void)
{
}
注意一件有趣的事情是,在keil軟體中若最後一行代碼 } 後面沒有多加一行,就會出現如下錯誤:
user\led_exti.c(46): warning: #1-D: last line of file ends without a newline
這隻是工程師設計軟體時候的設定,以後隻要記得寫完代碼後多回車一行即可
——關于GPIOx_CRL的配置
通過上面的圖示可以看出,若需要配置GPIOA的1234管腳,那麼需要使用到端口配置低寄存器GPIOx_CRL
需要解釋的是,每個端口都有16個管腳,其中0-7為低位,8-15為高位。
可以看出編号為0123的位屬于PA0,4567的位屬于PA1,依次類推可找出需要設定的PA1/2/3/4
因為按要求需要點亮GPIOA,則寄存器名:GPIOA_CRL。表中MODE和CNFx依次排列,MODE用于選擇輸入/輸出速率,CNFx用于選擇輸入/輸出工作模式。
絕大多數情況下使用推挽式輸出,遇到I2C總線使用開漏輸出
是以在CNF中選擇00即可,而在速率選擇中暫選擇50MHz,也即是11。故在PA1管腳,也即是4567位上寫入0011,依次可推斷出其他管腳的值都是換算成十六進制的0x3。
在寄存器中,一個位址的是由基位址+偏移位址 組成,從《STM32F103xE資料手冊》(以下簡稱資料手冊)中可得知,PortA的位址映射在0x40010800-0x40010BFF中,這是基位址,另可知偏移位址為0x00。是以目前GPIOx_CRL的位址等于0x40010800H+0x00H,之後在此位址中寫入0011、0011的資料就可以實作管腳的初始化配置。
#define GPIO_CRL (*(volatile unsigned long *)0x40010800)
注解:其中使用unsigned long類型取出寄存器位址0x40010800,然後外圍再套一個取位址符以将該位址取入GPIO_CRL這個變量名中
——關于GPIOx_ODR的配置
GPIOx_ODR是端口輸出資料寄存器,其參數如下
此寄存器16-31位是保留位。在0-15管腳中寫入1,則輸入高電平;寫入0,則輸入低電平。若設定的LED是共陰極觸發,則寫入1輸出高電平點亮,寫入0輸出低電平熄滅。可知ODR的偏移位址為0Ch,則寄存器GPIOx_ODR的位址為0x40010800H+0x0CH。
其中GPIOx_BSRR也可實作端口位設定,不過暫時不管,其BRR為端口位清除。
GPIOx_LCKR為端口配置鎖定寄存器,可以讓端口為寫入操作期間不可改變端口值,也暫時不需要考慮。
#define GPIOA_ODR (*(unsigned long *)0x4001080c)
—— 關于RCC寄存器的配置
由系統的構架圖可以看出
其中GPIOA是挂靠在APB2總線上的,而此時便需要另外一個寄存器——RCC寄存器,可以在資料手冊文檔中找到RCC在存儲器中的位址映射在0x40021000-0x400233FFH。關于RCC線上路中的時鐘控制,此節暫不詳談,可知其選擇為72MHz即可。
具體RCC中包含了如下寄存器
該寄存器的端口位設定如下
若要開啟GPIOA端口使能,則需要将位2寫入1
其中RCC_APB2ENR的寄存器偏移位址是0x18。是以可知寄存器RCC_APB2ENR的位址為0x40021000H+0x18H
#define RCCAPB2ENR (*(volatile unsigned long *)0x40021018)
——當我們整理好資料後,最終編寫出的程式即為
#include "stm32f10x.h"
#define RCCAPB2ENR (*(volatile unsigned long *)0x40021018)
#define GPIO_CRL (*(volatile unsigned long *)0x40010800)
#define GPIOA_ODR (*(unsigned long *)0x4001080c)
void Delay(unsigned long N);
int main(void)
{
RCCAPB2ENR |= (1<<2); //使能
GPIOA_CRL &= 0xfff0000f; //清除端口位
GPIOA_CRL |= 0x00033330; //将端口設定為推挽式輸出和50MHz速率
GPIOA_ODR &= ~((1<<1)|(1<<2)|(1<<3)|(1<<4)); //輸入低電平,燈熄滅
while(1)
{
GPIOA_ODR &= ((1<<1)|(1<<2)|(1<<3)|(1<<4)); //點亮LED燈
Delay(10000);
}
}
void Delay(unsigned long N)
{
while(N)
{ }
}