STM32暫存器操作、模板構建

舟清颺發表於2024-07-18

2024年7月18日 釋出於部落格園, 本文涉及到STM32F4XX和STM32F1XX系列

目錄
  • 外設暫存器查詢
    • ① 名稱
    • ② 偏移地址
    • ③ 暫存器位表
    • ④ 位功能說明
  • 暫存器基本操作
    • C語言的置位和清零
      • 具體方法
    • 設定GPIO流程
    • 給暫存器賦值
    • 帶引數宏
  • STM32F1xx
    • 晶片識別
    • 儲存器對映
    • 暫存器對映
    • 讓GPIOB埠的16個引腳輸出高電平,要怎麼實現?
    • STM32暫存器對映
    • C語言對暫存器的封裝
  • 新建暫存器(REG)模板
    • 建立工程

外設暫存器查詢

見<<野火STM32庫開發指南P64>>

在XX 外設的地址範圍內,分佈著的就是該外設的暫存器。以GPIO 外設為例,GPIO 是通用輸入輸出埠的簡稱,簡單來說就是STM32 可控制的引腳,基本功能是控制引腳輸出高電平或者低電平。最簡單的應用就是把GPIO 的引腳連線到LED 燈的陰極,LED 燈的陽極接電源,然後透過STM32 控制該引腳的電平,從而實現控制LED 燈的亮滅。

GPIO 有很多個暫存器,每一個都有特定的功能。每個暫存器為32bit,佔四個位元組,在該外設的基地址上按照順序排列,暫存器的位置都以相對該外設基地址的偏移地址來描述。

image

image

① 名稱

暫存器說明中首先列出了該暫存器中的名稱“(GPIOx_BSRR)(x=A⋯I)”這段的意思是該暫存器名為“GPIOx_BSRR”其中的“x”可以為A-I,也就是說這個暫存器說明適用於GPIOA、GPIOB 至GPIOI,這些GPIO 埠都有這樣的一個暫存器。

② 偏移地址

偏移地址是指本暫存器相對於這個外設的基地址的偏移。本暫存器的偏移地址是0x18,從參考手冊中我們可以查到GPIOA 外設的基地址為0x4002 0000 ,我們就可以算出GPIOA 的這個GPIOA_BSRR 暫存器的地址為:0x4002 0000+0x18;同理,由於GPIOB 的外設基地址
為0x4002 0400,可算出GPIOB_BSRR 暫存器的地址為:0x4002 0400+0x18 。其他GPIO 埠以此類推即可。

③ 暫存器位表

image

緊接著的是本暫存器的位表,表中列出它的0-31 位的名稱及許可權。表上方的數字為位編號,中間為位名稱,最下方為讀寫許可權,其中w 表示只寫,r 表示只讀,rw 表示可讀寫。本暫存器中的位許可權都是w,所以只能寫,如果讀本暫存器,是無法保證讀取到它真正內容的。而有的暫存器位只讀,一般是用於表示STM32 外設的某種工作狀態的,由STM32 硬體自動更改,程式透過讀取那些暫存器位來判斷外設的工作狀態。

④ 位功能說明

位功能是暫存器說明中最重要的部分,它詳細介紹了暫存器每一個位的功能。例如本暫存器中有兩種暫存器位,分別為BRy 及BSy,其中的y 數值可以是0-15,這裡的0-15 表示埠的引腳號,如BR0、BS0 用於控制GPIOx 的第0 個引腳,若x 表示GPIOA,那就是控制GPIOA 的第0 引腳,而BR1、BS1 就是控制GPIOA 第1 個引腳。

其中BRy 引腳的說明是“0:不會對相應的ODRx 位執行任何操作;1:對相應ODRx 位進行復位”。

