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

較新的 較舊