STM32筆記(25):系統時鐘與 Systick 定時器

上一篇文章「STM32筆記(24):七段顯示器顯示數字」在顯示數字時,設定每 1000ms 跳一個數字,這代表一秒鐘。這篇文章要來探討一下 STM32 的時鐘系,與如何利用 SysTick 產生精確的毫秒與微秒。

時鐘對於單片機是很重要的,因為所有指令都要靠時鐘信號去推動才能執行。而時鐘的頻率決定了處理的速度。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。
在開始編寫程式時,預設時鐘爲 72Mhz,是由外部晶振(HSE)提供的 8MHz 通過 PLLXTPRE 分頻器後,進入 PLLSRC 選擇開關,再通過 PLLMUL 鎖相環進行倍頻(x9)後,得到 72MHz 的系統時鐘(SYSCLK)。在經過 AHB 預分頻器對時鐘信號進行分頻,提供給低速外設使用。

說明
㊀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位元位置名稱類型復位值描述
16COUNTFLAGR0從上次讀取後,SysTick 倒數到 0 時,則為 1,在讀取之後會自動清為 0。
2CLKSOURCER/W0時鐘來源,0 = 外部時鐘源(STCLK),1 = 內部時鐘(FCLK)。
1TICKINTR/W0倒數到 0 的動作,1 = 產生 SysTick 異常請求,0 = 無動作。
0ENABLER/W0啟用(Enable) SysTick 定時器,1 = 啟用, 0 = 停用。

(2) LOAD:SysTick 自動重裝載值固定(最大2^24) 的暫存器。
位段名稱類型復位值描述
23:0RELOADR/W0當倒數至 0 時,將被重新裝載的值。

(3) VAL暫存器:SysTick 定时器當時數值暫存器。每個時鐘週期減1,減到0重新裝載LOAD的值。
位段名稱類型復位值描述
23:0CURRENTR/W0讀取時返回當前計數器的值,寫他側使之為 0,
同時還會清除在 SysTick 控制及狀態暫存器中的 COUNTFLAG 標誌。

(4) CALIB 暫存器:SysTick 校準值暫存器
位段名稱類型復位值描述
31NOREFR-NOREF 旗標,1 = 沒有外部參考時鐘(STCLK不可用),0 = 外部參考時鐘可用。
30SKEWR-1 = 校準值不是準確的 10ms,0 = 校準值是準確的 10ms。
23:0TENMSR/W010ms 的校準值,晶片設計者通過 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


[參考資料]


Post a Comment

較新的 較舊