(五)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 要好好学习哦!