上一篇文章「STM32筆記(24):七段顯示器顯示數字」在顯示數字時,設定每 1000ms 跳一個數字,這代表一秒鐘。這篇文章要來探討一下 STM32 的時鐘系,與如何利用 SysTick 產生精確的毫秒與微秒。
時鐘對於單片機是很重要的,因為所有指令都要靠時鐘信號去推動才能執行。而時鐘的頻率決定了處理的速度。STM32 有多個時鐘源,主要原因是 STM32 有多種外設,每種外設有其不同的特性與應用,並不是所有的外設都需要那麼高的頻率,如果全部都用高速時鐘,會讓功耗變大,造成浪費。因此複雜的 STM32 設計多種時鐘源給不同的外設使用,才有了時鐘系統和時鐘樹。
STM32 爲了減少功耗,預設將所有的外設時鐘都設置爲停用(Disable),如果要使用某個外設,才打開其對應的時鐘,這樣可以有效的降低功耗,讓系統更省電。這也就是為何對外設進行初始化前,都要開啟其對應時鐘的原因。
STM32 的時鐘樹(Clock Tree, 如下圖)看起來很複雜,其實很好理解。從 ② 跟 ㊁ 中間的 systick 分成左右兩邊,左邊的部分就是要選定使用那個時鐘源,而右邊是系統時鐘通過 AHB 預分頻器,給相對應的外設設置相對應的時鐘頻率, STM32 有 4 個獨立時鐘源,分成高速和低速,或分成內部及外部:HSI、HSE、LSE、LSI,(如下圖①、③~⑤),② PLL 鎖相環倍頻輸出其實不是自己產生的時鐘源,是由 HSI 倍頻或分頻而來。
在開始編寫程式時,預設時鐘爲 72Mhz,是由外部晶振(HSE)提供的 8MHz 通過 PLLXTPRE 分頻器後,進入 PLLSRC 選擇開關,再通過 PLLMUL 鎖相環進行倍頻(x9)後,得到 72MHz 的系統時鐘(SYSCLK)。在經過 AHB 預分頻器對時鐘信號進行分頻,提供給低速外設使用。
從時鐘圖 ㊂ 可以看出,其他所有外設的時鐘最終來源都是 SYSCLK。SYSCLK 通過 AHB 分頻器分頻後送給 5 大模組使用。這些模組包括:
APB1 和 APB2 的區別:
上述時鐘樹的 SYSCLK 通過 AHB 分頻器分頻後送給 5 大模組使用,其中一個模組就是 Systick,他是 Cortex M3 的標準配備,不是外設。故不需要在 RCC 寄存器組打開他的時鐘。SysTick 有兩大功能:
(1) 可以產生精確延遲計時;
(2) 可以給系統提供一個獨立的時鐘節拍。
SysTick 是一個 24 位遞減計數器,最大值為 2 的 24 次方 = 16777215;啟用後,每經過 1 個系統時鐘週期,計數值就減 1,不為 0 則進行循環等待;直到計數為 0 時,SysTick 計數器會從 LOAD 寄存器中自動重設定時器初始值並繼續計數,這時 COUNTFLAG 值為 1,也會產生一個中斷信號,因此只要知道計數的次數就可以準確得到它的延時時間。
在 STM32F1 的庫函數中,並沒有提供 SysTick 計時器的函數,我們要操作 SysTick 計時器就需要瞭解它的暫存器功能。其實 SysTick 計時器暫存器很簡單,只有 4 個,分別是:
(1) CTRL:SysTick 計時器的控制及狀態暫存器。
(2) LOAD:SysTick 自動重裝載值固定(最大2^24) 的暫存器。
(3) VAL暫存器:SysTick 定时器當時數值暫存器。每個時鐘週期減1,減到0重新裝載LOAD的值。
(4) CALIB 暫存器:SysTick 校準值暫存器
瞭解上述的 SysTick 暫存器後,我們來看一下稍早幾個程式中用到的延遲函式:delay_us(u32 nus)及 delay_ms(u16 nms),程式如下:
時鐘對於單片機是很重要的,因為所有指令都要靠時鐘信號去推動才能執行。而時鐘的頻率決定了處理的速度。STM32 有多個時鐘源,主要原因是 STM32 有多種外設,每種外設有其不同的特性與應用,並不是所有的外設都需要那麼高的頻率,如果全部都用高速時鐘,會讓功耗變大,造成浪費。因此複雜的 STM32 設計多種時鐘源給不同的外設使用,才有了時鐘系統和時鐘樹。
STM32 爲了減少功耗,預設將所有的外設時鐘都設置爲停用(Disable),如果要使用某個外設,才打開其對應的時鐘,這樣可以有效的降低功耗,讓系統更省電。這也就是為何對外設進行初始化前,都要開啟其對應時鐘的原因。
STM32 的時鐘樹(Clock Tree, 如下圖)看起來很複雜,其實很好理解。從 ② 跟 ㊁ 中間的 systick 分成左右兩邊,左邊的部分就是要選定使用那個時鐘源,而右邊是系統時鐘通過 AHB 預分頻器,給相對應的外設設置相對應的時鐘頻率, STM32 有 4 個獨立時鐘源,分成高速和低速,或分成內部及外部:HSI、HSE、LSE、LSI,(如下圖①、③~⑤),② PLL 鎖相環倍頻輸出其實不是自己產生的時鐘源,是由 HSI 倍頻或分頻而來。
名稱 | 說明 | |
---|---|---|
① | HSI 高速內部時鐘(High Speed Internal clock) | 由 STM32 內部的 RC 振盪器產生,頻率為 8 MHz。 |
② | PLL 高速時鐘,鎖相環倍頻輸出(Phase-Locked Loops) | 時鐘輸入源可選擇為 HSI/2 、HSE 或者 HSE/2。倍頻可選擇為 2~16 倍,但是其輸出頻率最大不得超過 72 MHz。 |
③ | HSE 高速外部時鐘(High Speed External Oscillator) | 可接石英/陶瓷諧振器,或者接外部時鐘源,頻率範圍為 4 MHz~16 MHz。 |
④ | LSE 低速外部時鐘(Low Speed External Oscillator) | 接頻率為 32.768kHz 的石英晶體。這個主要是 RTC 的時鐘源。 |
⑤ | LSI 低速內部時鐘(Low Speed Internal clock) | 由 STM32 內部的 RC 振盪器產生,頻率為 40kHz。可用於獨立看門狗 IWDG、實時時鐘 RTC。 |
說明 | |
---|---|
㊀ | USB 的時鐘是來自 PLL 時鐘源,其串行接口需要一個頻率為 48 MHz 的時鐘源。該時鐘源只能從 PLL 輸出端獲取,必須 Enable PLL,並選擇 1.5 分頻(72 MHz)或者 1 分頻(48 MHz)。PLLCLK 在輸入到 SW 前,還流向了 USB 預分頻器,這個分頻器輸出為 USB 外設的時鐘(USBCLK)。 |
㊁ | 是 STM32 的系統時鐘 SYSCLK,它是供STM32 中絕大部分部件工作的時鐘源。系統時鐘可選擇為 PLL 輸出、HSI 或者 HSE。系統時鐘最大頻率為 72MHz。 |
㊂ | 是指其他所有不同的外設。 |
㊃ | 是 RTC 時鐘源,RTC 時鐘源可以選擇 LSI、LSE 以及 HSE 的 128 分頻。 |
㊄ | MCO 是 STM32 的一個時鐘輸出 IO(PA8),它可以選擇一個時鐘信號輸出,可以選擇為 PLL 輸出的 2分頻、HSI、HSE、或者系統時鐘。這個時鐘可以用來給外部其他系統提供時鐘源。 |
從時鐘圖 ㊂ 可以看出,其他所有外設的時鐘最終來源都是 SYSCLK。SYSCLK 通過 AHB 分頻器分頻後送給 5 大模組使用。這些模組包括:
模組 | 說明 |
---|---|
內核總線 | AHB 總線、內核、內部記憶體和 DMA 使用的是 HCLK 時鐘。 |
Tick定時器 | 通過 8 分頻後送給 Cortex 的系統定時器時鐘,也就是 systick 。 |
I2S總線 | 直接送給 Cortex 的空閒運行時鐘 FCLK。 |
APB1外設 | 送給 APB1 分頻器。APB1分頻器可選擇1、2、4、8、16分頻,其輸出一路供APB1外設使用(PCLK1,最大頻率 36MHz),另一路送給通用定時器使用。該倍頻器可選擇 1 或者 2 倍頻,時鐘輸出供定時器 2-7 使用。 |
APB2外設 | 送給 APB2 分頻器。APB2分頻器可選擇1、2、4、8、16分頻,其輸出一路供APB2外設使用(PCLK2,最大頻率 72MHz),另一路送給高級定時器。該倍頻器可選擇 1 或者 2 倍頻,時鐘輸出供定時器 1 和定時器 8 使用。 |
APB1 和 APB2 的區別:
- APB1 連接的是低速外設,包括電源接口、備份接口、CAN、USB、I2C1、I2C2、UART2、UART3、UART4、UART5、SPI2、SP3等。
- APB2 連接的是高速外設,包括 UART1、SPI1、Timer1、ADC1、ADC2、ADC3、所有普通 IO 口(PA~PE)、第二功能 IO 口(AFIO)等。
[SysTick定時器]
之前如要得到時間延遲,會使用 for 迴圈,藉由改變 for 的結束值,得到暫停的時間長短。對 STM32 來說,執行一條指令大約幾十 ns(納秒),由於無法精確知道執行完成的時間,很難計算出延時 N 毫秒的精確值,因此需要重新設計一個新的方法來執行 Delay(N) 的功能。這時 SysTick 定時器就可輕鬆完成延遲的功能。上述時鐘樹的 SYSCLK 通過 AHB 分頻器分頻後送給 5 大模組使用,其中一個模組就是 Systick,他是 Cortex M3 的標準配備,不是外設。故不需要在 RCC 寄存器組打開他的時鐘。SysTick 有兩大功能:
(1) 可以產生精確延遲計時;
(2) 可以給系統提供一個獨立的時鐘節拍。
SysTick 是一個 24 位遞減計數器,最大值為 2 的 24 次方 = 16777215;啟用後,每經過 1 個系統時鐘週期,計數值就減 1,不為 0 則進行循環等待;直到計數為 0 時,SysTick 計數器會從 LOAD 寄存器中自動重設定時器初始值並繼續計數,這時 COUNTFLAG 值為 1,也會產生一個中斷信號,因此只要知道計數的次數就可以準確得到它的延時時間。
在 STM32F1 的庫函數中,並沒有提供 SysTick 計時器的函數,我們要操作 SysTick 計時器就需要瞭解它的暫存器功能。其實 SysTick 計時器暫存器很簡單,只有 4 個,分別是:
- CTRL: 控制和狀態暫存器。
- LOAD: 重裝載值暫存器。(設置後重新載入固定值,最大為 2^24)
- VAL: 當前暫存器。(重新寫入時會把狀態暫存器的 FLAG 清零)
- CALIB: 校準值暫存器。
(1) CTRL:SysTick 計時器的控制及狀態暫存器。
16位元位置 | 名稱 | 類型 | 復位值 | 描述 |
---|---|---|---|---|
16 | COUNTFLAG | R | 0 | 從上次讀取後,SysTick 倒數到 0 時,則為 1,在讀取之後會自動清為 0。 |
2 | CLKSOURCE | R/W | 0 | 時鐘來源,0 = 外部時鐘源(STCLK),1 = 內部時鐘(FCLK)。 |
1 | TICKINT | R/W | 0 | 倒數到 0 的動作,1 = 產生 SysTick 異常請求,0 = 無動作。 |
0 | ENABLE | R/W | 0 | 啟用(Enable) SysTick 定時器,1 = 啟用, 0 = 停用。 |
(2) LOAD:SysTick 自動重裝載值固定(最大2^24) 的暫存器。
位段 | 名稱 | 類型 | 復位值 | 描述 |
---|---|---|---|---|
23:0 | RELOAD | R/W | 0 | 當倒數至 0 時,將被重新裝載的值。 |
(3) VAL暫存器:SysTick 定时器當時數值暫存器。每個時鐘週期減1,減到0重新裝載LOAD的值。
位段 | 名稱 | 類型 | 復位值 | 描述 |
---|---|---|---|---|
23:0 | CURRENT | R/W | 0 | 讀取時返回當前計數器的值,寫他側使之為 0, 同時還會清除在 SysTick 控制及狀態暫存器中的 COUNTFLAG 標誌。 |
(4) CALIB 暫存器:SysTick 校準值暫存器
位段 | 名稱 | 類型 | 復位值 | 描述 |
---|---|---|---|---|
31 | NOREF | R | - | NOREF 旗標,1 = 沒有外部參考時鐘(STCLK不可用),0 = 外部參考時鐘可用。 |
30 | SKEW | R | - | 1 = 校準值不是準確的 10ms,0 = 校準值是準確的 10ms。 |
23:0 | TENMS | R/W | 0 | 10ms 的校準值,晶片設計者通過 Cortex-M3 的輸入信號提供該數值, 若該值讀回 0,則表示無法使用校準功能。 |
瞭解上述的 SysTick 暫存器後,我們來看一下稍早幾個程式中用到的延遲函式:delay_us(u32 nus)及 delay_ms(u16 nms),程式如下:
/********************************************************** * @file delay.c * @brief delay function **********************************************************/ #include "delay.h" #include "sys.h" static u8 fac_us=0; //us延時倍乘數 static u16 fac_ms=0; //ms延時倍乘數 //初始化延遲函數 //SYSTICK的時鐘固定為HCLK時鐘的1/8 //SYSCLK:系統時鐘 void delay_init() { SysTick_CLKSourceConfig(SysTick_CLKSource_HCLK_Div8); //選擇外部時鐘 HCLK/8 fac_us = SystemCoreClock/8000000; //為系統時鐘的1/8 fac_ms = (u16)fac_us*1000; //每個ms需要的systick時鐘數 } //延時nus,nus 為要延時的微秒數. void delay_us(u32 nus) { u32 temp; SysTick->LOAD = nus*fac_us; //時間載入 SysTick->VAL = 0x00; //清空計數器 SysTick->CTRL |= SysTick_CTRL_ENABLE_Msk ; //開始倒數 do { temp = SysTick->CTRL; }while((temp&0x01)&&!(temp&(1<<16))); //等待時間到達 SysTick->CTRL &= ~SysTick_CTRL_ENABLE_Msk; //關閉計數器 SysTick->VAL = 0x00; //清空計數器 } //延時 nms,nms 為要延時的毫秒數 //nms的範圍:SysTick->LOAD為24位暫存器,最大延時為: nms<=0xffffff*8*1000/SYSCLK //SYSCLK單位為Hz, 單位為ms //對72MHz條件下,nms<=1864 void delay_ms(u16 nms) { u32 temp; SysTick->LOAD = (u32)nms*fac_ms; //時間載入(SysTick->LOAD為24bit) SysTick->VAL = 0x00; //清空計數器 SysTick->CTRL |= SysTick_CTRL_ENABLE_Msk ; //開始倒數 do { temp=SysTick->CTRL; }while((temp&0x01)&&!(temp&(1<<16))); //等待時間到達 SysTick->CTRL &= ~SysTick_CTRL_ENABLE_Msk; //關閉計數器 SysTick->VAL = 0x00; //清空計數器 } //延時 s 秒,s 為延時秒數,最大值為65535 void delay_s(u16 s) { while( s-- != 0) { delay_ms(1000); //呼叫1000毫秒的延時 } }delay.h 內容如下:
#ifndef __DELAY_H #define __DELAY_H #include "sys.h" void delay_init(void); void delay_ms(u16 nms); void delay_us(u32 nus); void delay_s(u16 s); #endif
張貼留言