這裡的“復位”是將該位設定為0 的意思,而“置位”表示將該位設定為1;說明中的ODRx 是另一個暫存器的暫存器位,我們只需要知道ODRx 位為1 的時候,對應的引腳x 輸出高電平,為0 的時候對應的引腳輸出低電平即可(感興趣的讀者可以查詢該暫存器GPIOx_ODR 的說明了解)。所以,如果對BR0 寫入“1”的話,那麼GPIOx 的第0 個引腳就會輸出“低電平”,但是對BR0 寫入“0”的話,卻不會影響ODR0 位,所以引腳電平不會改變。要想該引腳輸出“高電平”,就需要對“BS0”位寫入“1”,暫存器位BSy 與BRy 是相反的操作。

image

具體查對應的手冊和系統庫函式的封裝, 注意, 要看資料手冊, 查詢對應的資源才能知道具體的位置. 重點是掌握方法.

暫存器基本操作

C語言的置位和清零

若直接賦值0或1, 會將所有位都變為0或1

例如:*(unsigned int*)(0x40010C0C)=1;*(unsigned int*)(0x40010C0C)=0;

任何數&1,值不變 任何數&0,均為0

故而只對目標位操作

//清零  &=~

//置位  |=

具體方法

/*對某暫存器某些位清零*/
	// 配置IO口為輸出
	GPIOB_CRL &=  ~( (0x0f) << (4*0) );//對暫存器清零. 0x0f是0000 1111, 因為要取反, 要把哪一位清零就用1去清零

/*對某暫存器某1位置位*/
	//寫入值
	GPIOB_CRL |=  ( (1) << (4*0) );

/*對某暫存器某1位置零*/
	// 控制 ODR 暫存器
	GPIOB_ODR &= ~(1<<0);

/* 置位操作 */
	a|=(1<<?);
/* 清零操作 */
	a&=~(1<<?);

設定GPIO流程

開啟GPIOB埠的時鐘-->配置埠配置低暫存器的埠模式和速率-->控制ODR暫存器的高低電平

//開啟GPIOB埠的時鐘(看2.3和第六章6.3.7)
	//RCC外設地址0x4002 1000位於AHB匯流排, 該外設下的APB2暫存器,APB2外設時鐘使能暫存器偏移地址為0x18
	//讓該暫存器的對應IOPB埠時鐘開啟, 位於該暫存器的第3位, 即左移3位
*(unsigned int *)0x40021018 |= ((1)<<3);

//配置IO口位輸出
	//配置8.2.1埠配置低暫存器的埠模式和速率
	//因為4個為1組,所以控制PB0則為第0組,需要左移0組
*(unsigned int *)0x40010C00 |= ((1)<<(4*0));

//控制ODR暫存器的高低電平,此處為置零
*(unsigned int *)0x40010C0C &= ~((1)<<0);

給暫存器賦值

方法1:宏定義直接賦值

/*透過絕對地址訪問記憶體單元*/
//GPIOB的埠全部輸出為高電平
*(unsigned int*)(0x40010C0C)=0xFFFF;
/*
(0x40010C0C)是GPIOB輸出資料暫存器ODR的地址, 是一個立即數
(unsigned int*)(0x40010C0C) 將立即數強制型別轉換為指標, 指向地址
*(unsigned int*)(0x40010C0C) 對該地址所指向的區域進行操作
*(unsigned int*)(0x40010C0C)=0xFFFF; 從該地址開始往後數32位,均為1(十六進位制0xffff)
*/

/*透過宏定義,暫存器別名的方式訪問記憶體單元*/
// GPIOB 埠全部輸出 高電平
#define GPIOB_ODR (unsignedint*)(0x40010C0C)//給該地址定義個宏
* GPIOB_ODR = 0xFF;//對宏進行操作


/*為了方便操作,我們乾脆把指標操作“*”也定義到暫存器別名裡面*/
// GPIOB 埠全部輸出 高電平
#define GPIOB_ODR   *(unsignedint*)(0x40010C0C)
GPIOB_ODR = 0xFF;

方法2: 採用移位的方式

* (unsigned int * )0x40010c0c &=~(1<<0);

#define PERIPH_BASE      ((unsigned int)0x40000000)
#define APB2PERIPH_BASE  (PERIPH_BASE + 0x00010000)
#define GPIOB_BASE       (APB2PERIPH_BASE + 0x0C00)
#define GPIOB_ODR        *(unsignedint*)(GPIOB_BASE+0x0C)//ODR的絕對地址

// PB0輸出輸出低電平(清零) 將目標位清零 &= ~(1<<目標位)
GPIOB_ODR &= ~(1<<0);
/*
等效於 GPIOB_ODR = GPIOB_ODR & ~(1<<0);
根據優先順序, 先計算括號內(1<<0)  
	0001<<0--->0001
取反~
	~0001--->1110
計算按位與
	 GPIOB_ODR & 1110 若原來值為1000
	 則 1000 & 1110 
	 得 1000
*/

// PB0輸出輸出高電平 將目標位拉高 |= ~(1<<目標位)
GPIOB_ODR |= (1<<0);

按位邏輯運算子: A &= B 等效於 A = A&B

移位運算子:

左移 1<<2

  • 把十進位制數1轉換為二進位制 0000 0001
  • 整體左移2位 0000 0100 , 左側移除的丟失, 空位補0

右移需要區分符號位, 結果看機器 (1000 1010)>>2

  • //轉換為二進位制
  • 無符號位 整體右移2位 0010 0010 , 右側移除的丟失, 空位補0
  • 有符號位 整體右移2位 0010 0010或1110 0010 , 右側移除的丟失, 空位補符號位

或不用宏定義

* (unsigned int *)0x40010C0C &=~(1<<0);//將第一位置零

帶引數宏

#define    ON        1 //代引數宏
#define    OFF       0

// \  C語言裡面叫續行符,後面不能有任何的東西
	//帶參宏的主體
#define   LED_G(a)   if(a) \
	                       GPIO_ResetBits(LED_G_GPIO_PORT, LED_G_GPIO_PIN); \
                     else  GPIO_SetBits(LED_G_GPIO_PORT, LED_G_GPIO_PIN);

void LED_GPIO_Config(void);

STM32F1xx

暫存器 需要反覆看書籍第六章

晶片識別

1、如何看晶片絲印

2、懂得如何辨別正方向

image

若有2個點,則看小的點,逆時針。

若沒有點,那麼正看絲印,左上方為第一腳,逆時針數。

image

STM32晶片架構簡圖

image

flash儲存程式,SRAM儲存變數

IP廠商(核心廠商):ARM

SOC廠商(晶片廠商):ST


STM32F10xx系統框圖

image

在參考手冊的儲存架構章節中。

驅動單元(CPU,核心):ARM公司設計

被動單元:外設:ST公司設計

ICode 中的I 表示Instruction,即指令。我們寫好的程式編譯之後都是一條條指令,存放在FLASH中,核心要讀取這些指令來執行程式就必須透過ICode 匯流排,它幾乎每時每刻都需要被使用,它是專門用來取指的。


DCode 匯流排:DCode 中的D 表示Data,即資料,那說明這條匯流排是用來取數的。我們在寫程式的時候,資料有常量和變數兩種,常量就是固定不變的,用C 語言中的const 關鍵字修飾,是放到內部的FLASH當中的,變數是可變的,不管是全域性變數還是區域性變數都放在內部的SRAM。因為資料可以被Dcode 匯流排和DMA 匯流排訪問,所以為了避免訪問衝突,在取數的時候需要經過一個匯流排矩陣來仲裁,決定哪個匯流排在取數。

系統匯流排system 系統匯流排主要是訪問外設的暫存器,我們通常說的暫存器程式設計,即讀寫暫存器都是透過這根系統匯流排來完成的。

DMA 匯流排:DMA 匯流排也主要是用來傳輸資料,這個資料可以是在某個外設的資料暫存器,可以在SRAM,可以在內部的FLASH。因為資料可以被Dcode 匯流排和DMA 匯流排訪問,所以為了避免訪問衝突,在取數的時候需要經過一個匯流排矩陣來仲裁,決定哪個匯流排在取數

image

APB1低速匯流排,APB2高速匯流排

