stm32學習筆記——基本的地址操作
一、stm32的儲存器
一共4G大小,地址範圍0x0000 0000到0xFFFF FFFF,分成8個塊(Block0~Block7),每塊大小為512M。
其中片上外設地址(0x4000 0000)-(0x5FFFFFFF)
一共有3條匯流排:APB1、APB2、AHB。
匯流排名稱 | 匯流排基地址 |
---|---|
APB1 | 0x4000 0000 |
APB2 | 0x4001 0000 |
AHB | 0x4001 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韌體庫的寫法,瞭解一下就差不多了,如果是暫存器開發的話,還是用韌體庫的模板,直接用結構體成員指標找到對應的暫存器直接操作就好了,裡面的結構體都是韌體庫中定義好的。如果用庫函式的形式開發,根本用不上這些。
相關文章
- MySQL學習筆記--基本操作MySql筆記
- Mybatis學習筆記 2:Mybatis 基本的CURD操作MyBatis筆記
- Swift學習筆記(二十)——陣列的基本操作Swift筆記陣列
- STM32學習筆記——GPIO筆記
- Swift學習筆記(二十二)——字典的基本操作Swift筆記
- STM32學習筆記之中斷筆記
- STM32學習筆記——中斷筆記
- Stm32學習筆記(四)通用定時器基本原理筆記定時器
- 學習筆記【MySQL基礎操作-第一節:MySQL基本操作】筆記MySql
- vim學習筆記——三種基本模式和相關操作筆記模式
- NEO學習筆記,從WIF到地址筆記
- JavaScript學習筆記---基本語法JavaScript筆記
- OpenGL學習筆記(12)基本光照筆記
- 機器學習學習筆記——基本知識機器學習筆記
- 【kafka學習筆記】kafka的基本概念Kafka筆記
- oracle基本概念的學習筆記(轉)Oracle筆記
- Linux 學習筆記--目錄結構及檔案基本操作Linux筆記
- TS學習筆記(一):基本型別筆記型別
- 《PHP學習筆記——PHP基本語法》PHP筆記
- OCI介面學習筆記--基本理解(二)筆記
- 【numpy學習筆記】矩陣操作筆記矩陣
- ElasticSearch 學習筆記(一) 基本概念與基本使用Elasticsearch筆記
- STM32學習記錄(一):STM32概述
- numpy的學習筆記\pandas學習筆記筆記
- pandas之常用基本函式學習筆記函式筆記
- Java學習筆記之----------Java基本知識Java筆記
- vue學習筆記1-基本知識Vue筆記
- HTTP2基本概念學習筆記HTTP筆記
- OpenXml SDK學習筆記(1):Word的基本結構XML筆記
- 撤銷操作 —— Git 學習筆記 12Git筆記
- SQL學習筆記—非select操作SQL筆記
- spark學習筆記--RDD鍵對操作Spark筆記
- Python學習筆記-字串及操作Python筆記字串
- Rxjs TakeUntil 操作符的學習筆記JS筆記
- JavaScript中的物件學習筆記(屬性操作)JavaScript物件筆記
- Hive學習之基本操作Hive
- STM32學習筆記——Keil uVision5建專案筆記
- STM32學習記錄(九):RTC