(五)STM32學習之SysTick
1、SysTick簡介
SysTick:系統定時器,24位計數器,只能遞減,存在於內核,嵌套在NVIC中,所有的Cortex-M內核的單片機都具有這個定時器。遞減計數器在時鐘的驅動下,從reload初值開始往下遞減計數到0,產生中斷和置位COUNTFLAG標誌。然後又從reload k值開始重新遞減計數,如此循環。
SysTick定時時間計算:T = Reload * (1 / Clk) 即總次數 * 一次所需要的時間
例:Clk = 72M
Reload = 72時,T = 72*(1/72M)=1us
Reload = 72000時,T = 72000*(1/72M)=1ms
2、SysTick寄存器
固件庫core_cm3.h中SysTick寄存器定義:
typedef struct
{
__IO uint32_t CTRL; /*!< 偏移量: 0x00 SysTick控制和狀態寄存器*/
__IO uint32_t LOAD; /*!< 偏移量: 0x04 SysTick重載值寄存器 */
__IO uint32_t VAL; /*!< 偏移量: 0x08 SysTick當前值寄存器 */
__I uint32_t CALIB; /*!< 偏移量: 0x0C SysTick校準寄存器 , 此寄存器不常用 */
} SysTick_Type;
固件庫core_cm3.h中SysTick配置:
static __INLINE uint32_t SysTick_Config(uint32_t ticks)
{
//判斷tick的值是否大於2^24,如果大於,則不符合規則
if (ticks > SysTick_LOAD_RELOAD_Msk) return (1);
//配置reload寄存器的初始值,
SysTick->LOAD = (ticks & SysTick_LOAD_RELOAD_Msk) - 1;
//配置中斷優先級爲 (1 << 4) - 1 = 15,優先級爲最低,數值越大優先級越低
NVIC_SetPriority (SysTick_IRQn, (1<<__NVIC_PRIO_BITS) - 1);
//配置counter計數器的值
SysTick->VAL = 0;
//配置systick的時鐘爲72M
//使能中斷
//使能systick
SysTick->CTRL = SysTick_CTRL_CLKSOURCE_Msk |
SysTick_CTRL_TICKINT_Msk |
SysTick_CTRL_ENABLE_Msk;
return (0);
}
3、SysTick中斷優先級
1-STM32裏面無論是內核還是外設都是使用4個二進制位來表示中斷優先級。
2-中斷優先級的分組對內核和外設同樣適用。當比較的時候,只需要把內核外設的中斷優先級的四個位按照外設的中斷優先級來分組來解析即可,即人爲的分出搶佔優先級和子優先級。
3-當內核和外設軟件優先級相同時,看優先級的硬件編號,內核優先級硬件編號默認小於外設,即硬件優先級更高。
下面結合具體例子給予說明:
固件庫misc.h中斷分組:
下表列出了搶佔優先級和子優先級的允許值NVIC_PriorityGroupConfig函數執行的優先級分組配置
============================================================================================================================
NVIC_PriorityGroup | NVIC_IRQChannelPreemptionPriority | NVIC_IRQChannelSubPriority | Description
============================================================================================================================
NVIC_PriorityGroup_0 | 0 | 0-15 | 0 位用於搶佔優先級
| | | 4 位用於子優先級
----------------------------------------------------------------------------------------------------------------------------
NVIC_PriorityGroup_1 | 0-1 | 0-7 | 1 位用於搶佔優先級
| | | 3 位用於子優先級
----------------------------------------------------------------------------------------------------------------------------
NVIC_PriorityGroup_2 | 0-3 | 0-3 | 2 位用於搶佔優先級
| | | 2 位用於子優先級
----------------------------------------------------------------------------------------------------------------------------
NVIC_PriorityGroup_3 | 0-7 | 0-1 | 3 位用於搶佔優先級
| | | 1 位用於子優先級
----------------------------------------------------------------------------------------------------------------------------
NVIC_PriorityGroup_4 | 0-15 | 0 | 4 位用於搶佔優先級
| | | 0 位用於子優先級
============================================================================================================================
例如:將systick中斷優先級設置爲15,若採用中斷分組2,則由上表可知,搶佔優先級和子優先級均由兩位二進制表示範圍均爲0-3,將15寫爲二進制爲 1111,人爲分出得到搶佔優先級爲3,子優先級爲3。
若採用中斷分組3,則由上表可知,3位二進制表示搶佔優先級,範圍0-7,1位二進制表示子優先級,範圍0-1,將15寫爲二進制爲 1111,人爲分出得到搶佔優先級爲7,子優先級爲1。
4、匿名基於SysTick時分調度器代碼解析
Systick.H:
#ifndef __BSP_SYSTICK_H
#define __BSP_SYSTICK_H
#include "stm32f10x.h"
#define TICK_PER_SECOND 1000
#define TICK_US (1000000/TICK_PER_SECOND)
typedef struct
{
u8 init_flag;
u32 old;
u32 now;
u32 dT;
} _get_dT_st;
void TIM_INIT(void);
void sys_time(void);
u16 Get_Time(u8,u16,u16);
u32 Get_Delta_T(_get_dT_st * );
u32 SysTick_GetTick(void);
void Cycle_Time_Init(void);
extern volatile uint32_t sysTickUptime;
void Delay_us(uint32_t);
void Delay_ms(uint32_t);
void SysTick_Configuration(void);
uint32_t GetSysTime_us(void);
#endif /*__BSP_SYSTICK_H*/
Systick.C:
#include "bsp_systick.h"
volatile uint32_t sysTickUptime = 0;
void SysTick_Configuration ( void )
{
RCC_ClocksTypeDef rcc_clocks;
uint32_t cnts;
RCC_GetClocksFreq ( &rcc_clocks );
cnts = ( uint32_t ) rcc_clocks.HCLK_Frequency / TICK_PER_SECOND; //rcc_clocks.HCLK_Frequency = 72M 72000 000 / 1000 = 72000
cnts = cnts / 8; //72000 / 8 = 9000
SysTick_Config ( cnts ); //設置重裝載值爲9000
SysTick_CLKSourceConfig ( SysTick_CLKSource_HCLK_Div8 ); //HCLK_Frequency 8分頻 -> 9M T = 9000 * ( 1 / 9000 000 ) = 1ms 即一毫秒進一次中斷
}
uint32_t GetSysTime_us ( void )
{
register uint32_t ms; //register修飾符暗示編譯程序相應的變量將被頻繁地使用,如果可能的話,應將其保存在CPU的寄存器中,以加快其存儲速度
u32 value;
do
{
ms = sysTickUptime; //獲得系統當前以ms爲單位的時間
value = ms * TICK_US + ( SysTick->LOAD - SysTick->VAL ) * TICK_US / SysTick->LOAD; //系統定時器是一個遞減計數器 未滿1000us的部分us = ( (LOAD - VAL) / LOAD ) * 1000
}
while(ms != sysTickUptime);//爲真則重複循環,爲假則跳出循環
return value;
}
void Delay_us ( uint32_t us )
{
uint32_t now = GetSysTime_us();
while ( GetSysTime_us() - now < us );
}
void Delay_ms ( uint32_t ms )
{
while ( ms-- )
Delay_us ( 1000 );
}
u32 systime_ms;
void sys_time()
{
systime_ms++;
}
u32 SysTick_GetTick(void)
{
return systime_ms;
}
u32 Get_Delta_T(_get_dT_st *data) //得到變化的時間
{
data->old = data->now; //上一次的時間
data->now = GetSysTime_us(); //本次的時間
data->dT = ( ( data->now - data->old ) );//間隔的時間(週期)
if(data->init_flag == 0)
{
data->init_flag = 1;//第一次調用時輸出 0 作爲初始化,以後正常輸出
return 0;
}
else
{
return data->dT;
}
}
void SysTick_Handler(void)
{
sysTickUptime++;
sys_time();
}
Scheduler.H文件:
#ifndef __BSP_SHCEDULER_H
#define __BSP_SHCEDULER_H
#include "stm32f10x.h"
typedef struct
{
void(*task_func)(void);
uint16_t rate_hz;
uint16_t interval_ticks;
uint32_t last_run;
}sched_task_t;
void Scheduler_Setup(void);
void Scheduler_Run(void);
#endif /*__BSP_SHCEDULER_H*/
Scheduler.C文件:
u32 test_dT_1000hz[3],test_rT[6];
static void Loop_1000Hz(void) //1ms執行一次
{
test_dT_1000hz[0] = test_dT_1000hz[1];
test_rT[3] = test_dT_1000hz[1] = GetSysTime_us ();
test_dT_1000hz[2] = (u32)(test_dT_1000hz[1] - test_dT_1000hz[0]) ;
//////////////////////////////////////////////////////////////////////
// 添加被執行代碼
//////////////////////////////////////////////////////////////////////
test_rT[4]= GetSysTime_us ();
test_rT[5] = (u32)(test_rT[4] - test_rT[3]) ;
}
static void Loop_500Hz(void) //2ms執行一次
{
//////////////////////////////////////////////////////////////////////
// 添加被執行代碼
//////////////////////////////////////////////////////////////////////
}
static void Loop_200Hz(void) //5ms執行一次
{
//////////////////////////////////////////////////////////////////////
// 添加被執行代碼
//////////////////////////////////////////////////////////////////////
}
static void Loop_100Hz(void) //10ms執行一次
{
test_rT[0]= GetSysTime_us ();
//////////////////////////////////////////////////////////////////////
// 添加被執行代碼
//////////////////////////////////////////////////////////////////////
test_rT[1]= GetSysTime_us ();
test_rT[2] = (u32)(test_rT[1] - test_rT[0]) ;
}
static void Loop_50Hz(void) //20ms執行一次
{
//////////////////////////////////////////////////////////////////////
// 添加被執行代碼
//////////////////////////////////////////////////////////////////////
}
static void Loop_20Hz(void) //50ms執行一次
{
//////////////////////////////////////////////////////////////////////
// 添加被執行代碼
//////////////////////////////////////////////////////////////////////
}
static void Loop_2Hz(void) //500ms執行一次
{
//////////////////////////////////////////////////////////////////////
// 添加被執行代碼
//////////////////////////////////////////////////////////////////////
}
//系統任務配置,創建不同執行頻率的“線程”
static sched_task_t sched_tasks[] =
{
{Loop_1000Hz,1000, 0, 0},
{Loop_500Hz , 500, 0, 0},
{Loop_200Hz , 200, 0, 0},
{Loop_100Hz , 100, 0, 0},
{Loop_50Hz , 50, 0, 0},
{Loop_20Hz , 20, 0, 0},
{Loop_2Hz , 2, 0, 0},
};
//根據數組長度,判斷線程數量
#define TASK_NUM (sizeof(sched_tasks)/sizeof(sched_task_t))
void Scheduler_Setup(void)
{
uint8_t index = 0;
//初始化任務表
for(index=0;index < TASK_NUM;index++)
{
//計算每個任務的延時週期數
sched_tasks[index].interval_ticks = TICK_PER_SECOND/sched_tasks[index].rate_hz;
//最短週期爲1,也就是1ms
if(sched_tasks[index].interval_ticks < 1)
{
sched_tasks[index].interval_ticks = 1;
}
}
}
//這個函數放到main函數的while(1)中,不停判斷是否有線程應該執行
void Scheduler_Run(void)
{
uint8_t index = 0;
//循環判斷所有線程,是否應該執行
for(index=0;index < TASK_NUM;index++)
{
//獲取系統當前時間,單位MS
uint32_t tnow = SysTick_GetTick();
//進行判斷,如果當前時間減去上一次執行的時間,大於等於該線程的執行週期,則執行線程
if(tnow - sched_tasks[index].last_run >= sched_tasks[index].interval_ticks)
{
//更新線程的執行時間,用於下一次判斷
sched_tasks[index].last_run = tnow;
//執行線程函數,使用的是函數指針
sched_tasks[index].task_func();
}
}
}
此博客僅用於自身學習,存放,歸納筆記使用 Arelir 要好好學習哦!