作者:junziyang
數字系統中,指令的執行以時鐘周期為基本時間單位。常用時鐘周期的倒數來表征處理信息的快慢,稱為時鐘頻率。比如主頻為72MHz的MCU,其最短時鐘周期是1/72微秒。STM32集成了大多數主流的外設(Peripheral),如USART、DMA、ADC等。不同的外設所需的時鐘頻率是不同的。為了指揮(同步)不同外設的工作,STM32內建了一套時鐘系統。通過配置專門的寄存器,可以對時鐘源和時鐘頻率進行設置,並通過總線架構,實現向不同外設(組)的時鐘分配。除了配置時鐘頻率,時鐘控制系統的另一個作用是降低功耗。由於外設眾多,為了降低功耗,復位後外設驅動器的時鐘一般都是關閉的,啟用外設前需要通過時鐘控制系統主動使能其時鐘。
了解STM32的系統架構和時鐘系統,可從宏觀上理解STM32工作原理,掌握不同外設時鐘的配置和初始化方法,加深對底層硬件和程序基本原理的理解。
註:如非特別聲明,以下筆記內容均針對STM32F103ZET6而言。不同型號,細節可能存在差別。
一、STM32的系統架構
熟悉MCU的系統架構,對理解外設和時鐘系統大有裨益。圖1所示為MCU功能框圖。可以看到芯片內部是十分復雜的,包含多個功能模塊。我們先分區域來看一下整個芯片的功能佈局。
![](https://news.xinpengboligang.com/upload/keji/48c2e149b11d5000a83429cba23323a7.jpeg)
圖1. STM32系統架構框圖。
1.1 電源和時鐘源
右上角主要是電源和時鐘源,負責給芯片供電和提供時鐘信號。粉紅色填充的4個模塊是時鐘源,它們產生的時鐘信號通過RCC(Reset and Clock Control,復位與時鐘控制)模塊加載到AHB(Advanced High-performance Bus)總線上。
1.2 驅動模塊
左上角橙色填充的模塊為整個MCU的驅動(Master)模塊,包括:
- Cortex-M3核心:中間為CPU,上方為調試接口,下方為非線性向量中斷控制器NVIC(Nonlinear Vector Interupt Controller)。
- DMA(Direct Memory Access)控制器:DMA1為7通道,DMA2為5通道。
- 總線(Bus):負責數據和控制信號的傳遞。
DMA專門負責處理不同速度的外設對存儲器的訪問請求和數據搬運。典型的應用就是將數據從一個地址區間復制到另外一個地址區間。 經DMA請求,CPU把總線控制權交給DMA控制器,傳輸過程由 DMA 控制器來實行和完成,無需CPU主動幹預,因此可以降低CPU的負擔,提高效率。DMA傳輸結束後,總線控制權再交回給CPU。一個完整的DMA傳輸過程包括DMA請求、DMA響應、DMA傳輸、DMA結束4個步驟。
總線是數據和指令的傳輸通道。芯片中有多條總線,各自負責不同的信息傳遞功能:
- ICode bus:Instruction Code bus,指令碼總線,連接內核的ICode總線與閃存指令接口(FLITF,FLash InTerFace),負責從閃存(預)取指令。
- DCode bus:Data Code bus,連接內核的DCode總線與總線矩陣,然後連接到閃存數據接口,負責數據傳輸(常量加載、調試訪問等)。
- System bus:系統總線,連接內核的系統總線(外設總線)與總線矩陣。
- DMA bus:DMA總線,連接DMA與總線矩陣。
可以看出,除了ICode總線是專線外,其餘總線均連接到總線矩陣(Bus Matrix)。與Bus Matrix相連的總線有4進4出,分別鏈接左側的驅動模塊和右側的從動模塊,負責信息的上傳下達。由於是一對多的服務,該模塊采用了一種稱為輪詢調度的算法(Round Robin algorithm),來均衡各方需求。
1.3 從動模塊
從動模塊(Slave)又稱被動模塊,是不能發號施令而隻能唯命是從的模塊。Bus Matrix右側的總線從上到下來看,依次去往Flash、SRAM(Static Random-Access Memory, 靜態隨機存取存儲器)、FSMC(Flexible Static Memory Controller,可變靜態存儲控制器,用於內存擴展)和AHB 系統總線。
AHB總線又通過兩個AHB/APB橋,同步連接到兩個APB(Advanced Peripheral Bus)高級外設總線。所有外設都掛載在這兩個外設總線上,其中APB2是全速外設總線,最高速度與AHB相同;而APB1是限速總線,最高速度通常隻有AHB的一半。以STM32F103ZET6為例,APB2的速度可達全速72MHz,而APB1最高限速為36MHz。
另外,SDIO (Security Digital Input/Output,安全數字輸入輸出)和RCC (Reset & Clock Control)直接掛載在AHB總線上。DMA1和DMA2經Bus Matrix實際上也是與AHB總線相連。RCC通過AHB總線,為整個系統提供復位和時鐘控制服務。
註意:
- 系統復位後,所有外設的時鐘都是關閉的,因此使用外設前必須先使能其對應的時鐘。AHB總線下方的外設通過設置RCC_AHBENR寄存器使能時鐘,APB1和APB2總線下的外設則分別通過設置RCC_APB1ENR和 RCC_APB2ENR寄存器使能時鐘。AHB總線上方的SRAM和FLITF的時鐘不受系統復位的影響。
- 不同的芯片型號支持的外設種類和數量可能會有些不同。穩妥起見建議查閱《Reference Manual》-Memory organization章節來確定外設對應的總線及關聯的寄存器。
二、STM32的時鐘系統
再高端的MCU離開了時鐘的同步也無法正常工作。就像人一樣,沒有了心跳,再好的身體條件也變得毫無意義。時鐘系統就是MCU的心臟,RCC就是控制芯片內外各部分協調工作的總指揮。由於支持的外設眾多,為了適應不同外設對時鐘頻率的需求,MCU內部構建了一套復雜的時鐘系統,可以通過配置相關寄存器,來調配時鐘頻率、使能和復位外設時鐘。我們先通過STM32CubeIDE或STM32CubeMX中的時鐘配置圖,從宏觀上來了解一下時鐘系統的結構,然後再學習相關的寄存器和時鐘設置操作。
2.1 STM32的時鐘源
在STM322CubeIDE或STM32CubeMX中,可以通過圖形界面,以交互式的方式來配置時鐘。圖2所示即為STM32CubeMx中STM32F103ZET6的Clock Configuration界面。我們藉此來來分析一下時鐘系統整體架構。
![](https://news.xinpengboligang.com/upload/keji/063b5edd0eb69ef11656c13628e7e715.jpeg)
圖2. STM32F103ZET6的時鐘配置框圖
圖2中所示的時鐘系統可分為三個模塊:紅色虛線框標示的為低速時鐘區、綠色虛線框標示的為主時鐘輸出區、兩個框之外的部分為高速時鐘區。系統共有4個時鐘源,即LSI、LSE、HSI、HSE。這4個時鐘源,若按時鐘來源,可分為:
- LSI和HSI為內部時鐘源,二者均為RC振蕩源。由於RC時鐘的精度較差,因此這兩個內部時鐘一般用於對時間精度要求不高的場合或在外部時鐘出現故障時作為備用應急時鐘。
- LSE和HSE為外部時鐘源,一般為高精度石英或陶瓷振蕩器。
若按照時鐘速度,可分為:
- LSI和LSE為低速時鐘:LSI RC頻率約40kHz,LSE的頻率為32.678kHz(2^15)。低速時鐘主要用於實時時鐘RTC(Real-Time Clock)和獨立看門狗IWDG(Independent WachDog)。
- HSI和HSE為高速時鐘:HSI RC頻率約8MHz,HSE的頻率為4-16MHz范圍可選。高速時鐘是系統的主時鐘源,這兩個時鐘源根據需要經過倍頻和分頻產生系統時鐘SYSCLK(SYStem CLocK)。
- PLL是個鎖相環倍頻器(Phase-locking loop),可以將輸入頻率提高2-16倍,但不能超過系統最高主頻。它的時鐘來源可以是HSI和HSE。
圖中數字框中帶除號的為分頻器;帶乘號的為倍頻器;藍色邊框的編輯框可輸入數字,右擊可鎖定/解鎖設置好的值。梯形圖標是多路選擇器,可按需要將某一路選通。從圖中部System Clock Mux多路選擇器輸入端可以看成出,SYSCLK有3個時鐘來源:HSI、HSE,以及PLL。SYSCLK經AHB預分頻器分頻,產生的HCLK即為AHB總線時鐘(HCLK中的H取的是AHB中的H)。HCLK再經後面的APB分頻器分頻,產生PCLK1和PCLK2;兩個PCLK還可以再次分頻,產生外設所需的頻率。應該說明的是,並非時鐘頻率越高越好,原因有二:時鐘頻率越高能耗越高;頻率越高電磁輻射越嚴重(電磁感應),可能會增加外圍電路設計的難度。所以,在滿足應用需求的情況下,應盡可能選擇較低的時鐘頻率。
圖2所下角的MCO(Master Clock Output)為主時鐘輸出端口,可將主時鐘輸出,用於時鐘監控或外部電路同步等。MCO通過選通器可以從4個來源進行選擇,但最高輸出頻率不高於36MHz。
2.2 RCC寄存器
時鐘源的選擇和開關、多路選擇器的選通、分頻/倍頻系數的設置、時鐘的鎖定和復位等等,均是通過配置相應的RCC寄存器來實現的。STM32F103ZET6共有10個RCC寄存器,圖3所示為這些寄存器的地址映射和復位值表。可以看出,RCC寄存器均為32位寄存器。與GPIO寄存器隻能按字訪問不同,RCC寄存器大都可以按字、字節或位訪問。
![](https://news.xinpengboligang.com/upload/keji/1e961548fd95ff6b1a483f7bfe01278e.jpeg)
圖3. RCC寄存器地址映射與復位值
說明:
- 絕大多數可配置功能或外設的復位值為0,使用前需要主動配置寄存器進行使能。
- 最上方的RCC_CR寄存器有3個位的復位值是1。說明HSI是系統復位後的默認時鐘。HSITRIM是用來校準HSI的修飾位。
- 中部的RCC_AHBENR寄存器有2個位的復位值為1。說明復位後FLITF和SRAM的時鐘是默認開啟的。
- 最下方RCC_CSR寄存器有2個位的復位值為1。PORRSETF和PINRSTF是上電復位和外部引腳復位的標志。
- 黃色背景標示的是一些標志位(Flag),一般由硬件來設置。
下面分別介紹各個RCC寄存器的功能。RCC寄存器配置的實際上是圖2所示的時鐘系統中相關部分的選通和設值。若計算機上安裝了STM32CubeIDE或STM32CubeMx,可以新建一個項目,切換到Clock Configuration界面,對照著學習。
1. RCC-CR
RCC-CR為時鐘控制寄存器(Clock Control Register),用以控制時鐘源及查詢時鐘源狀態的寄存器。如前所述,系統時鐘有3個來源:HSI、HSE和PLL。將圖2中的相關部分放大,如圖4所示。
![](https://news.xinpengboligang.com/upload/keji/2fa92f988e8bcfc3bdd008065f3fafb2.jpeg)
圖4. 系統時鐘源構成
RCC-CR寄存器配位表如圖5所示。
![](https://news.xinpengboligang.com/upload/keji/bb279ab256fa959f1b484faad9c76133.jpeg)
圖5. RCC-CR寄存器配位表
低16位用來配置HSI時鐘:
- HSION,1使能,0關閉;
- HSIRDY,標志位,時鐘是否就緒(ReaDY);
- HSITRIM,對電壓和溫度影響進行修飾(trimming),提高時鐘精度;
- HSICAL,用於時鐘校準(calibration)。
高16位用來配置HSE和PLL:
- HSEON,開/關時鐘;
- HSERDY,標志位,時鐘是否就緒;
- HSEBYP,時鐘旁路(bypass),外接自定義時鐘(方波、正弦、三角波等)須旁路HSE;
- CSSON,開/關時鐘安全系統(Clock security system);
- PLLON,開/關PLL;PLLRDY,標志位,PLL是否就緒。
說明:
- HSI是RC時鐘,環境參數變化(如電壓和溫度)會對時鐘精度造成影響。RCC-CR寄存器設置了校準和溫度/電壓修飾位,可根據需要設置這些位,以提高時鐘精度。ST出廠校正精度為1%@25℃,復位後HSICAL和HSITRIM恢復出廠設置。
- CSS用來確保HSE時鐘的安全運行。開啟CSS後,如果HSE出現故障,CSS會觸發一個NMI(Non-Maskable Interrupt)中斷,自動將時鐘切換到HSI。如果原時鐘源是HSE PLL,PLL也會被關閉。
- 從圖3可以看出,復位後HSION的值為1,可見HSI是復位後默認的系統時鐘,使用其他時鐘源必須主動開啟。
2. RCC-CFGR
RCC-CFGR為時鐘配置寄存器(Clock Configure Register),用以配置圖2中所示的多路選擇器的選通、分頻器/倍頻器系數的寄存器。RCC-CFGR寄存器配位表如圖6所示。CubeMX中的對應配置區域參考圖7。
![](https://news.xinpengboligang.com/upload/keji/6c051a9e3becfa90467fbcd3fb247970.jpeg)
圖6. RCC-CFGR寄存器配位表
![](https://news.xinpengboligang.com/upload/keji/8f7ea8f282d19b923ab465843e4b6a80.jpeg)
圖7. STM32CubeMX中AHB和APB總線的時鐘配置
SW(Systme clock sWitch),配置系統時鐘選通器(圖4中System Clock Mux)。配位邏輯:00 - HSI;01 - HSE;10 - PLL;11 - 禁用。STM32CubeMX中,選通器通過選中圖標上的單選框進行配置(如圖中4所示)。
- SWS(System clock sWitch Status),系統時鐘狀態標志位。配位邏輯與SW相同。
- HPRE(AHB prescaler),設置AHB主線預分頻系數。系統時鐘SYSCLK經AHB Prescaler分頻,產生AHB總線時鐘HCLK(參考圖4和圖7)。配位邏輯:0xxx - 1(不分頻);1000 - 2;1001 - 4;1010 - 8;1011 - 16:1100 - 64;1101 - 128;1110 - 256;1111 - 512。CubeMX中,分頻/倍頻系數通過下拉列表進行選擇(如圖4中所示)。
- PPRE1和PPRE2 (APB prescaler),分別設置APB1和APB2的預分頻系數。HCLK分別經APB1 Prescaler和APB2 Prescaler分頻,產生外設總線時鐘PCLK1和PCLK2(參考圖7)。配位邏輯:0xx-1(不分頻);100 - 2;101 - 4;110 - 8;111 - 16。
- ADCPRE(ADC prescaler),設置ADC預分頻系數。ADC的時鐘由PCLK2經ADC Prescaler進一步分頻得到(參考圖7)。配位邏輯:00 - 2;01 - 4;10 - 6;11 - 8。
- PLLSRC(PLL source),配置PLL時鐘選通器(圖4中PLL Source Mux)。配位邏輯:0 - HSI2分頻;1 - HSE。
- PLLXTPRE(PLL eXTernal clock prescaler):設置PLL外部時鐘源(即HSE)預分頻系數 (參考圖4,HSE與PLL Source Mux間的分頻器)。配位邏輯:0 - HSE不分頻;1 - HSE2分頻。
- PLLMUL(PLL multiplication factor):設置PLL倍頻系數(參考圖4,*PLLMul)。根據時鐘源頻率設置合適的倍頻系數,但最高輸出頻率不能高於芯片主頻。配位邏輯:從0000遞增到1110分別對應2-16倍,1111也對應16倍。
- USBPRE(USB prescaler):設置USB預分頻系數。PLLCLK經USB Prescaler分頻產生48MHz的USB時鐘(參考圖4,右下角)。配位邏輯:0 - 1.5分頻,PLLCLK為72MHz時對應48MHz;1 - 不分頻。
- MCO:配置主時鐘輸出選通器(參考圖2左下角)。配位邏輯:0xx-關閉MCO;100 - 輸出SYSCLK;101 - 輸出HSI;110 - 輸出HSE;111 - 輸出PLL的2分頻。
3. RCC_CIR
RCC_CIR為時鐘中斷寄存器(Clock Interupt Register),用於管理時鐘相關的中斷。CC-CIR寄存器配位表如圖8所示。
![](https://news.xinpengboligang.com/upload/keji/bcbcd1f79c4f22cf5a2e5e7ee8ab15ac.jpeg)
圖8. RCC-CIR寄存器配位表
按功能可以分為3組:
- 以E結尾的(8-12位)為時鐘就緒(事件)中斷使能位(Enable),可讀可寫。設為1則對應的時鐘穩定下來後會觸發RDY中斷;設為0則關閉中斷。
- 以F結尾的(1-4,7位)為時鐘就緒標志位(Flag),隻可讀。如果某時鐘設置並觸發了RDY中斷,則由硬件將對應的位設為1。如果開啟了CSS,HSE發生故障時會觸發中斷,設置CSSF位。
- 以C結尾的(16-20,23位)為時鐘就緒中斷清除位(Clear),隻可寫。與事件標志位是一一對應的,將某位設為1,可以將對應的標志位清除;設為0,則無影響。中斷處理完成後,一般要主動清除標志位。
可以看出,RCC相關的事件有6個,其中5個事件可按需開啟中斷,而CSS中斷隻要開啟CSS就會自動開啟。
4. RCC_APB2ENR和RCC_APB2RSTR
從圖3可以看出,與APB2總線上的外設相關的RCC寄存器有兩個,分別為RCC_APB2ENR和RCC_APB2RSTR,前者稱為APB2外設時鐘使能寄存器(APB2 peripheral enable register),後者稱為APB2外設復位寄存器(APB2 peripheral reset register)。兩個寄存器的功能位是一一對應的。 RCC_APB2ENR和RCC_APB2RSTR的配位表分別如圖9和圖10所示。
![](https://news.xinpengboligang.com/upload/keji/870d637b1af04cbbf7ff893cebc39d33.jpeg)
圖9. RCC-APB2EN寄存器配位表
![](https://news.xinpengboligang.com/upload/keji/5b6a1e65cc053c94e1b5fa32b73b652b.jpeg)
圖10. RCC-APB2RSTR寄存器配位表
每個位對應一個外設端口,對應的外設從表格中的名稱可以一目了然,毋庸贅言(IOP意為I/O port,即GPIO)。向RCC_APB2ENR的某位寫入1,可以使能對應的外設時鐘,寫入0則關閉對應外設的時鐘;向RCC_APB2RST的某位寫入1,則可以將對應的外設復位,寫入0無影響。
註意:
- 從圖2中可以看出,兩個寄存器的復位值均為0,說明系統復位後所有APB2上的外設時鐘都是關閉的,因此使用外設前必須主動使能對應的時鐘。
- 這兩個寄存器與前面學習過的GPIO的BRR和BSRR寄存器有本質的區別,BRR和BSRR是分別將ODR的對應位置0和置1。而這裡的APB2ENR是使能外設的時鐘,而APB2RSTR是將外設復位,即將外設的所有寄存器的值設為復位值表中的值。例如,將RCC_APB2RST中的IOPARST位置1,則GPIOA所有的寄存器被復位為默認值。
5. RCC_APB1ENR和RCC_APB1RSTR
與APB1總線上的外設相關的RCC寄存器也有兩個,分別為RCCAPB1ENR和RCC_APB1RSTR,配位表分別如圖11和圖12所示,所有位的復位值也為0。除了管理的外設不一樣,功能與APB2總線相關的兩個寄存器完全一致,不再贅述。
![](https://news.xinpengboligang.com/upload/keji/5f75ac5c1262abccd82b0174c5870f77.jpeg)
圖11. RCC-APB1ENR寄存器配位表
![](https://news.xinpengboligang.com/upload/keji/01f8b8181e7843d2ffefb43112132692.jpeg)
圖12. RCC-APB1RSTR寄存器配位表
6. RCC_AHBENR
RCC_AHBENR為AHB外設時鐘使能寄存器(AHB peripheral clock enable register),用於管理與AHB總線相連的外設時鐘。圖13所示為寄存器配位圖。
![](https://news.xinpengboligang.com/upload/keji/90abfb6863b0fd1c3a04aa42733ce883.jpeg)
圖13. RCC-AHBENR寄存器配位表
AHB總線的外設隻有一個ENR寄存器,沒有*RSTR。RCC_AHBENR原理同APB1和APB2的ENR寄存器,向某位寫入1,使能對應外設時鐘;寫入0,關閉對應外設時鐘。系統復位後,除FLITFEN和SARAMEN位的復位值為1,其餘位復位值為0,說明復位後FLITF和SARAM的時鐘是默認開啟的。
7. RCC_BDCR
RCC_BDCR為備份區控制寄存器(Backup domain control register),用於管理備份區的復位與時鐘選擇,以及開關RTC。所謂備份區是一組特殊的寄存器(BKP,backup registers),用於在VDD斷電後,依靠板載電池VBAT保存用戶數據。備份區主要功能包括:侵入檢測和RTC時鐘校準。在圖1中,備份區位於右上方,LSI時鐘(XTAL32kHz)圖標的下方,有一個TAMPER引腳引出,用於侵入監測或輸出RTC鬧鐘脈沖等(詳情查閱《Reference manual》-BKP寄存器相關章節)。
![](https://news.xinpengboligang.com/upload/keji/ee0ae8bd582b5a25ffa236d50e0a0c26.jpeg)
圖14. RCC-BDCR寄存器配位表
RCC_BDCR寄存器的配位圖如圖14所示。這個寄存器隻用到了7位:
- 前三位:LSEON-使能LSE時鐘;LSERDY-LSE就緒標志;LSEBYP-旁路LSE時鐘。
- RTCSEL:選擇RTC的時鐘源(圖2右上角的RTC Clock Mux)。配位邏輯:00-無時鐘;01-LSE;10-LSI;11-HSE的128分頻。
- RTCEN:RTC使能位,寫入1開啟RTC,寫入0關閉RTC。
- BDRST:寫入1,整個備份區將被復位。
8. RCC_CSR
RCC_CSR為控制/狀態寄存器(Control/status register),用以管理LSI時鐘和系統復位標志。STM32系列MCU支持6種系統復位方式,無論哪種方式觸發復位,除RCC_CSR和備份區寄存器外的所有寄存器都將被復位。備份域寄存器需要設置RCC_BDCR中的BDRST = 1進行復位,RCC_CSR寄存器隻有在發生電源復位是才會被復位。因此,當發生系統復位時,可以用RCC_CSR寄存器記錄復位方式。RCC_CSR寄存器的配位圖如圖15所示。
![](https://news.xinpengboligang.com/upload/keji/2e51e6f5b9bff67e51a34ac81710bc8f.jpeg)
圖15. RCC-CSR寄存器配位表
26-31位為復位方式標志位(ReSeT Flag):
- LPWRRSTF - Low-power reset flag,低功耗復位標志。系統進入低功耗模式,導致系統復位時,由硬件將該位置1。
- WWDGRSTF - Window watchdog reset flag,窗口看門狗復位標志。窗口看門狗復位發生時,由硬件將該位置1。
- IWDGRSTF - Independent watchdog reset flag,獨立看門狗復位標志。獨立看門狗復位發生時,由硬件將該位置1。
- SFTRSTF - Software reset flag,軟件復位標志。軟件復位發生時,由硬件將該位置1。
- PORRSTF - POR/PDR reset flag,上電/掉電復位標志位。上電/掉電復位發生時,由硬件將該位置1。
- PINRSTF - PIN reset flag,引腳復位標志位。芯片有個NRST引腳(exteral reset 見圖2右上角),該引腳為低電平時觸發系統復位。開發板上一般有個RST按鈕,按下後會該引腳接地,觸發系統復位。
24位RMVF(Remove flag)為復位標志清除位,向該位寫入1將會把26-31位中的復位標志全部清除。
0位LSION和1位LSIRDY分別為LSI使能和就緒標志位。如前已述,HSE和HSI的對應功能位在RCC_CR寄存器中設置,LSE的在RCC_BDCR寄存器中設置。
三、 時鐘復位與初始化
3.1 寄存器操作
在頭文件stm32f10x.h(以F103系列標準庫為例,HAL庫中對應的頭文件為stm32f103xe.h)中定義了大量的宏、枚舉類和結構體,對寄存器地址進行了映射。與RCC相關的地址映射摘錄如下:
#define PERIPH_BASE ((uint32_t)0x40000000) /*!< 別名區總線基地址 */
#define AHBPERIPH_BASE (PERIPH_BASE 0x20000)
#define RCC_BASE (AHBPERIPH_BASE 0x1000)
#define RCC ((RCC_TypeDef *) RCC_BASE)
typedef struct{
__IO uint32_t CR; //指向RCC首地址,後續依次便宜0x04(32bit)
__IO uint32_t CFGR;
__IO uint32_t CIR;
__IO uint32_t APB2RSTR;
__IO uint32_t APB1RSTR;
__IO uint32_t AHBENR;
__IO uint32_t APB2ENR;
__IO uint32_t APB1ENR;
__IO uint32_t BDCR;
__IO uint32_t CSR;
} RCC_TypeDef;
通過別名引用寄存器地址,向對應的位寫入0或1,即可實現對寄存器的設置。時鐘配置可以參考圖2,按順序依次配置相關寄存器。例如,先將系統時鐘源設HSE,頻率72MHz,然後開啟GPIOB,GPIOE的時鐘:
/* 復位RCC相關寄存器 */ //CR不要清零,HSICAL出廠數據不確定
RCC->APB1RSTR = 0x00000000;
RCC->APB2RSTR = 0x00000000;
RCC->AHBENR = 0x00000014;
RCC->APB2ENR = 0x00000000;
RCC->APB1ENR = 0x00000000;
RCC->CFGR = 0x00000000;
RCC->CIR = 0x00000000; //關閉所有中斷
/* 配置時鐘 */
RCC->CR |= 0x00010001;//使能HSION/HSEON
while(!(RCC->CR >> 17)); //等待HSERDY
// 配置PLL
RCC->CFGR |= 0x001D0402;//HSE 9倍頻 PCLK2=72MHz
RCC->CR |= 0x01000000; //使能PLL
FLASH->ACR|=0x32; //FLASH 2個延時周期
while(!(RCC->CR >> 25)); //等待PLLRDY
RCC->APB2ENR |= 0x00000048;//開啟GPIOB,GPIOE的時鐘
頭文件stm32f10x.h中還定義了與RCC寄存器的功能位相關的宏,利用這些宏上述操作可改寫為:
RCC->CR |= RCC_CR_HSION | RCC_CR_HSEON;//使能HSION/HSEON
while((RCC->CR & RCC_CR_HSERDY) == 0);
RCC->CFGR &= ~(RCC_CFGR_PLLXTPRE|RCC_CFGR_SW|RCC_CFGR_PLLMULL|RCC_CFGR_PLLSRC);
RCC->CFGR |= RCC_CFGR_PLLXTPRE_PREDIV1|RCC_CFGR_SW_PLL|RCC_CFGR_PLLMULL9|RCC_CFGR_PLLSRC_HSE;
RCC->CR |= RCC_CR_PLLON;
while((RCC->CR & RCC_CR_PLLRDY) == 0);
RCC->APB2ENR |= RCC_APB2ENR_IOPBEN|RCC_APB2ENR_IOPEEN;
小結:
- 如果新設的所有位均為1,準備好一個合適的Set mask(32bit十六進制數),直接|=即可。
- 如果新設的位中有0,需要先準備好一個Reset mask,&=定位清零,然後再準備一個Set mask,|=寫入新值。
- 為了便於計算Reset mask和Set mask,可以用Excel結合VBA編程做一個自動計算表。每個sheet對應一組寄存器進行統一管理。
- 用stm32f10x.h中預定義的宏操代碼可讀性和復用性好,但代碼確實要復雜一些。好在常用的IDE都有代碼提示功能,不需要記憶大量的宏名稱。
3.2 STD庫RCC函數
STD庫中RCC相關的庫函數在stm32f10x_rcc.h中聲明,在stm32f10x_rcc.c中實現。頭文件stm32f10x_rcc.h中為RCC相關寄存器中的功能位定義了宏名稱,包括時鐘源、各種外設、分頻/倍頻系數、多路選擇器選項、標志位等定義,這些宏主要是為了配合庫函數使用的。為了在庫函數中實現按位(bit banding)操作,提高效率,在stm32f10x_rcc.c中,對部分寄存器在別名區的地址也進行了映射,為常用功能位定義了設置/復位掩碼(Set/Reset mask)。
庫函數是對寄存器操作的封裝。STD庫中RCC相關的庫函數有30多個,下面按函數所操作的寄存器分組並簡述其功能,可參照圖3查看寄存器所關聯的寄存器功能位/組。
1. 跨寄存器函數
- void RCC_DeInit(void) 復位CR、CFGR、CIR寄存器,這三個寄存器管理的是時鐘配置和中斷配置。復位後CIR寄存器的值與復位值表稍有不同,以C結尾的寄存器值被賦值為1,可能是因為F結尾的位是通過硬件設置的,隻能通過軟件設置C結尾的位來來觸發硬件復位F結尾的位。
- void RCC_GetClocksFreq(RCC_ClocksTypeDef* RCC_Clocks) 查詢SYSCLK/HCLK/PCLK1/PCLK2/ADCCLK時鐘的頻率。該函數會根據當前時鐘配置,計算並返回所查詢時鐘的 頻率,單位Hz。
- FlagStatus RCC_GetFlagStatus(uint8_t RCC_FLAG) 查詢狀態標志位的值。 該函數用於查詢CR、BDCR以及CSR中的*RDY位和*RST位(參見圖3)。
2. RCC_CR
- void RCC_HSICmd(FunctionalState NewState) 開/關HSI時鐘(HSION位)。
- void RCC_AdjustHSICalibrationValue(uint8_t HSICalibrationValue) 調整HSI校準參數(HSITRIM[4:0]位)。取值范圍0x00-0x1F。
- void RCC_HSEConfig(uint32_t RCC_HSE) 配置HSE時鐘(HSEON和HSEBYP位)。從函數定義可以看出,如果使能HSEBYP位,HSEON位必須同時使能,設置HSEBYP是為了使用別的外部時鐘源,將HSE旁路,但HSE端口必須使能。
- ErrorStatus RCC_WaitForHSEStartUp(void) 等待HSE就緒。通過HSEON開啟HSE時鐘時,需要6個HSE時鐘周期才會穩定下來,穩定下來後將HSERDY位置1。該函數中用一個do while循環調用RCC_GetFlagStatus(),查詢RCC_FLAG_HSERDY,即HSERDY位的狀態。直至HSERDY=SET或超時(1280次)。時鐘就緒時返回SUCCESS,否則返回ERROR。
- void RCC_ClockSecuritySystemCmd(FunctionalState NewState) 開/關CSS(CSSON位)。位帶操作。
- void RCC_PLLCmd(FunctionalState NewState) 開/關PLL(PLLON位)。位帶操作。
3. RCC_CFGR
- void RCC_SYSCLKConfig(uint32_t RCC_SYSCLKSource) SYSCLK配置(SW[1:0]位)。
- void RCC_HCLKConfig(uint32_t RCC_SYSCLK) HCLK配置(HPRE[3:0]位)。
- void RCC_PCLK1Config(uint32_t RCC_HCLK) PCLK1配置(PPRE1[2:0]位)。
- void RCC_PCLK2Config(uint32_t RCC_HCLK) PCLK2配置(PPRE2[2:0]位)。
- void RCC_ADCCLKConfig(uint32_t RCC_PCLK2) ADCCLK配置(ADCPRE[1:0]位)。
- void RCC_PLLConfig(uint32_t RCC_PLLSource, uint32_t RCC_PLLMul) 配置PLL時鐘源(PLLSRC位和PLLXTPRE位)和倍頻系數(PLLMUL位)。
- void RCC_MCOConfig(uint8_t RCC_MCO) 配置系統時鐘輸出(MCO[2:0])。
- uint8_t RCC_GetSYSCLKSource(void) 查詢系統時鐘源(SWS[1:0])。
4. RCC_CIR
- void RCC_ITConfig(uint8_t RCC_IT, FunctionalState NewState) 中斷配置(6個*E位)。
- ITStatus RCC_GetITStatus(uint8_t RCC_IT) 中斷狀態查詢(6個*F位)。
- void RCC_ClearITPendingBit(uint8_t RCC_IT) 中斷狀態清除(6個*C位)。
5. RCC_BDCR
- void RCC_LSEConfig(uint8_t RCC_LSE) LSE時鐘配置(LSEON位和LSEBYP位)。
- void RCC_RTCCLKConfig(uint32_t RCC_RTCCLKSource) RTC時鐘源配置(RTCSEL[1:0]位)。
- void RCC_RTCCLKCmd(FunctionalState NewState) 開/關RTCRTC時鐘(RTCEN位)。
- void RCC_BackupResetCmd(FunctionalState NewState) 復位備份區(BDRST位)。
6. RCC_CSR
- void RCC_LSICmd(FunctionalState NewState) 開/關LIS時鐘(LSION位)。
- void RCC_ClearFlag(void) 清除標志位(RMVF位)。項RMVF為寫入1,清除復位*RSTF位。
7. 開關外設時鐘
- void RCC_AHBPeriphClockCmd(uint32_t RCC_AHBPeriph, FunctionalState NewState) 開/關AHB外設時鐘。
- void RCC_APB2PeriphClockCmd(uint32_t RCC_APB2Periph, FunctionalState NewState) 開/關APB2外設時鐘。
- void RCC_APB1PeriphClockCmd(uint32_t RCC_APB1Periph, FunctionalState NewState) 開/關AHB1外設時鐘。
- void RCC_APB2PeriphResetCmd(uint32_t RCC_APB2Periph, FunctionalState NewState) 復位APB2外設時鐘。
- void RCC_APB1PeriphResetCmd(uint32_t RCC_APB1Periph, FunctionalState NewState) 復位APB1外設時鐘。
小結:
- 幾乎每個寄存器功能位/組,都有與之對應的庫函數。
- *cmd函數為功能使能/關閉函數;*Config為功能配置函數;*Init為復位或初始化函數;Get*為查詢函數;Wait*為等待函數;Clear*標志位為清除函數。
示例:
RCC_HSEConfig(RCC_HSE_ON); //使能HSE
RCC_PLLConfig(CC_PLLSource_HSE_Div1,RCC_PLLMul_9);//配置PLL
RCC_PLLCmd(ENABLE); //使能PLL
RCC_SYSCLKConfig(RCC_SYSCLKSource_PLLCLK); //選通PLL作為系統時鐘源
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB|RCC_APB2Periph_GPIOE,ENABLE);//開啟GPIOB,GPIOE的時鐘
說明:
STD庫中的時鐘配置可以按上述方式,通過調用庫函數配置。其實有更簡單的方法,在system_stm32f10x.c中,STMicroelectronicst已經實現了各種可能的時鐘配置,我們隻需打開相應的宏定義即可。system_stm32f10x.c中的第一個宏定義模塊中,去掉/* #define SYSCLK_FREQ_72MHz 72000000 前的註釋符即可。在系統初始化函數SystemInit中會自動調用SetSysClock函數完成時鐘配置。
3.3 HAL庫RCC函數
HAL庫中與一個外設相關的文件通常有4個:stm32f1xx_hal_ppp.c, stm32f1xx_hal_ppp.h, stm32f1xx_hal_ppp_ex.c, stm32f1xx_hal_ppp_ex.h。前兩個文件提供全F1系列芯片外設通用功能的API,後兩個文件則是當前型號外設特有功能的API(擴展API,ex是extension的縮寫)。擴展API中定義的函數也用*_pppEX_*命名以示區分。
HAL庫中RCC相關的普通庫函數精簡到了17個,同時定義了大量的宏函數。
- HAL_StatusTypeDef HAL_RCC_DeInit(void) 復位CR、CFGR、CIR、CSR寄存器。功能相當於STD庫中的RCC_DeInit RCC_CC_ClearFlag。
- HAL_StatusTypeDef HAL_RCC_OscConfig(RCC_OscInitTypeDef *RCC_OscInitStruct) 時鐘源配置。HAL庫中定義了RCC_OscInitTypeDef和RCC_PLLInitTypeDef兩個結構體,用以集中管理LSI、LSE、HSI、HSE、PLL5個時鐘源的配置參數。
- HAL_StatusTypeDef HAL_RCC_ClockConfig(RCC_ClkInitTypeDef *RCC_ClkInitStruct, uint32_t FLatency) 時鐘配置。RCC_ClkInitTypeDef結構體管理時鐘類型(SYSCLK/HCLK/PCLK1/PCLK2)、SYSCLK來源、AHB/APB1/APB2的預分頻系數。
- void HAL_RCC_MCOConfig(uint32_t RCC_MCOx, uint32_t RCC_MCOSource, uint32_t RCC_MCODiv) 配置MCO。
- void HAL_RCC_EnableCSS(void) 使能CSS。
- void HAL_RCC_DisableCSS(void) 關閉CSS。
- uint32_t HAL_RCC_GetSysClockFreq(void) 查詢SYSCLK頻率。
- uint32_t HAL_RCC_GetHCLKFreq(void) 查詢HCLK頻率。
- uint32_t HAL_RCC_GetPCLK1Freq(void) 查詢PCLK1頻率。
- uint32_t HAL_RCC_GetPCLK2Freq(void) 查詢PCLK2頻率 < *--以上4個函數計算並返回相應時鐘頻率,單位Hz。
- void HAL_RCC_GetOscConfig(RCC_OscInitTypeDef *RCC_OscInitStruct) 查詢當前時鐘源配置。輸入參數為RCC_OscInitTypeDef型指針,所以無需返回。
- void HAL_RCC_GetClockConfig(RCC_ClkInitTypeDef *RCC_ClkInitStruct, uint32_t *pFLatency) 查詢當前時鐘配置。
- void HAL_RCC_NMI_IRQHandler(void) CSS中斷請求處理函數(Interrupt ReQuest)。CCS中斷是NMI(None maskable interrupt)中斷,表明時鐘源出了問題。系統的所有NMI中斷由NMI_Handler()函數處理(stm32f1xx_it.c),所以該函數應該在NMI_Handler()中被。
- __weak void HAL_RCC_CSSCallback(void) CSS中斷回調函數。這是真正的中斷處理函數,是個week函數,要由用戶具體實現。CSS中斷最典型的應用就是系統時鐘監控,如果HSE時鐘出了故障,調用該回調函數,嘗試重啟時鐘或報錯。
- static void RCC_Delay(uint32_t mdelay) 延時函數。基於CPU時鐘周期延時指定的時長(ms)
- HAL_StatusTypeDef HAL_RCCEx_PeriphCLKConfig(RCC_PeriphCLKInitTypeDef *PeriphClkInit)
- void HAL_RCCEx_GetPeriphCLKConfig(RCC_PeriphCLKInitTypeDef *PeriphClkInit)
- uint32_t HAL_RCCEx_GetPeriphCLKFreq(uint32_t PeriphClk) < *--擴展API函數,上述3個函數用於查詢特有外設的時鐘配置和頻率,包括:RTC、ADC、I2S2、I2S3,USB。這些外設同系列不同型號的芯片可能要求不一樣(參見圖2)。
小結:
- HAL庫中主要為時鐘源和時鐘參數配置、時鐘查詢和中斷處理等功能位定義了函數。
- 外設時鐘管理被定義成了宏函數,語法格式:(pppp為外設名稱,宏函數以__HAL開頭,註意前面是2個下劃線)
__HAL_RCC_pppp_CLK_ENABLE() //使能外設時鐘
__HAL_RCC_pppp_CLK_DISABLE() //關閉外設時鐘
__HAL_RCC_pppp_IS_CLK_ENABLED() //查詢是否使能成功
__HAL_RCC_pppp_IS_CLK_DISABLED() //查詢是否關閉成功
__HAL_RCC_pppp_FORCE_RESET() //復位為1
__HAL_RCC_pppp_RELEASE_RESET() //復位為0
- 時鐘源的開關/配置/查詢,中斷開關/清除/查詢、狀態標志位查詢/清除、備份區的設置等也都有對應的宏函數。
- RCC相關的普通函數其實也隻是宏函數的組合封裝。函數內部對寄存器的操作大都是通過調用宏函數去實現的。
- 基於HAL庫開發,強烈推薦使用STM32CubeIDE或STM32CubeMX,可以以圖形化方式完成時鐘和外設端口配置,省卻無盡麻煩!!!
示例:
RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
RCC_OscInitStruct.HSEState = RCC_HSE_ON; //使能HSE
RCC_OscInitStruct.HSEPredivValue = RCC_HSE_PREDIV_DIV1;
RCC_OscInitStruct.HSIState = RCC_HSI_ON;
RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON; //使能PLL
RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE; //PLL源為HSE
RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL9; //9倍頻
if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK){
Error_Handler();
}
__HAL_RCC_GPIOB_CLK_ENABLE(); //開啟GPIOB的時鐘
__HAL_RCC_GPIOE_CLK_ENABLE(); //開啟GPIOE的時鐘