1.滴答定时器 SysTick
- 滴答定时器是一个24位的倒计数定时器,当计数到0时,将从RELOAD寄存器中自动重装载定时器初值,只要不关闭SysTick使能位,就将永久不息。
- SysTick的最大使命:定期的产生异常请求作为系统的时基。
//初始化延迟函数
//当使用OS的时候,此函数会初始化OS的时钟节拍
//SYSTICK的时钟固定为AHB时钟的1/8
//SYSCLK:系统时钟频率
void delay_init(u8 SYSCLK)
{
#if SYSTEM_SUPPORT_OS //如果需要支持OS.
u32 reload;
#endif
SysTick_CLKSourceConfig(SysTick_CLKSource_HCLK_Div8);
fac_us=SYSCLK/8; //不论是否使用OS,fac_us都需要使用
#if SYSTEM_SUPPORT_OS //如果需要支持OS.
reload=SYSCLK/8; //每秒钟的计数次数 单位为M
reload*=1000000/delay_ostickspersec; //根据delay_ostickspersec设定溢出时间,溢出所需的计数次数
//reload为24位寄存器,最大值:16777216,在168M下,约合0.7989s左右
fac_ms=1000/delay_ostickspersec; //代表OS可以延时的最少单位
SysTick->CTRL|=SysTick_CTRL_TICKINT_Msk; //开启SYSTICK中断
SysTick->LOAD=reload; //每1/delay_ostickspersec秒中断一次
SysTick->CTRL|=SysTick_CTRL_ENABLE_Msk; //开启SYSTICK
#else
fac_ms=(u16)fac_us*1000; //非OS下,代表每个ms需要的systick时钟数
#endif
}
PS:
reload = SYSCLK/8 = 168MHz/8 = 21MHz,每us 可计数21次
delay_ostickspersec :OS_TICKS_PER_SEC (200)
溢出所需的计数次数:reload = 1/200100000021 = 105000
//systick中断服务函数,使用OS时用到
void SysTick_Handler(void)
{
if(delay_osrunning==1) //OS开始跑了,才执行正常的调度处理
{
OSIntEnter(); //进入中断
OSTimeTick(); //调用ucos的时钟服务程序
OSIntExit(); //触发任务切换软中断
}
}
PS: OSTimeTick在时钟节拍到来时,检查每个任务的任务控制块中 OSTCBDly-1 后是否为0,如果为0 则刚才此任务为挂起状态,现在应该为就绪状态。
OSTCBDly :标志任务的延时时间
2.os_cpu_a.asm 文件详解
IMPORT OSRunning
IMPORT OSPrioCur
IMPORT OSPrioHighRdy
IMPORT OSTCBCur
IMPORT OSTCBHighRdy
IMPORT OSIntNesting
IMPORT OSIntExit
IMPORT OSTaskSwHook
EXPORT OSStartHighRdy
EXPORT OSCtxSw
EXPORT OSIntCtxSw
EXPORT OS_CPU_SR_Save
EXPORT OS_CPU_SR_Restore
EXPORT PendSV_Handler
PS:
IMPORT 定义表示 是一个外部变量标号,不是在本程序定义的;
EXPORT 定义表示这些函数是在本文中定义的,供其他文件调用;
NVIC_INT_CTRL EQU 0xE000ED04 ; 中断控制寄存器
NVIC_SYSPRI2 EQU 0xE000ED22 ; 系统优先级寄存器(2)
NVIC_PENDSV_PRI EQU 0xFFFF ; PendSV中断和系统节拍中断
; (都为最低,0xff).
NVIC_PENDSVSET EQU 0x10000000 ; 触发软件中断的值.
PS:
① EQU 和 C中的define 相同,定义一个宏;
② NVIC_PENDSV_PRI 为PendSV和Systick的中断优先级,0xFF都为最低;
③ NVIC_PENDSVSET 可以触发软件中断,通过中断控制寄存器(NVIC_INT_CTRL)的bit28 写1来触发,因此值为0x10000000 ;
OS_CPU_SR_Save ;关中断
MRS R0, PRIMASK ;读取PRIMASK到R0,R0为返回值
CPSID I ;PRIMASK=1,关中断(NMI和硬件FAULT可以响应)
BX LR ;返回
OS_CPU_SR_Restore ;开中断
MSR PRIMASK, R0 ;读取R0到PRIMASK中,R0为参数
BX LR ;返回
PS:
OS_CPU_SR_Save 和 OS_CPU_SR_Restore 开关中断的汇编代码
;/**************************************************************************************
;* 函数名称: OSStartHighRdy
;*
;* 功能描述: 使用调度器运行第一个任务
;*
;* 参 数: None
;*
;* 返 回 值: None
;**************************************************************************************/
OSStartHighRdy
LDR R4, =NVIC_SYSPRI2 ; set the PendSV exception priority
LDR R5, =NVIC_PENDSV_PRI
STR R5, [R4]
MOV R4, #0 ; set the PSP to 0 for initial context switch call
MSR PSP, R4
LDR R4, =OSRunning ; OSRunning = TRUE
MOV R5, #1
STRB R5, [R4]
;切换到最高优先级的任务
LDR R4, =NVIC_INT_CTRL ;rigger the PendSV exception (causes context switch)
LDR R5, =NVIC_PENDSVSET
STR R5, [R4]
CPSIE I ;enable interrupts at processor level
OSStartHang
B OSStartHang ;should never get here
PS: OSStartHighRdy 是由OSStart() 调用,用来开启多任务的。
;/**************************************************************************************
;* 函数名称: OSCtxSw
;*
;* 功能描述: 任务级上下文切换
;*
;* 参 数: None
;*
;* 返 回 值: None
;***************************************************************************************/
OSCtxSw
PUSH {R4, R5}
LDR R4, =NVIC_INT_CTRL ;触发PendSV异常 (causes context switch)
LDR R5, =NVIC_PENDSVSET
STR R5, [R4]
POP {R4, R5}
BX LR
;/**************************************************************************************
;* 函数名称: OSIntCtxSw
;*
;* 功能描述: 中断级任务切换
;*
;* 参 数: None
;*
;* 返 回 值: None
;***************************************************************************************/
OSIntCtxSw
PUSH {R4, R5}
LDR R4, =NVIC_INT_CTRL ;触发PendSV异常 (causes context switch)
LDR R5, =NVIC_PENDSVSET
STR R5, [R4]
POP {R4, R5}
BX LR
NOP
PS:
①OSCtxSw 和 OSIntCtxSw 用来做任务切换,它们都只是触发1个PendSV中断,具体切换过程在PendSV中断服务函数中进行。
②OSCtxSw 和 OSIntCtxSw 意义不同:
OSCtxSw :任务级切换,如从任务A切换到任务B
OSIntCtxSw :中断级切换,从中断退出时切换到一个任务中
;/**************************************************************************************
;* 函数名称: OSPendSV
;*
;* 功能描述: OSPendSV is used to cause a context switch.
;*
;* 参 数: None
;*
;* 返 回 值: None
;***************************************************************************************/
PendSV_Handler
CPSID I ;关中断,任务切换期间要关中断
MRS R0, PSP ;R0 = PSP
CBZ R0, PendSV_Handler_Nosave ; 如果PSP 为0 就转移到 PendSV_Handler_Nosave (1)
;任务如果使用FPU 就保存S16~S31寄存器
TST R14, #0x10 ;(2)
IT EQ
VSTMDBEQ R0!, {S16-S31}
SUBS R0, R0, #0x20 ;R0 -= 0x20
STM R0, {R4-R11} ;保存剩余R4~R11寄存器
LDR R1, =OSTCBCur ; R1= &OSTCBCur
LDR R1, [R1] ;R1 = *R1 即 R1 = OSTCBCur
STR R0, [R1] ; *R1 = R0 即 OSTCBCur = SP
; At this point, entire context of process has been saved
PendSV_Handler_Nosave
PUSH {R14} ; 保存R14的值,后面要调用函数
LDR R0, =OSTaskSwHook ;R0 = &OSTaskSwHook
BLX R0 ;调用OSTaskSwHook()函数
POP {R14} ;恢复R14
LDR R0, =OSPrioCur ; R0 = &OSPrioCur
LDR R1, =OSPrioHighRdy ;R1 = &OSPrioHighRdy
LDRB R2, [R1] ; R2 = *R1即 R2 = OSPrioHighRdy
STRB R2, [R0] ; *R0 = R2 即OSPrioCur = OSPrioHighRdy
LDR R0, =OSTCBCur ; R0 = &OSPrioCur
LDR R1, =OSTCBHighRdy ; R1 = &OSPrioHighRdy
LDR R2, [R1] ;R2 = *R1即 R2 = OSPrioHighRdy
STR R2, [R0] ; *R0 = R2 即OSPrioCur = OSPrioHighRdy
LDR R0, [R2] ; R0 = *R2即 R0 = OSPrioHighRdy R0是新任务的SP(3)
LDM R0, {R4-R11} ; 从堆栈中恢复R4~R11
ADDS R0, R0, #0x20 ; R0 += 0x20
;任务如果使用FPU 将S16~S31寄存器恢复出来
TST R14, #0x10 ;(4)
IT EQ
VLDMIAEQ R0!, {S16-S31}
MSR PSP, R0 ; PSP = R0 用新任务的SP加载PSP
ORR LR, LR, #0x04 ; 确保LR的位2为1,返回后使用进程堆栈(5)
CPSIE I ;开中断
BX LR ; 中断返回
NOP
end
PS:
(1) 如果PSP 为0,说明是第一次做任务切换,而任务创建的时候会调用堆栈初始化函数OSTaskStkInit() 来初始化堆栈,在初始化过程中已经做了入栈处理,所以这里就不需要再做入栈处理,直接跳转到PendSV_Handler_Nosave 。
(2) EXC_RETURN的bit4 用来表示是否使用FPU,故通过判断R14的bit4 来决定是否将S16~S31寄存器做入栈处理。
(3) SP指向的是要运行的最高优先级任务
(4) 同(2)
(5) 因为进入中断使用的是MSP,而退出中断的时候使用的是PSP,此处须将LR的位2置1.
3.os_cpu.h 文件详解
定义了一些数据类型
typedef unsigned char BOOLEAN;
typedef unsigned char INT8U; /* 无符号8位数 */
typedef signed char INT8S; /* 有符号8位数 */
typedef unsigned short INT16U; /* 无符号16位数 */
typedef signed short INT16S; /* 有符号16位数 */
typedef unsigned int INT32U; /* 无符号32位数 */
typedef signed int INT32S; /* 有符号32位数 */
typedef float FP32; /* 单精度浮点数*/
typedef double FP64; /* 双精度浮点数*/
//STM32是32位位宽的,这里OS_STK和OS_CPU_SR都应该为32位数据类型
typedef unsigned int OS_STK; /* OS_STK 32位数据,即4字节*/
typedef unsigned int OS_CPU_SR; /* 默认CPU状态寄存器 32位*/
//定义栈的增长方向.
//CM3中,栈是由高地址向低地址增长的,所以OS_STK_GROWTH设置为1
#define OS_STK_GROWTH 1 /* 堆栈增长方向 */
//任务切换宏,由汇编实现.
#define OS_TASK_SW() OSCtxSw()
//OS_CRITICAL_METHOD = 1 :直接使用处理器的开关中断指令来实现宏
//OS_CRITICAL_METHOD = 2 :利用堆栈保存和恢复CPU的状态
//OS_CRITICAL_METHOD = 3 :利用编译器扩展功能获得程序状态字,保存在局部变量cpu_sr
#define OS_CRITICAL_METHOD 3 //进入临界段的方法
#if OS_CRITICAL_METHOD == 3
#define OS_ENTER_CRITICAL() {cpu_sr = OS_CPU_SR_Save();}
#define OS_EXIT_CRITICAL() {OS_CPU_SR_Restore(cpu_sr);}
#endif
PS:
进入临界段的方法为3 则进出临界段的宏定义分别为OS_ENTER_CRITICAL() 、OS_EXIT_CRITICAL() (由汇编编写的)。
4.os_cpu_c.c 文件详解
OSTaskStkInit() 函数,堆栈初始化函数
OS_STK *OSTaskStkInit (void (*task)(void *p_arg), void *p_arg, OS_STK *ptos, INT16U opt)
{
OS_STK *stk;
(void)opt; /* 'opt' is not used, prevent warning */
stk = ptos; /* Load stack pointer */
#if (__FPU_PRESENT==1)&&(__FPU_USED==1)
*(--stk) = (INT32U)0x00000000L; //No Name Register
*(--stk) = (INT32U)0x00001000L; //FPSCR
*(--stk) = (INT32U)0x00000015L; //s15
*(--stk) = (INT32U)0x00000014L; //s14
*(--stk) = (INT32U)0x00000013L; //s13
*(--stk) = (INT32U)0x00000012L; //s12
*(--stk) = (INT32U)0x00000011L; //s11
*(--stk) = (INT32U)0x00000010L; //s10
*(--stk) = (INT32U)0x00000009L; //s9
*(--stk) = (INT32U)0x00000008L; //s8
*(--stk) = (INT32U)0x00000007L; //s7
*(--stk) = (INT32U)0x00000006L; //s6
*(--stk) = (INT32U)0x00000005L; //s5
*(--stk) = (INT32U)0x00000004L; //s4
*(--stk) = (INT32U)0x00000003L; //s3
*(--stk) = (INT32U)0x00000002L; //s2
*(--stk) = (INT32U)0x00000001L; //s1
*(--stk) = (INT32U)0x00000000L; //s0
#endif
/* Registers stacked as if auto-saved on exception */
*(stk) = (INT32U)0x01000000L; /* xPSR */
*(--stk) = (INT32U)task; /* Entry Point */
*(--stk) = (INT32U)OS_TaskReturn; /* R14 (LR) (init value will cause fault if ever used)*/
*(--stk) = (INT32U)0x12121212L; /* R12 */
*(--stk) = (INT32U)0x03030303L; /* R3 */
*(--stk) = (INT32U)0x02020202L; /* R2 */
*(--stk) = (INT32U)0x01010101L; /* R1 */
*(--stk) = (INT32U)p_arg; /* R0 : argument */
#if (__FPU_PRESENT==1)&&(__FPU_USED==1)
*(--stk) = (INT32U)0x00000031L; //s31
*(--stk) = (INT32U)0x00000030L; //s30
*(--stk) = (INT32U)0x00000029L; //s29
*(--stk) = (INT32U)0x00000028L; //s28
*(--stk) = (INT32U)0x00000027L; //s27
*(--stk) = (INT32U)0x00000026L; //s26
*(--stk) = (INT32U)0x00000025L; //s25
*(--stk) = (INT32U)0x00000024L; //s24
*(--stk) = (INT32U)0x00000023L; //s23
*(--stk) = (INT32U)0x00000022L; //s22
*(--stk) = (INT32U)0x00000021L; //s21
*(--stk) = (INT32U)0x00000020L; //s20
*(--stk) = (INT32U)0x00000019L; //s19
*(--stk) = (INT32U)0x00000018L; //s18
*(--stk) = (INT32U)0x00000017L; //s17
*(--stk) = (INT32U)0x00000016L; //s16
#endif
/* Remaining registers saved on process stack */
*(--stk) = (INT32U)0x11111111L; /* R11 */
*(--stk) = (INT32U)0x10101010L; /* R10 */
*(--stk) = (INT32U)0x09090909L; /* R9 */
*(--stk) = (INT32U)0x08080808L; /* R8 */
*(--stk) = (INT32U)0x07070707L; /* R7 */
*(--stk) = (INT32U)0x06060606L; /* R6 */
*(--stk) = (INT32U)0x05050505L; /* R5 */
*(--stk) = (INT32U)0x04040404L; /* R4 */
return (stk);
}
PS:
堆栈初始化函数OSTaskStkInit() ,由任务创建函数OSTaskCreate()和OSTaskCreateExt() 调用,用于创建任务时初始堆栈,即在任务堆栈中保存寄存器的值。如果使用FPU ,就要保存FPU寄存器的值,否则只保存通用寄存器。