STM32學習筆記(5)——系統定時器SysTick


單獨拿出來講的一個內核外設(所以不要期望在STM32中文參考手冊找到它!即使找到也只會叫你看cm3內核編程手冊),說明它真的很重要。

一、系統定時器Systick

1. SysTick簡介

SysTick是一個24位的系統節拍定時器,具有自動重載和溢出中斷功能,所有基於Cortex M3或Cortex M4處理器的微控制器都有這個定時器。

Systick定時器常用來做延時,或者用來做實時系統的心跳時鍾。這樣可以節省MCU資源,不用浪費一個定時器。比如UCOS中,分時復用,需要一個最小的時間戳,一般在STM32+UCOS系統中,都采用Systick做UCOS心跳時鍾。

它一個24位的倒計數定時器(意味着有2^24個時間間隔),計到0時,將從RELOAD寄存器中自動重裝載定時初值。只要不把它在SysTick控制及狀態寄存器中的使能位清除,就永不停息,即使在睡眠模式下也能工作。

學過51單片機的同學應該對計數器比較熟悉了。

SysTick在NVIC的中斷向量號為6(可對照STM32中文參考手冊9.1.2節中斷和異常向量中表格的灰色部分)。

2. SysTick相關寄存器

SysTick有4個寄存器:CTRL(控制和狀態寄存器)、LOAD(自動重裝載除值寄存器)、VAL(當前值寄存器)、CALIB(校准值寄存器)(如圖所示)。

關於這4個寄存器的參考資料為 《 STM32F10xxx Cortex-M3編程手冊》(STM32F10xxx/20xxx/21xxx/L1xxxx
Cortex ® -M3 programming manual)
(沒找到中文版的,自己生啃一下英文吧),4.5節SysTick timer (STK)。4.5.6節還有SysTick寄存器地圖。

由於篇幅夠多,下面我們來一一介紹這4個寄存器(其實引用了上述手冊的介紹):

(1)SysTick control and status register (STK_CTRL)

16位(COUNTFLAG): SysTick數到0后,該位為1;其他情況為0。如果讀取該位,也會置0。說白了就是用來標志是不是數完了。

2位(CLKSOURCE): 0為外部時鍾源(STCLK)(HCLK的1/8),1為內部時鍾源(FCLK)(HCLK)。用來選擇時鍾源的。順便提一句,HCLK用的是AHB時鍾。

1位(TICKINT): 1為計數器數到零時產生SysTick中斷請求,0為不產生請求。

0位(ENABLE): 用來使能的。

其余位保留。

(2)SysTick reload value register (STK_LOAD)

23位-0位(RELOAD): 存儲的是一個值,當計數到0時,將重新裝載該值,開始新一輪倒數。

其余位保留。

(3)SysTick current value register (STK_VAL)

23位-0位(CURRENT): 跟上面的有點像,也是存儲一個值。但是,當你讀取它時,寄存器會返回這個值(就是讀取了這個值);當你寫它時會自動清零,同時CTRL寄存器的第16位(COUNTFLAG)置0。

其余位保留。

(4)SysTick calibration value register (STK_CALIB)

這個寄存器似乎比較少關注到,對於用戶來講應該不常用?

31位(NOREF): 1為沒有外部參考時鍾(外部時鍾源STCLK不可用),0為外部參考時鍾可用。

30位(SKEW): 1為校准值不是准確的10ms,0為校准值是准確的10ms。

23位-0位(TENMS): 存儲的是10ms的間隔倒計時的格數。芯片設計者應該提供這數值,若該值為0,則無法使用校准功能。

其余位保留。

在頭文件core_cm3.h中有結構體定義:

typedef struct
{
  __IO uint32_t CTRL;                         /*!< Offset: 0x00  SysTick Control and Status Register */
  __IO uint32_t LOAD;                         /*!< Offset: 0x04  SysTick Reload Value Register       */
  __IO uint32_t VAL;                          /*!< Offset: 0x08  SysTick Current Value Register      */
  __I  uint32_t CALIB;                        /*!< Offset: 0x0C  SysTick Calibration Register        */
} SysTick_Type;

3. 詳解SysTick_Config()函數

在使用SysTick之前,用戶必須調用SysTick_Config()函數對其進行配置。這個函數是怎樣進行配置的呢?我們可以看看頭文件core_cm3.h 的1694行:

static __INLINE uint32_t SysTick_Config(uint32_t ticks)
{ 
  if (ticks > SysTick_LOAD_RELOAD_Msk)  return (1);            /* Reload value impossible */
                                                               
  SysTick->LOAD  = (ticks & SysTick_LOAD_RELOAD_Msk) - 1;      /* set reload register */
  NVIC_SetPriority (SysTick_IRQn, (1<<__NVIC_PRIO_BITS) - 1);  /* set Priority for Cortex-M0 System Interrupts */
  SysTick->VAL   = 0;                                          /* Load the SysTick Counter Value */
  SysTick->CTRL  = SysTick_CTRL_CLKSOURCE_Msk | 
                   SysTick_CTRL_TICKINT_Msk   | 
                   SysTick_CTRL_ENABLE_Msk;                    /* Enable SysTick IRQ and SysTick Timer */
  return (0);                                                  /* Function successful */
}