儲存器對映

儲存器本身不具有地址資訊,它的地址是由晶片廠商或使用者分配,給儲存器分配地址的過程就稱為儲存器對映,具體見圖儲存器對映。如果給儲存器再分配一個地址就叫儲存器重對映

2的32次方是4G。

圖在Datasheet search site的Memory mapping章節

image

重點學習Block2

image

image

image

image

暫存器對映

image

看STM32參考手冊中文版第八章

image

地址偏移是相對於埠基地址偏移,見第二章儲存器組織。

讓GPIOB埠的16個引腳輸出高電平,要怎麼實現?

/*透過絕對地址訪問記憶體單元*/
//GPIOB的埠全部輸出為高電平
*(unsigned int*)(0x40010C0C)=0xFFFF;
/*
(0x40010C0C)是GPIOB輸出資料暫存器ODR的地址, 是一個立即數
(unsigned int*)(0x40010C0C) 將立即數強制型別轉換為指標, 指向地址
*(unsigned int*)(0x40010C0C) 對該地址所指向的區域進行操作
*(unsigned int*)(0x40010C0C)=0xFFFF; 從該地址開始往後數32位,均為1(十六進位制0xffff)
*/

/*透過宏定義,暫存器別名的方式訪問記憶體單元*/
// GPIOB 埠全部輸出 高電平
#define GPIOB_ODR (unsignedint*)(0x40010C0C)//給該地址定義個宏
* GPIOB_ODR = 0xFF;//對宏進行操作


/*為了方便操作,我們乾脆把指標操作“*”也定義到暫存器別名裡面*/
// GPIOB 埠全部輸出 高電平
#define GPIOB_ODR   *(unsignedint*)(0x40010C0C)
GPIOB_ODR = 0xFF;

什麼是暫存器?

給有特定功能的記憶體單元取一個別名,這個別名就是我們經常說的暫存器,這個給已經分配好地址的有特定功能的記憶體單元取別名的過程就叫暫存器對映。

什麼叫儲存器對映?

給儲存器分配地址的過程叫儲存器對映,再分配一個地址叫重對映。

STM32暫存器對映

image

image

image

如何計算?以GPIOB為例

GPIOB的外設基地址為:0x4001 0C00

因為埠輸入資料暫存器(GPIOx_IDR) 地址偏移為:0x08

所以(GPIOx_IDR) 的絕對地址為 0x4001 0C08

所以我們只需要+4就能得到下一個暫存器的地址, 一個位元組有8位, 4個位元組即32位

CRL 埠配置低暫存器

CRH 埠配置高暫存器

IDR 輸入資料暫存器

ODR 資料輸出暫存器

BSRR和BRR是配置某埠的某一個位的

LCKR 鎖定暫存器

重點看第85頁

C語言對暫存器的封裝

在程式設計上為了方便理解和記憶,我們把匯流排基地址和外設基地址都以相應的宏定義起來,匯流排或
者外設都以他們的名字作為宏名

程式碼清單: 暫存器-4 匯流排和外設基址宏定義

/* 外設基地址*/
#define PERIPH_BASE ((unsigned int)0x40000000)

/* 匯流排基地址*/
#define APB1PERIPH_BASE PERIPH_BASE
#define APB2PERIPH_BASE (PERIPH_BASE + 0x00010000)
#define AHBPERIPH_BASE (PERIPH_BASE + 0x00020000)


/* GPIO 外設基地址*/
#define GPIOA_BASE (APB2PERIPH_BASE + 0x0800)
#define GPIOB_BASE (APB2PERIPH_BASE + 0x0C00)
#define GPIOC_BASE (APB2PERIPH_BASE + 0x1000)
#define GPIOD_BASE (APB2PERIPH_BASE + 0x1400)
#define GPIOE_BASE (APB2PERIPH_BASE + 0x1800)
#define GPIOF_BASE (APB2PERIPH_BASE + 0x1C00)
#define GPIOG_BASE (APB2PERIPH_BASE + 0x2000)


