stm32學習筆記——基本的地址操作

輕染QAQ發表於2020-12-21

一、stm32的儲存器
在這裡插入圖片描述
一共4G大小,地址範圍0x0000 0000到0xFFFF FFFF,分成8個塊(Block0~Block7),每塊大小為512M。
其中片上外設地址(0x4000 0000)-(0x5FFFFFFF)
在這裡插入圖片描述
在這裡插入圖片描述
一共有3條匯流排:APB1、APB2、AHB。

匯流排名稱匯流排基地址
APB10x4000 0000
APB20x4001 0000
AHB0x4001 8000

APB1、APB2和AHB都是在外設地址(0x4000 0000)偏移,分別偏移0x0,0x0001 0000
和0x0002 0000。
在這裡插入圖片描述
在這裡插入圖片描述
相應的暫存器,又是在掛載匯流排的地址上進行偏移的。

二、stm32最最底層的地址操作。
以PC13引腳輸出高低電平

int main(void{
	*unsigned int*0x4002 1018 |=(1<<4);//開啟時鐘
	*unsigned int*0x40011004 &= ~(0x0F<<(4*5));//對應位清零
    *unsigned int*0x40011004 |= (1<<20);//配置輸出模式
    *unsigned int*0x4001 100C&=~(1<<13)//配置輸出低電平
    *unsigned int*0x4001 100C|=(1<<13)//配置輸出高電平
}

根據微控制器的地址對映,查詢到相應匯流排地址,再去查詢相應的GPIO的地址(可直接查表,也可根據相對匯流排的地址去算偏移地址),在相應的GPIO下面,又有對應的暫存器的地址,最後就可以算出要操作的暫存器的地址。

找到相應的地址後,這個還只是一個數字,跟程式沒有關係,通過定義一個(unsigned int*)型別的指標,把這個數字轉換成微控制器能夠識別的相應的地址,再在這個地址前面加上*,如*(unsigned int*)0x4002 1018,就可以對它進行賦值了,*表示取地址的內容

下面的程式和上面的是一個意思,只是把相應的地址操作用#define替換成了相應的暫存器名字。

#define PERIPH_BASE   0x4000 0000
//片上外設基地址
#define  APB1PERIPH_BASE   PERIPH_BASE     // APB1匯流排地址
#define  APB2PERIPH_BASE   (PERIPH_BASE+0x00010000)  //APB2匯流排地址
#define  AHBPERIPH_BASE   (PERIPH_BASE+0x00020000)  //AHB匯流排地址

#define  RCC_BASE  (AHBPERIPH_BASE+0x1000)   //時鐘地址
#define  RCC_APB2ENR  *(unsigned int*)(RCC_BASE+0x18)  
//APB2外設時鐘使能暫存器地址

#define  GPIOC_BASE    (APB2PERIPH_BASE+0x00010000)   //GPIOC地址
//GPIOC相關暫存器的地址
#define GPIOC_CRL          *(unsigned int *)( GPIOC_BASE+0x00)
#define GPIOC_CRH          *(unsigned int *)( GPIOC_BASE+0x04)
#define GPIOC_IDR          *(unsigned int *)( GPIOC_BASE+0x08)
#define GPIOC_ODR          *(unsigned int *)( GPIOC_BASE+0x0C)
#define GPIOC_BSRR         *(unsigned int *)( GPIOC_BASE+0x10)
#define GPIOC_BRR          *(unsigned int *)( GPIOC_BASE+0x14)
#define GPIOC_LCKR         *(unsigned int *)( GPIOC_BASE+0x18)

這些巨集定義可直接定義在標頭檔案中。
以上實現過程都是先找到基地址,再經過偏移取找到相應的地址。
修改後就變成了下滿的可讀性強一點的程式碼了。

int main(void{
	RCC_APB2 |=(1<<4);//開啟時鐘
	GPIOC_CRH &= ~(0x0F<<(4*5));//配置輸出模式
    GPIOC_CRH |= (1<<20);
    GPIOC_ODR&=~(1<<13);//配置輸出低電平
    GPIOC_ODR|=(1<<13);//配置輸出高電平
}

三、結構體的方式把同一外設的暫存器歸類

stm32韌體庫中的外設基本上都是通過結構體的方式,把相應的外設的暫存器定義在一起的。

由於GPIOA—GPIOG有眾多組,每組有7個暫存器,如果每次都去找地址再用巨集替換,就會寫很多,實際上每個GPIO組,裡面的7個暫存器都是一樣的,所以可以直接把這七個暫存器定義在一起(採用結構體方式)。

typedef unsigned int uint32_t; //(4位元組)
typedef struct 
{
	uint32_t CRL;
	uint32_t CRH;
	uint32_t IDR;
	uint32_t ODR;
	uint32_t BSRR;
	uint32_t BRR;
	uint32_t LCKR;
}GPIO_TypeDef;

結構體中定義的變數都是連續排列的,恰好每個暫存器都是32位的,佔用4個位元組,這個就和每個暫存器在實際的儲存器映像中偏移量是一樣的了,看上面的結構體,我們只需要,定義一個結構體指標,讓他指向需要使用到的GPIO口的地址就可以了。就相當於給了一個入口地址,再用結構體指標,就很容易的引用其中的成員變數了。

#define  GPIOC  ((GPIO_TypeDef*)GPIOC_BASE)

這條語句中,GPIOC_BASE是之前用巨集定義過的GPIOC的地址(只是一個數值),前面加上一個(GPIO_TypeDef*)把他轉換為微控制器能夠識別的地址。這裡的GPIOC_BASE就相當於給這個結構體傳遞了一個GPIO口的地址,然後就可以很方便的引用GPIO口中的暫存器了。

上面這條語句的意思就是用GPIOC替換替換了 (GPIO_TypeDef*)型別的GPIOC的地址

如果要引用GPIOC中的暫存器,用結構體成員指標就ok。

GPIOC->CRH    
GPIOC->ODR

時鐘相關的暫存器定義也和GPIO口差不多

typedef struct 
{
	uint32_t CR;
	uint32_t CFGR;
	uint32_t CIR;
	uint32_t APB2RSTR;
	uint32_t APB1RSTR;
	uint32_t AHBENR;
	uint32_t APB2ENR;
	uint32_t APB1ENR;
	uint32_t BDCR;
	uint32_t CSR;
}RCC_TypeDef;
#define  RCC  ((RCC_TypeDef*)RCC_BASE)

然後是在相應的標頭檔案也應該進行更改

Stm32f10x.h

#define PERIPH_BASE           ((unsigned int)0x40000000) 
#define APB2PERIPH_BASE       (PERIPH_BASE + 0x10000)
/* AHB匯流排基地址 */
#define AHBPERIPH_BASE        (PERIPH_BASE + 0x20000)
/*GPIOC外設基地址*/
#define GPIOC_BASE            (APB2PERIPH_BASE + 0x1000)	
 /*RCC外設基地址*/
#define RCC_BASE      (AHBPERIPH_BASE + 0x1000)
typedef unsigned int uint32_t;
typedef struct 
{
	uint32_t CRL;
	uint32_t CRH;
	uint32_t IDR;
	uint32_t ODR;
	uint32_t BSRR;
	uint32_t BRR;
	uint32_t LCKR;
}GPIO_TypeDef;

typedef struct 
{
	uint32_t CR;
	uint32_t CFGR;
	uint32_t CIR;
	uint32_t APB2RSTR;
	uint32_t APB1RSTR;
	uint32_t AHBENR;
	uint32_t APB2ENR;
	uint32_t APB1ENR;
	uint32_t BDCR;
	uint32_t CSR;
}RCC_TypeDef;

#define  GPIOC  ((GPIO_TypeDef*)GPIOC_BASE)
#define  RCC  ((RCC_TypeDef*)RCC_BASE)

四、編寫標頭檔案
在這裡插入圖片描述
說明:BSRR暫存器,低16位是用來置1的,高16位是用來置0的。
都是1有效,0不對相應的位產生影響。
在這裡插入圖片描述
說明:BRR暫存器是用來置0,高16位不用,低16位置0,高電平有效

標頭檔案中,一般寫入 巨集替換 函式的宣告 結構體型別等
比如要定義一個控制GPIO的標頭檔案 stm32f10x_gpio.h

#ifndef  __STM32F10X_GPIO_H
#define  __STM32F10X_GPIO_H    //標頭檔案中宣告標頭檔案的格式

#include "stm32f10x.h"   //引用的另一個標頭檔案

#define GPIO_Pin_0  ((uint16_t)0x0001)//二進位制:0b0000 0001
#define GPIO_Pin_1  ((uint16_t)0x0002)//二進位制:0b0000 0010
#define GPIO_Pin_2  ((uint16_t)0x0004)//二進位制:0b0000 0100
#define GPIO_Pin_3  ((uint16_t)0x0008)
#define GPIO_Pin_4  ((uint16_t)0x0010)
#define GPIO_Pin_5  ((uint16_t)0x0020)
#define GPIO_Pin_6  ((uint16_t)0x0040)
#define GPIO_Pin_7  ((uint16_t)0x0080)
#define GPIO_Pin_8  ((uint16_t)0x0100)
#define GPIO_Pin_9  ((uint16_t)0x0200)
#define GPIO_Pin_10  ((uint16_t)0x0400)
#define GPIO_Pin_11  ((uint16_t)0x0800)
#define GPIO_Pin_12  ((uint16_t)0x1000)
#define GPIO_Pin_13  ((uint16_t)0x2000)
#define GPIO_Pin_14  ((uint16_t)0x4000)
#define GPIO_Pin_15  ((uint16_t)0x8000)
#define GPIO_Pin_all  ((uint16_t)0xFFFF)

void GPIO_SetBits(GPIO_TypeDef * GPIOx,uint16_t GPIO_Pin);
void GPIO_ResetBits(GPIO_TypeDef * GPIOx,uint16_t GPIO_Pin);

#endif  //第一行,第二行,最後一行都是固定格式

其中宣告瞭兩個函式在stm32f10x_gpio.c中

#include "stm32f10x_gpio.h"   
void GPIO_SetBits(GPIO_TypeDef *GPIOx,uint16_t GPIO_Pin)
{
    GPIOx->BSRR |= GPIO_Pin;  //用BSRR暫存器置1
}
void GPIO_ResetBits(GPIO_TypeDef *GPIOx,uint16_t GPIO_Pin)
{
     GPIOx->BRR |= GPIO_Pin;  //用BRR暫存器置0
}

定義這兩個函式之後,就可以直接呼叫了。

GPIO_ResetBits(GPIOC,GPIO_Pin_0);

這個函式中GPIOC就是之前講過的用巨集替換成了一個結構體指標了。

#define  GPIOC  ((GPIO_TypeDef*)GPIOC_BASE)

GPIO_Pin_0是用二進位制碼在標頭檔案中定義過了的。

stm32f10x_gpio.h中呼叫的一個標頭檔案
Stm32f10x.h

#ifndef  __STM32F10X_H
#define __STM32F10X_H

#define PERIPH_BASE           ((unsigned int)0x40000000)

#define APB2PERIPH_BASE       (PERIPH_BASE + 0x10000)
/* AHB匯流排基地址 */
#define AHBPERIPH_BASE        (PERIPH_BASE + 0x20000)

/*GPIOC外設基地址*/
#define GPIOC_BASE            (APB2PERIPH_BASE + 0x1000)	
	
/*RCC外設基地址*/
#define RCC_BASE      (AHBPERIPH_BASE + 0x1000)

typedef unsigned int uint32_t; 	
typedef unsigned short uint16_t;
typedef struct 
{
	uint32_t CRL;
	uint32_t CRH;
	uint32_t IDR;
	uint32_t ODR;
	uint32_t BSRR;
	uint32_t BRR;
	uint32_t LCKR;
}GPIO_TypeDef;

typedef struct 
{
	uint32_t CR;
	uint32_t CFGR;
	uint32_t CIR;
	uint32_t APB2RSTR;
	uint32_t APB1RSTR;
	uint32_t AHBENR;
	uint32_t APB2ENR;
	uint32_t APB1ENR;
	uint32_t BDCR;
	uint32_t CSR;
}RCC_TypeDef;

#define  GPIOC  ((GPIO_TypeDef*)GPIOC_BASE)
#define  RCC  ((RCC_TypeDef*)RCC_BASE)

#endif

整個主程式

#include "stm32f10x.h"
#include "stm32f10x_gpio.h"
int main(void)
{
	RCC->APB2ENR |= 1<<4;
	GPIOC->CRH &=~(0x0F<<(4*5));
	GPIOC->CRH |=(1<<(4*5));
	GPIO_ResetBits(GPIOC,GPIO_Pin_0);	
	while(1);
}
void SystemInit()
{
}

這篇文章主要是幫助理解stm32韌體庫的寫法,瞭解一下就差不多了,如果是暫存器開發的話,還是用韌體庫的模板,直接用結構體成員指標找到對應的暫存器直接操作就好了,裡面的結構體都是韌體庫中定義好的。如果用庫函式的形式開發,根本用不上這些。

相關文章