uint32_t SysTick_Config(uint32_t ticks)

函數形參ticks為兩個中斷之間的脈沖數(number of ticks between two interrupts),即相隔ticks個時鍾周期會引起一次中斷。

if (ticks > SysTick_LOAD_RELOAD_Msk) return (1);

判斷ticks是否符合規則,這個是什么規則呢?就是判斷ticks是否大於SysTick_LOAD_RELOAD_Msk,如果大於,則返回1(failed),表示不符合規則。

SysTick_LOAD_RELOAD_Msk是一個宏,在文件388行可以找到:

#define SysTick_LOAD_RELOAD_Pos             0 
#define SysTick_LOAD_RELOAD_Msk            (0xFFFFFFul << SysTick_LOAD_RELOAD_Pos) 

0xFFFFFF在十進制中是2^24(=16777216),其中,ul的意思是無符號long型整數(
unsigned long int)
。然后我們就知道,ticks最大值不能超過2^24(因為是24位系統定時器),否則就配置失敗。

SysTick->LOAD = (ticks & SysTick_LOAD_RELOAD_Msk) - 1; /* set reload register */

這里就是將ticks寫入load寄存器,ticks是計數最開始的值。減去一的原因是計數從0結束。

NVIC_SetPriority (SysTick_IRQn, (1<<__NVIC_PRIO_BITS) - 1);

NVIC中斷配置,SysTick_IRQn在頭文件stm32f10x.h可以找到:

 SysTick_IRQn                = -1,     /*!< 15 Cortex-M3 System Tick Interrupt*/

宏定義__NVIC_PRIO_BITS可以在本文件core_cm3.h找到:

#define __NVIC_PRIO_BITS          4 /*!< STM32 uses 4 Bits for the Priority Levels*/

因此(1<<__NVIC_PRIO_BITS) - 1的意思是1左移4位(即16),表達式的值為15(注意二進制表示為1111),這里面的4是因為(寄存器NVIC->IPRx)使用4個位來配置中斷優先級。

所以SysTick的優先級是最低的嗎?由於沒有指定優先級分組,所以這就要看怎么分了。

假如分組為1,則將1111分為兩組,前面組為搶占優先級,只有一個1(說明搶占優先級為1),而后面組為響應優先級,為111(說明響應優先級為7),需要注意到這里1和0的個數表示了二進制的數,進而指明了優先級別的高低。

假如分組為2,則將1111分為兩組,前面組為搶占優先級,有11(說明搶占優先級為3),而后面組為響應優先級,為11(說明響應優先級為3)。

假如分組為3,則將1111分為兩組,前面組為搶占優先級,有111(說明搶占優先級為7),而后面組為響應優先級,為1(說明響應優先級為1)。以此類推,所以SysTick的優先級是不一定的,只要你去配置,你想怎樣都行。

SysTick->VAL = 0;

將0載入到val寄存器中。

SysTick->CTRL = SysTick_CTRL_CLKSOURCE_Msk | SysTick_CTRL_TICKINT_Msk | SysTick_CTRL_ENABLE_Msk;

配置時鍾源、中斷請求和使能。

4. SysTick相關的函數

SysTick_CLKSourceConfig()    //Systick時鍾源選擇,在misc.c中

SysTick_Config(uint32_t ticks) //初始化systick,時鍾為HCLK,並開啟中斷,在core_m3.h中

void SysTick_Handler(void);
//Systick中斷服務函數,需要用戶自行編寫

二、延時函數

使用SysTick寫出來的延時函數,其精度要比軟件延時要高。不過這個函數要自己寫。

下面是一個寫好的微妙級別的延時函數,我們來分析一下:

void Systick_Delay_us(uint32_t us)
{
   uint32_t i;
   SysTick_Config(72);
   
   for( i = 0; i < us; i++)
   {
   	while( !((SysTick -> CTRL) & (1 << 16)) );
   }
   SysTick -> CTRL &= ~SysTick_CTRL_ENABLE_Msk;
   
}

SysTick_Config(72);

配置SysTick時鍾。這里為什么是72呢?有個公式:t = reload * ( 1/clk ),clk為內核時鍾頻率,reload為LOAD寄存器的值。在這個程序中,Clk = 72MHz,reload = 72,因此 t = (72) *(1 / 72 MHz)= 1us。

while( !((SysTick -> CTRL) & (1 << 16)) );