/* 暫存器基地址,以GPIOB 為例*/
#define GPIOB_CRL (GPIOB_BASE+0x00)
#define GPIOB_CRH (GPIOB_BASE+0x04)
#define GPIOB_IDR (GPIOB_BASE+0x08)
#define GPIOB_ODR (GPIOB_BASE+0x0C)
#define GPIOB_BSRR (GPIOB_BASE+0x10)
#define GPIOB_BRR (GPIOB_BASE+0x14)
#define GPIOB_LCKR (GPIOB_BASE+0x18)

首先定義了“片上外設”基地址PERIPH_BASE,接著在PERIPH_BASE 上加入各個匯流排的地址偏移,得到APB1、APB2 匯流排的地址APB1PERIPH_BASE、APB2PERIPH_BASE,在其之上加入外設地址的偏移,得到GPIOA-G 的外設地址,最後在外設地址上加入各暫存器的地址偏移,得到特定暫存器的地址。一旦有了具體地址,就可以用指標讀寫

重點看64頁

讓PB0輸出低/高電平,要怎麼實現?

#define PERIPH_BASE      ((unsigned int)0x40000000)
#define APB2PERIPH_BASE  (PERIPH_BASE + 0x00010000)
#define GPIOB_BASE       (APB2PERIPH_BASE + 0x0C00)
#define GPIOB_ODR        *(unsignedint*)(GPIOB_BASE+0x0C)//ODR的絕對地址

// PB0輸出輸出低電平(清零)
GPIOB_ODR &= ~(1<<0);//(1<<0)將1左移0位 0000 0000變成0000 0001,別的為0
/*
先計算左邊 0000 0001 取反後 1111 1110
再與原來的值0000 1000 按位與
結果 0000 0000
*/

// PB0輸出輸出高電平
GPIOB_ODR |= (1<<0);
/*
|=是相加的意思
原來0000 1000
相加0000 0001 先計算出1<<0的結果,再位相加
結果0000 1001 
*/

使用結構體封裝暫存器列表?

image

image

-> 和結構體的. 的作用是類似的, 都是對結構體成員的訪問, 可以見C結構體

image

這部分工作都由韌體庫幫我們完成了

新建暫存器(REG)模板

建立工程

image

工程命名--不要中文

image

選擇對應晶片型號

image

跳過軟體包

image

匯入啟動檔案,檔案路徑,在韌體庫中:

C:\MYDATA~1\網課資料\野火指~1\A盤(~1\1-程式~1\1-_野~1\1-書籍~1\0【固~1.0\STM32F~1.0\【韌體~1.0\LIBRAR~1\CMSIS\CM3\DEVICE~1\ST\STM32F~1\startup\arm\

根據Flash大小選擇對應啟動檔案:

image

image

複製啟動檔案到工程專案資料夾中

image

雙擊匯入檔案

image

記事本新建一個main.c檔案並匯入

image

設定程式碼大小

image

image

#include "stm32f10x.h"//<>是表示標頭檔案在軟體安裝根目錄下, ""表示先在工程目錄下尋找,再去軟體安裝根目錄尋找

int main (void)
{


}


void SystemInit(void)
{
	//編譯時, 會先在啟動檔案的彙編中初始化時鐘, 採用的插入方式. 函式體為空,目的是為了騙過編譯器不報錯
}

修改名稱

間斷雙擊target,重新命名

image

修改生成的目標工程檔名稱,存放在專案的objects資料夾中。

image

hex是透過串列埠下載的可執行檔案,

axf是透過編譯器下載的可執行檔案

image

**燒錄設定 ** 連線模擬器和微控制器 上電

image

image

image

image

然後編譯後,選擇download下載

這個是可以上相容的,即在同一核心版本下,F103,引腳少的工程專案在引腳多的晶片上可以直接燒錄,無需修改。因為引腳少的晶片的所有引腳在引腳多的晶片上都有。

移植工程,需要修改晶片型號,然後重新配置模擬器:

image

image

關閉C語言語法的動態檢查:

image

相關文章