- 基于ARM Cortex-M3的STM32系列嵌入式微控制器應用實踐
- 彭剛 秦志強編著
- 5614字
- 2018-12-27 16:01:44
2.2 STM32單片機的時鐘配置
一般而言,嵌入式系統在正式工作前,都要進行一些初始化工作,我們常把這個階段寫成一個子函數的形式,叫BSP_Init函數(Board Support Package,BSP,板級支持包)。
開發板初始化函數BSP_Init會調用3個函數:RCC_Configuration(復位和時鐘設置),GPIO_Configuration(IO口設置),NVIC_Configuration(中斷設置)。這里先介紹了前兩個函數,第3個函數在后面講解中斷時再介紹。
void BSP_Init() { RCC_Configuration(); /*Configure the system clocks:系統時鐘設置*/ GPIO_Configuration(); /*GPIO Configuration:設置I/O */ NVIC_Configuration(); /*NVIC Configuration:設置中斷*/ }
我們先認識一下開發板初始化函數中的復位和時鐘配置函數RCC_Configuration(Reset and Clock Configuration,RCC),它與STM32系列微控制器中的時鐘有關。
STM32系列微控制器中的五個時鐘源:HSI、HSE、LSI、LSE、PLL
① HSI(High Speed Internal)是高速內部時鐘,RC振蕩器,頻率為8MHz(精度較差)。
② HSE(High Speed External)是高速外部時鐘,可接石英/陶瓷諧振器,或者接外部時鐘源,頻率范圍為4~16MHz(精度高)。
③ LSI(Low Speed Internal)是低速內部時鐘,RC振蕩器,頻率為30~60kHz。
④ LSE(Low Speed External)是低速外部時鐘,接頻率為32.768kHz的石英晶體,供實時時鐘RTC使用。電路如圖2.3(b)所示。
⑤ PLL(Phase Lock Loop)為鎖相環倍頻輸出,其時鐘輸入源可選擇為HSI/2、HSE或者HSE/2。倍頻可選擇為2~16倍,但是其輸出頻率最大不得超過72MHz。
鎖相環的基本組成
PLL(Phase Locked Loop):鎖相回路或叫鎖相環。PLL用于振蕩器中的反饋技術。許多電子設備要正常工作,通常需要外部的輸入信號與內部的振蕩信號同步,利用鎖相環路就可以實現這個目的。鎖相環是一種反饋控制電路,特點是:利用外部輸入的參考信號控制環路內部振蕩信號的頻率和相位。因鎖相環可以實現輸出信號頻率對輸入信號頻率的自動跟蹤,所以鎖相環通常用于閉環跟蹤電路。
鎖相環在工作的過程中,當輸出信號的頻率與輸入信號的頻率相等時,輸出電壓與輸入電壓保持固定的相位差值,即輸出電壓與輸入電壓的相位被鎖住,這就是鎖相環名稱的由來。鎖相環通常由鑒相器(Phase Detector,PD)、環路濾波器(Loop Filter,LF)和壓控振蕩器(Voltage Controlled Oscillator,VCO)三部分組成,鎖相環組成的原理框圖如圖2.4所示。圖中的鑒相器又稱為相位比較器,它的作用是檢測輸入信號和輸出信號的相位差,并將檢測出的相位差信號轉換成uD(t)電壓信號輸出,該信號經低通濾波器濾波后形成壓控振蕩器的控制電壓uC(t),對振蕩器輸出信號的頻率實施控制。輸出頻率fout與輸入參考頻率fr的關系為:fout = M*fr。

圖2.4 鎖相環組成的原理框圖
STM32單片機的將時鐘信號(常是HSE)經過分頻或倍頻(PLL)后,得到系統時鐘,系統時鐘經過分頻,產生外設所使用的時鐘。圖2.5是STM32時鐘系統結構圖。

圖2.5 STM32時鐘系統結構圖
其中,40kHz(典型值)的LSI供獨立看門狗IWDG使用,另外它還可以被選擇為實時時鐘RTC的時鐘源。實時時鐘RTC的時鐘源也可以選擇LSE,或者是HSE的128分頻。RTC的時鐘源通過備份域控制寄存器(RCC_BDCR)的RTCSEL[1:0]來選擇。
STM32中有一個全速功能的USB模塊,其串行接口引擎需要一個頻率為48MHz的時鐘源。該時鐘源只能從PLL輸出端獲取,可以選擇為1.5分頻或者1分頻,也就是,當需要使用USB模塊時,PLL必須使能,并且時鐘頻率配置為48MHz或72MHz。
另外,STM32還可以選擇一個時鐘信號輸出到MCO腳(PA8)上,可以選擇為PLL輸出的2分頻、HSI、HSE、或者系統時鐘。
系統時鐘SYSCLK,它是供STM32中絕大部分部件工作的時鐘源。系統時鐘可選擇為PLL輸出、HSI或者HSE。系統時鐘最大頻率為72MHz,它通過AHB分頻器分頻后送給各模塊使用,AHB分頻器可選擇1、2、4、8、16、64、128、256、512分頻。其中AHB分頻器輸出的時鐘送給8大模塊使用。
① 送給SDIO使用的SDIOCLK時鐘。
② 送給FSMC使用的FSMCCLK時鐘。
③ 送給AHB總線、內核、內存和DMA使用的HCLK時鐘。
④ 通過8分頻后送給Cortex的系統定時器時鐘(SysTick)。
⑤ 直接送給Cortex的空閑運行時鐘FCLK。
⑥ 送給APB1分頻器。APB1分頻器可選擇1、2、4、8、16分頻,其輸出一路供APB1外設使用(PCLK1,最大頻率36MHz),另一路送給定時器2、3、4倍頻器使用。該倍頻器可選擇1或者2倍頻,時鐘輸出供定時器2、3、4使用。
⑦ 送給APB2分頻器。APB2分頻器可選擇1、2、4、8、16分頻,其輸出一路供APB2外設使用(PCLK2,最大頻率72MHz),另一路送給定時器1倍頻器使用。該倍頻器可選擇1或者2倍頻,時鐘輸出供定時器1使用。另外,APB2分頻器還有一路輸出供ADC分頻器使用(可選擇為2、4、6、8分頻),分頻后得到ADCCLK時鐘,送給ADC模塊使用。
⑧ 2分頻后送給SDIO AHB接口使用(HCLK/2)。
AMBA片上總線
片上總線標準種類繁多,而由ARM公司推出的AMBA片上總線受到了廣大IP開發商和SoC(System on Chip)片上系統集成者的青睞,已成為一種流行的工業標準片上結構。AMBA規范主要包括了AHB(Advanced High performance Bus)系統總線和APB(Advanced Peripheral Bus)外設總線。二者分別適用于高速與相對低速設備的連接。
時鐘輸出的使能控制
在以上的時鐘輸出中,有很多是帶使能控制的,如AHB總線時鐘、內核時鐘、各種APB1外設、APB2外設等。當需要使用某個外設模塊時,記得一定要先使能對應的時鐘,否則這個外設不能工作。因此,使用任何一個外設都必須打開相應的時鐘。這樣的好處就是,如果不使用一個外設時,就把它的時鐘關掉,從而可以降低系統的功耗,達到節能,實現低功耗的效果。當STM32單片機系統時鐘為72MHz時,在運行模式下,打開全部外設時的功耗電流為36mA,關閉全部外設時的功耗電流為27mA。
需要注意的是定時器2、3、4的倍頻器,當APB1的分頻為1時,它的倍頻值為1(且只能為1,因為不能高于AHB頻率),此時,定時器的時鐘頻率等于APB1的頻率;當APB1的預分頻系數為其他數值(即預分頻系數為2、4、8或16)時,它的倍頻值就為2。連接在APB1(低速外設)上的設備有電源接口、備份接口、CAN、USB、I2C1、I2C2、UART2、UART3、SPI2、窗口看門狗、Timer2、Timer3、Timer4。注意USB模塊雖然需要一個單獨的48MHz時鐘信號,但它應該不是供USB模塊工作的時鐘,而只是提供給串行接口引擎(SIE)使用的時鐘。USB模塊工作的時鐘是由APB1提供的。連接在APB2(高速外設)上的設備有:UART1、SPI1、Timer1、ADC1、ADC2、所有普通I/O口(PA~PE)、第二功能I/O口。
為什么ARM時鐘這么復雜?
大家可能看到了,基于ARM Cortex-M3的STM32單片機時鐘很復雜,與51單片機相差太大。標準51單片機很簡單,外部晶振12分頻就是機器頻率,即51單片機工作的基準頻率,增強型51(如C8051)也不過是外部晶振頻率直接是機器頻率而已。
其實,隨著芯片工藝的發展,臺式機和嵌入式系統的處理器頻率越來越快,而處理器除了中央處理單元(CPU)外,還有一些外設接口,如串口,它們的時鐘并沒有那么快。如果中央處理單元與外設接口公用一樣的時鐘,那么中央處理單元在同一時間內要做很多事情,外設接口才能做一件事情,如數據存取。設想一下,當中央處理單元等待外設接口傳來一個數據時,豈不是要等到很久。這樣中央處理單元的性能就不能發揮出來,而且外設接口也沒必要提供太高的時鐘。另一個原因就是時鐘分開有助于實現低功耗。
因此,現在的嵌入式系統處理器常常將時鐘分開,有供中央處理單元使用的,也有供外設接口使用的。不僅僅是基于ARM內核的芯片,很多其他32位嵌入式處理器也是這樣。
復位和時鐘配置函數RCC_Configuration
時鐘的具體配置是從RCC配置寄存器開始。定義RCC配置寄存器的是結構體RCC_TypeDef,在文件“stm32f10x_map.h”中定義如下:
typedef struct { vu32 CR; //時鐘控制寄存器:Clock control register vu32 CFGR; //時鐘配置寄存器:Clock configuration register vu32 CIR; //時鐘中斷寄存器:Clock interrupt register vu32 APB2RSTR; //APB2外設復位寄存器:APB2 Peripheral reset register vu32 APB1RSTR; //APB1外設復位寄存器:APB1 Peripheral reset register vu32 AHBENR; //AHB外設時鐘使能寄存器:AHB Peripheral Clock enable register vu32 APB2ENR; //APB2外設時鐘使能寄存器:APB2 Peripheral Clock enable register vu32 APB1ENR; //APB1外設時鐘使能寄存器:APB1 Peripheral Clock enable register vu32 BDCR; //備份域控制寄存器:Backup domain control register vu32 CSR; //控制/狀態寄存器:Control/status register } RCC_TypeDef; … #define PERIPH_BASE ((u32)0x40000000) … #define AHBPERIPH_BASE (PERIPH_BASE+0x20000) … #define RCC_BASE (AHBPERIPH_BASE+0x1000) … #ifdef _RCC #define RCC ((RCC_TypeDef*)RCC_BASE) #endif
其中,vu32代表一個32位的無符號長整形數,在文件“stm32f10x_type.h”中定義了:
typedef signed long s32; typedef signed short s16; typedef signed char s8; typedef volatile signed long vs32; typedef volatile signed short vs16; typedef volatile signed char vs8; typedef unsigned long u32; typedef unsigned short u16; typedef unsigned char u8; typedef unsigned long const uc32; /*Read Only*/ typedef unsigned short const uc16; /*Read Only*/ typedef unsigned char const uc8; /*Read Only*/ typedef volatile unsigned long vu32; typedef volatile unsigned short vu16; typedef volatile unsigned char vu8; typedef volatile unsigned long const vuc32; /*Read Only*/ typedef volatile unsigned short const vuc16; /*Read Only*/ typedef volatile unsigned char const vuc8; /*Read Only*/ typedef enum {FALSE = 0, TRUE = !FALSE} bool; typedef enum {RESET = 0, SET = !RESET} FlagStatus, ITStatus; typedef enum {DISABLE = 0, ENABLE = !DISABLE} FunctionalState; #define IS_FUNCTIONAL_STATE(STATE) ((STATE == DISABLE) || (STATE == ENABLE)) typedef enum {ERROR = 0, SUCCESS = !ERROR} ErrorStatus;
從上面的幾個宏定義可以看出,在程序中所有寫RCC的地方,編譯器的預處理程序將它替換成((RCC_TypeDef *) 0x40021000)。其實,這個地址是RCC寄存器組的首地址,RCC寄存器映像和復位值如表2.1 所示,這些寄存器的具體定義和使用方式參見芯片數據手冊。關于STM32處理器的存儲映射參見附錄,這里不再贅述。
表2.1 RCC寄存器映像和復位值表

volatile關鍵字的含義
一個定義為volatile的變量是說這變量是易變的,可能會被意想不到地改變,它們的值可能由于程序控制之外的事件而被潛在改變。這樣,編譯器就不會去假設這個變量的值了。準確地說就是,編譯器優化時,在用到這個變量時必須每次都重新讀取這個變量的值,即每次讀寫都必須訪問實際地址存儲器的內容,而不是使用保存在寄存器中的副本。
在嵌入式系統中,volatile大量地用來描述一個對應于內存映射的輸入/輸出端口,或者硬件寄存器(如狀態寄存器)。
進一步講解volatile
那為什么編譯器會將沒有被volatile修飾的變量在寄存器里保存個備份呢?這往往是基于程序運行效率的考慮,因為從寄存器里取數據要更快些,寄存器是在嵌入式處理器內核中,而從實際的存儲器地址(往往是外設)訪問會慢些。
這個問題往往是區分C程序員和嵌入式系統程序員的最基本問題。嵌入式工程師經常同硬件、中斷、RTOS等打交道,所有這些都要求用到volatile變量。不懂得volatile的含義就將會給嵌入式系統軟件帶來缺陷,發生不可預料甚至災難性的后果。
其次,中斷服務例程中使用的非自動變量或者多線程應用程序中多個任務共享的變量也必須使用volatile進行限定。例如代碼:
int flag=0; void f(){ while(1){ if(flag) some_action(); } } void isr_f(){ flag=1; }
如果沒有使用volatile限定flag變量,編譯器看到在f()函數中并沒有修改flag,可能只執行一次flag讀操作并將flag的值緩存在寄存器中,以后每次訪問flag(讀操作)都使用寄存器中的緩存值而不進行存儲器絕對地址訪問,導致some_action函數永遠無法執行,即使中斷函數isr_f()執行了將flag置1。
下面,我們看看復位和時鐘配置函數RCC_Configuration。
ErrorStatus HSEStartUpStatus; /* 枚舉變量,定義高速時鐘的起動狀態*/ … void RCC_Configuration (void) { /* 將外設RCC寄存器重設為默認值,即有關寄存器復位,但該函數不改動寄存器RCC_CR的 HSITRIM[4:0]位,也不重置寄存器RCC_BDCR和寄存器RCC_CSR */ RCC_DeInit(); RCC_HSEConfig(RCC_HSE_ON); /* 使能外部HSE高速晶振*/ /* 等待HSE高速晶振穩定,或者在超時的情況下退出*/ HSEStartUpStatus = RCC_WaitForHSEStartUp(); /* SUCCESS:HSE晶振穩定且就緒,ERROR:HSE晶振未就緒 */ if (HSEStartUpStatus == SUCCESS) { /*HCLK=SYSCLK設置高速總線時鐘=系統時鐘*/ RCC_HCLKConfig(RCC_SYSCLK_Div1); /*PCLK2=HCLK設置低速總線2時鐘=高速總線時鐘*/ RCC_PCLK2Config(RCC_HCLK_Div1); /*PCLK1=HCLK/2 設置低速總線1的時鐘=高速時鐘的二分頻*/ RCC_PCLK1Config(RCC_HCLK_Div2); /* 設置FLASH存儲器延時時鐘周期數,2是針對高頻時鐘的, FLASH_Latency_0:0延時周期,FLASH_Latency_1:1延時周期 FLASH_Latency_2:2延時周期 */ FLASH_SetLatency(FLASH_Latency_2); /* 使能flash預取指令緩沖區。這兩句跟RCC沒直接關系*/ FLASH_PrefetchBufferCmd(FLASH_PrefetchBuffer_Enable); /* Set PLL clock output to 72MHz using HSE (8MHz) as entry clock */ /* 利用鎖相環將HSE外部8MHz晶振9倍頻到72MHz*/ RCC_PLLConfig(RCC_PLLSource_HSE_Div1, RCC_PLLMul_9); /* Enable PLL:使能PLL鎖相環*/ RCC_PLLCmd(ENABLE); /* Wait till PLL is ready:等待鎖相環輸出穩定 */ /* RCC_FLAG_HSIRDY:HSI晶振就緒,RCC_FLAG_HSERDY:HSE晶振就緒 RCC_FLAG_PLLRDY:PLL就緒,RCC_FLAG_LSERDY:LSE晶振就緒 RCC_FLAG_LSIRDY:LSI晶振就緒,RCC_FLAG_PINRST:引腳復位 RCC_FLAG_PORRST:POR/PDR復位,RCC_FLAG_SFTRST:軟件復位 RCC_FLAG_IWDGRST:IWDG復位,RCC_FLAG_WWDGRST:WWDG復位 RCC_FLAG_LPWRRST:低功耗復位 */ while (RCC_GetFlagStatus(RCC_FLAG_PLLRDY) == RESET) { } /* Select PLL as system clock source:將鎖相環輸出設置為系統時鐘 */ /* RCC_SYSCLKSource_HSI:選擇HSI作為系統時鐘 RCC_SYSCLKSource_HSE:選擇HSE作為系統時鐘 RCC_SYSCLKSource_PLLCLK:選擇PLL作為系統時鐘*/ RCC_SYSCLKConfig(RCC_SYSCLKSource_PLLCLK); /* 等待PLL作為系統時鐘標志位置位*/ /* 0x00:HSI作為系統時鐘;0x04:HSE作為系統時鐘 0x08:PLL作為系統時鐘 */ while (RCC_GetSYSCLKSource() != 0x08) { } } /* Enable GPIOA~E and AFIO clocks:使能外圍端口總線時鐘。注意各外設的隸屬情況,不同芯片 和開發板的分配不同*/ RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOB | RCC_APB2Periph_GPIOC | RCC_APB2Periph_GPIOD | RCC_APB2Periph_GPIOE | RCC_APB2Periph_AFIO, ENABLE); /* USART1 clock enable:USART1時鐘使能 */ RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE); /* TIM1 clock enable:TIM1時鐘使能 */ RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM1, ENABLE); /* TIM2 clock enable:TIM2時鐘使能*/ RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE); /* ADC1 clock enable:ADC1時鐘使能*/ RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE); }
在初始化階段,RCC_Configuration函數完成系統的復位和時鐘設置。這些函數的具體實現在庫文件“stm32f10x_rcc.c”中(\library\src目錄下)。復位和時鐘設置函數RCC_Configuration中的第一條語句是RCC_DeInit(),其作用是復位定義在結構體RCC_TypeDef中的各個RCC配置寄存器。教學開發板上有一個8MHz的晶振,將PLL設置為9 倍頻,這樣系統時鐘為72MHz(STM32F103 增強型單片機最高工作頻率為72MHz),高速總線和低速總線2 都為72MHz,低速總線1為36MHz。這里應注意:PLL的設定需要在使能之前,一旦PLL使能后參數不可更改。
由上述程序可以看出,系統時鐘的設定是比較復雜的,外設越多,需要考慮的因素就越多。這種設定是有規律可循的,設定參數也很規范。
例如,加入RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA, ENABLE);語句,則使能DMA外設時鐘。如果你想給模數轉換器(ADC)設置時鐘,可以在if(HSEStartUpStatus ==SUCCESS)邏輯塊中加入:
RCC_ADCCLKConfig(RCC_PCLK2_Div6);
這樣,ADC的時鐘就設為12MHz,即系統時鐘的6分頻。
注意:由于USB時鐘的數據傳輸標準為48MHz,因此你需經過1.5分頻設置才可實現。
時鐘設置
一般的,時鐘設置需要先考慮系統時鐘的來源,是內部RC、外部晶振、還是外部的振蕩器,是否需要PLL。然后再考慮內部總線和外部總線,最后考慮外設的時鐘信號。遵從先倍頻作為CPU時鐘,然后再由內向外分頻的原則。
要注意的是,STM32處理器因為低功耗的需要,各模塊需要分別獨立開啟時鐘,所以,一定不要忘記給用到的模塊和引腳使能時鐘。
系統復位后,HSI振蕩器被選為系統時鐘。當時鐘源被直接或通過PLL間接作為系統時鐘時,它將不能被停止。只有當目標時鐘源準備就緒了(經過啟動穩定階段的延遲或PLL穩定),從一個時鐘源到另一個時鐘源的切換才會發生。在被選擇時鐘源沒有就緒前,系統時鐘的切換不會發生;直至目標時鐘源就緒,才發生切換。時鐘控制寄存器(RCC_CR)中的狀態位指示了哪個時鐘已經準備好了,哪個時鐘目前被用做系統時鐘。
STM32單片機的時鐘安全系統
時鐘安全系統(CSS)可以通過軟件被激活。一旦其被激活,時鐘監測器將在HSE振蕩器啟動延遲后被使能,并在HSE時鐘關閉后關閉。如果HSE時鐘發生故障,HSE振蕩器被自動關閉,時鐘失效事件將被送到高級定時器(TIM1和TIM8)的剎車輸入端,并產生時鐘安全中斷CSSI,允許軟件進行緊急處理操作。此CSSI中斷連接到Cortex-M3的NMI中斷(不可屏蔽中斷)。關于STM32單片機的中斷,在后面的章節再做介紹。
注意:一旦CSS被激活,并且HSE時鐘出現故障,CSS中斷就產生,并且NMI也自動產生。NMI將被不斷執行,直到CSS中斷掛起位被清除。因此,在NMI的處理程序中必須通過設置時鐘中斷寄存器(RCC_CIR)里的CSSC位來清除CSS中斷。如果HSE振蕩器被直接或間接(經PLL倍頻)地作為系統時鐘,時鐘故障將導致系統時鐘自動切換到HSI振蕩器,同時外部HSE振蕩器被關閉。在時鐘失效時,如果HSE振蕩器時鐘是用做系統時鐘的PLL的輸入時鐘,PLL也將被關閉。因此,STM32單片機的時鐘系統具有很高的安全型。