這里有必要說明一下(SysTick -> CTRL) & (1 << 16)是什么意思。SysTick -> CTRL是CTRL寄存器的首地址,1<<16為左移16位,兩者進行與運算,正好得到CTRL寄存器第16位地址,即COUNTFLAG,因此這條語句是用來判斷計數是否到0。如果沒到0,while繼續循環。每跳出一次while循環,說明已經過去了1us的時間,而for循環則循環了指定次數的1us。

SysTick -> CTRL &= ~SysTick_CTRL_ENABLE_Msk;

每一次使用完SysTick后要記得關閉。

三、簡單例程

1. 不使用中斷服務函數

//在systick.c中
void Systick_Delay_us(uint32_t us)
{
	uint32_t i;
	SysTick_Config(72);
	
	for( i = 0; i < us; i++)
	{
		while( !((SysTick -> CTRL) & (1 << 16)) );
	}
	SysTick -> CTRL &= ~SysTick_CTRL_ENABLE_Msk;
	
}

void Systick_Delay_ms(uint32_t ms)
{
	uint32_t i;
	SysTick_Config(72000);
	
	for( i = 0; i < ms; i++)
	{
		while( !((SysTick -> CTRL) & (1 << 16)) );
	}
	SysTick -> CTRL &= ~SysTick_CTRL_ENABLE_Msk;
	
}


//在main.c中
//實現功能:每隔500ms,LED0和LED1交錯亮滅一次
 while(1)
{
	  GPIO_ResetBits(GPIOB,GPIO_Pin_5);
	  GPIO_SetBits(GPIOE,GPIO_Pin_5);
	  Systick_Delay_ms(500);
	  GPIO_SetBits(GPIOB,GPIO_Pin_5);
	  GPIO_ResetBits(GPIOE,GPIO_Pin_5);
	  Systick_Delay_ms(500);
}

2. 使用中斷服務函數

//systick.h
#ifndef __SYSTICK_H
#define __SYSTICK_H

#include "stm32f10x.h"
#include "core_cm3.h"

void Systick_Delay_ms(uint32_t ms);
void Systick_Delay_us(uint32_t us);

void SysTick_Init(void);
void Delay_ms(uint32_t ms);

#endif


//systick.c
#include "systick.h"

/* use interrupt */
__IO uint32_t TimingDelay;

void SysTick_Init(void)
{
	if(SysTick_Config(72000) == 1)
    {
       while(1);
    }
	SysTick->CTRL &= ~SysTick_CTRL_ENABLE_Msk;
}

void Delay_ms(uint32_t ms)
{
	TimingDelay = ms;
	SysTick->CTRL |= SysTick_CTRL_ENABLE_Msk;
	while(TimingDelay != 0);
}


//stm32f10x_it.h
#include "stm32f10x_it.h" 
extern uint32_t TimingDelay; //看見這玩意我就惡心

void SysTick_Handler(void)
{
	if(TimingDelay != 0x00)
	{
		TimingDelay--;
	}
}


//main.c
SysTick_Init();	 
while(1)
{
	  GPIO_ResetBits(GPIOB,GPIO_Pin_5);
	  GPIO_SetBits(GPIOE,GPIO_Pin_5);
	  Delay_ms(500);
	  GPIO_SetBits(GPIOB,GPIO_Pin_5);
	  GPIO_ResetBits(GPIOE,GPIO_Pin_5);
	  Delay_ms(500);
}

上面這種寫法用了extern全局變量,個人感覺很別扭,但是中斷服務函數是沒有形參的,所以還有一種折中的寫法(自己寫一個函數,然后中斷服務函數去調用):

//systick.h
#ifndef __SYSTICK_H
#define __SYSTICK_H

#include "stm32f10x.h"
#include "core_cm3.h"

void Systick_Delay_ms(uint32_t ms);
void Systick_Delay_us(uint32_t us);

void SysTick_Init(void);
void Delay_ms(uint32_t ms);
void timeDec(void); //自己寫的函數,待會服務函數去調用

static __IO uint32_t TimingDelay;

#endif


//systick.c
#include "systick.h"

/* use interrupt */
void SysTick_Init(void)
{
	if(SysTick_Config(72000) == 1)
    {
       while(1);
    }
	SysTick->CTRL &= ~SysTick_CTRL_ENABLE_Msk;
}

void Delay_ms(uint32_t ms)
{
	TimingDelay = ms;
	SysTick->CTRL |= SysTick_CTRL_ENABLE_Msk;
	while(TimingDelay != 0);
}


void timeDec(void)
{
	if(TimingDelay != 0x00)
	{
		TimingDelay--;
	}
}


//stm32f10x_it.h
#include "stm32f10x_it.h" 

void SysTick_Handler(void)
{
	timeDec(); //調用自己寫的函數
}

再次,我個人習慣是,extern只有到萬不得已才使用,因為這玩意太破壞文件之間的耦合度了,而且被多個文件使用,不太好追蹤源頭。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM