STM32寄存器深入分析
可能很多剛開始學習STM32的小夥伴都有一個疑惑,創建項目時會需要很多頭文件,導致學習過程中很難明白那些頭文件的作用,雖然知道頭文件都是對寄存器的封裝,但是怎麼封裝的就不知道了。這裡我以led燈為試驗,不需要頭文件,自己跟着寄存器的說明寫一個簡單的demo,應該能加深小夥伴們對STM32的理解。
一、有效地址
C語言功底相對差一些的小夥伴可能看不明白「STM32的寄存器手冊」,不明白手冊中的地址說明是什麼,比如手冊中的兩個寄存器,他們的偏移地址都是0x00,這樣直接給0x00這個寄存器直接複製是不行的。
到這裡我們得明白有效地址這個概念,我們操作寄存器的時候,都是操作的寄存器的有效地址,而有效地址等於基地址加偏移地址。
- 有效地址 = 基地址 + 偏移地址
對有效地址還有疑問的小夥伴可以參考偏移地址的理解
二、時鐘系統(RCC)與 GPIO 的有效地址
想要知道STM的相關外設的有效地址,那麼需要了解一些STM32的系統架構
注意:代碼區始終從地址0x0000 0000開始(通過ICode和DCode總線訪問)
從圖中可知,外設的有效地址都是在系統外設總線的地址上進行偏移的,我們可以通過STM32提供的庫文件得知相關寄存器的地址,也可以通過「STM32的寄存器手冊」獲取相關外設的地址。
從圖中可知GPIB的有效地址是0x40010C00,RCC的有效地址是0x40021000
- GPIB = 0x40000000 + 0x10000 + 0xC00 = 0x40010C00
- GPIB = 0x40000000 + 0x20000 + 0x1000 = 0x40021000
除了這樣計算之外,還可以通過「STM32的寄存器手冊」直接查看即可
現在就可以通過「STM32的寄存器手冊」提供的偏移地址定義我們要使用的變量,當然也可以參考我之前的STM32時鐘系統的配置寄存器和源碼分析
#define RCC_BASE ((uint32_t)0x40021000)
#define GPIOB_BASE ((uint32_t)0x40010C00)
#define FLASH_ACR ((uint32_t *)0x40022000)
#define GPIOB_CRH ((uint32_t *)(GPIOB_BASE + 0x04))
#define GPIOB_ODR ((uint32_t *)(GPIOB_BASE + 0x0C))
#define RCC_CR ((uint32_t *)(RCC_BASE + 0x00))
#define RCC_CFGR ((uint32_t *)(RCC_BASE + 0x04))
#define RCC_CIR ((uint32_t *)(RCC_BASE + 0x08))
#define RCC_APB2RSTR ((uint32_t *)(RCC_BASE + 0x0C))
#define RCC_APB1RSTR ((uint32_t *)(RCC_BASE + 0x10))
#define RCC_AHBENR ((uint32_t *)(RCC_BASE + 0x14))
#define RCC_APB2ENR ((uint32_t *)(RCC_BASE + 0x18))
#define RCC_APB1ENR ((uint32_t *)(RCC_BASE + 0x1C))
三、初始化時鐘系統
- 把所有時鐘系統複位
/*------------------------------------------------------------
把所有時鐘寄存器複位
------------------------------------------------------------*/
void RCC_DeInit(void)
{
*RCC_APB2RSTR = 0x00000000;//外設複位
*RCC_APB1RSTR = 0x00000000;
*RCC_AHBENR = 0x00000014; //flash時鐘,閃存時鐘使能.DMA時鐘關閉
*RCC_APB2ENR = 0x00000000; //外設時鐘關閉.
*RCC_APB1ENR = 0x00000000;
*RCC_CR |= 0x00000001; //使能內部高速時鐘HSION
*RCC_CFGR &= 0xF8FF0000; //複位SW[1:0],HPRE[3:0],PPRE1[2:0],PPRE2[2:0],ADCPRE[1:0],MCO[2:0]
*RCC_CR &= 0xFEF6FFFF; //複位HSEON,CSSON,PLLON
*RCC_CR &= 0xFFFBFFFF; //複位HSEBYP
*RCC_CFGR &= 0xFF80FFFF; //複位PLLSRC, PLLXTPRE, PLLMUL[3:0] and USBPRE
*RCC_CIR = 0x009F0000; //關閉所有中斷
}
- 通過8MHz的外部時鐘配置72MHz的系統時鐘
/*------------------------------------------------------------
外部8M,則得到72M的系統時鐘
------------------------------------------------------------*/
void Stm32_Clock_Init(void)
{
unsigned char temp=0;
u8 timeout=0;
RCC_DeInit();
RCC_CR|=0x00010000; //外部高速時鐘使能HSEON
timeout=0;
while(!(RCC_CR>>17)&&timeout<200)timeout++;//等待外部時鐘就緒
//0-24M 等待0;24-48M 等待1;48-72M等待2;(非常重要!)
FLASH_ACR|=0x32;//FLASH 2個延時周期
RCC_CFGR|=0X001D2400;//APB1/2=DIV2;AHB=DIV1;PLL=9*CLK;HSE作為PLL時鐘源
RCC_CR|=0x01000000; //PLLON
timeout=0;
while(!(RCC_CR>>25)&&timeout<200)timeout++;//等待PLL鎖定
RCC_CFGR|=0x00000002;//PLL作為系統時鐘
while(temp!=0x02&&timeout<200) //等待PLL作為系統時鐘設置成功
{
temp=RCC->CFGR>>2;
timeout++;
temp&=0x03;
}
}
- 程序我就不過多介紹了,這裡相對比較簡單,有感興趣的小夥伴可以通過寄存器對照一下就明白了,或者參考STM32時鐘系統的配置寄存器和源碼分析
四、程序源碼
main.c文件
typedef unsigned int uint32_t;
typedef unsigned char uint8_t;
#define CLOCK 72/8 //時鐘=72M
#define RCC_BASE ((uint32_t)0x40021000)
#define GPIOB_BASE ((uint32_t)0x40010C00)
#define FLASH_ACR ((uint32_t *)0x40022000)
#define GPIOB_CRH ((uint32_t *)(GPIOB_BASE + 0x04))
#define GPIOB_ODR ((uint32_t *)(GPIOB_BASE + 0x0C))
#define RCC_CR ((uint32_t *)(RCC_BASE + 0x00))
#define RCC_CFGR ((uint32_t *)(RCC_BASE + 0x04))
#define RCC_CIR ((uint32_t *)(RCC_BASE + 0x08))
#define RCC_APB2RSTR ((uint32_t *)(RCC_BASE + 0x0C))
#define RCC_APB1RSTR ((uint32_t *)(RCC_BASE + 0x10))
#define RCC_AHBENR ((uint32_t *)(RCC_BASE + 0x14))
#define RCC_APB2ENR ((uint32_t *)(RCC_BASE + 0x18))
#define RCC_APB1ENR ((uint32_t *)(RCC_BASE + 0x1C))
/*------------------------------------------------------------
把所有時鐘寄存器複位
------------------------------------------------------------*/
void RCC_DeInit1(void)
{
*RCC_APB2RSTR = 0x00000000;//外設複位
*RCC_APB1RSTR = 0x00000000;
*RCC_AHBENR = 0x00000014; //flash時鐘,閃存時鐘使能.DMA時鐘關閉
*RCC_APB2ENR = 0x00000000; //外設時鐘關閉.
*RCC_APB1ENR = 0x00000000;
*RCC_CR |= 0x00000001; //使能內部高速時鐘HSION
*RCC_CFGR &= 0xF8FF0000; //複位SW[1:0],HPRE[3:0],PPRE1[2:0],PPRE2[2:0],ADCPRE[1:0],MCO[2:0]
*RCC_CR &= 0xFEF6FFFF; //複位HSEON,CSSON,PLLON
*RCC_CR &= 0xFFFBFFFF; //複位HSEBYP
*RCC_CFGR &= 0xFF80FFFF; //複位PLLSRC, PLLXTPRE, PLLMUL[3:0] and USBPRE
*RCC_CIR = 0x009F0000; //關閉所有中斷
}
void Stm32_Clock_Init1(void)
{
unsigned char temp=0;
uint8_t timeout=0;
RCC_DeInit1();
*RCC_CR|=0x00010000; //外部高速時鐘使能HSEON
timeout=0;
while(!(*RCC_CR>>17)&&timeout<200)timeout++;//等待外部時鐘就緒
//0-24M 等待0;24-48M 等待1;48-72M等待2;(非常重要!)
*FLASH_ACR|=0x32;//FLASH 2個延時周期
*RCC_CFGR|=0X001D2400;//APB1/2=DIV2;AHB=DIV1;PLL=9*CLK;HSE作為PLL時鐘源
*RCC_CR|=0x01000000; //PLLON
timeout=0;
while(!(*RCC_CR>>25)&&timeout<200)timeout++;//等待PLL鎖定
*RCC_CFGR|=0x00000002;//PLL作為系統時鐘
while(temp!=0x02&&timeout<200) //等待PLL作為系統時鐘設置成功
{
temp = *RCC_CFGR>>2;
timeout++;
temp&=0x03;
}
}
/*------------------------------------------------------------
us延時函數
------------------------------------------------------------*/
void delay_us(unsigned int us)
{
uint8_t n;
while(us--)for(n=0;n<CLOCK;n++);
}
/*------------------------------------------------------------
ms延時函數
------------------------------------------------------------*/
void delay_ms(unsigned int ms)
{
while(ms--)delay_us(1000);
}
/*------------------------------------------------------------
主函數
------------------------------------------------------------*/
int main()
{
Stm32_Clock_Init1();
*RCC_APB2ENR|=0X0000001c;//先使能外設IO PORTa,b,c時鐘
*RCC_APB2ENR |= 1 << 12;
*GPIOB_CRH = 0X00030000; //設置GPIOB的12引腳為推挽輸出
while (1)
{
delay_ms(1000);
//GPIOB->ODR = ~(1 << 12); //設置12引腳輸出0
*GPIOB_ODR = ~(1 << 12);
delay_ms(1000);
//GPIOB->ODR |= 1 << 12; //設置12引腳輸出1
*GPIOB_ODR |= 1 << 12;
}
}
五、測試
由於沒有使用任何庫文件,所以創建項目就比較簡單了,我就不在進行演示了,有知道怎麼創建項目的小伙可以瀏覽我之前的STM32新建模板之庫文件和STM32新建模板之寄存器
這裡只需要使用startup_stm32f10x_hd.s啟動文件和main.c文件即可
筆記到這裡就完成了,相信到這裡的小夥伴對STM32的庫文件都有一定的了解,也知道怎麼去學習,接下載就進入實戰學習了,通過編寫不同的外設來提升自己對庫文件的了解,如果那些寫得不好的忘大家指出。
參考文獻
偏移地址的理解://www.jianshu.com/p/9704c5e758bf