RTOS中的延时叫阻塞延时,即线程需要延时的时候,线程会放弃CPU的使用权,CPU可以去干其他事情,当线程延时时间到,重新获取CPU使用权,线程继续运行,这样就充分利用了CPU的使用权,而不是刚等着。
当线程需要延时,CPU进入阻塞状态,那CPU又去干什么事情了?如果没有其它线程可以运行,RTOS都会为CPU创建一个空闲线程,这个时候CPU就运行空闲线程。在RT-Thread中,空闲线程是系统在初始化的时候创建的优先级最低的线程。空闲线程主体主要做一些系统内存的清理工作。在实际应用中,当系统进入空闲线程的时候,可在空闲线程中让单片机进入休眠或者低功耗等操作。
1、实现空闲线程
1.1定义空闲线程的栈
空闲线程的栈在idle.c文件中定义,
#include <rtthread.h> #include <rthw.h> #define IDLE_THREAD_STACK_SIZE 512 ALIGN(RT_ALIGN_SIZE) static rt_uint8_t rt_thread_stack[IDLE_THREAD_STACK_SIZE];
空闲线程的栈是一个定义好的数组,大小由IDLE_THREAD_STACK_SIZE 这个宏控制,默认为512,即128个字。
1.2空闲线程的线程控制块
/* 空闲线程的线程控制块 */ struct rt_thread idle;
2、实现阻塞延时
阻塞延时的阻塞是指线程调用该延时函数后,线程会被剥离CPU使用权,然后进入阻塞状态,直到延时结束,线程重新获取CPU使用权才可以继续运行,在线程阻塞这段时间,CPU可以去执行其他的线程,如果其他的线程也在延时状态,那么CPU就将运行空闲线程。阻塞延时函数在thread.c中定义。
void rt_thread_delay(rt_tick_t tick) { struct rt_thread *thread; /* 获取当前线程的线程控制块 */ thread = rt_current_thread; (1) /* 设置延时时间 */ thread->remaining_tick = tick; (2) /* 进行系统调度 */ rt_schedule(); (3) }
(1)获取当前线程的线程控制块。rt_current_thread 是一个在scheduler.c中定义的全局变量,用于指向当前正在运行的线程的线程控制块。
(2)remaining_tick 是线程控制块的一个成员,用于记录线程需要延时的时间,单位为SysTick 的中断周期。
3、SysTick_Handler中断服务函数
在系统调度函数rt_schedule()中,会判断每个线程的线程控制块中的延时成员remaining_tick的值是否为0,如果为0,就要将对应的线程就绪,如果不为0,就继续延时。如果一个线程要延时,一开始remaining_tick 肯定不为0,当remaining_tick为0就延时结束,那么remaining_tick是以什么周期在递减?在哪里递减?在RT-Thread中,这个周期由SysTick中断提供,操作系统里面最小的时间单位就是SysTick的中断周期,我们称之为一个tick,SysTick中断服务函数
/* 关中断 */ rt_hw_interrupt_disable(); (1) /* SysTick 中断频率设置 */ SysTick_Config( SystemCoreClock / RT_TICK_PER_SECOND ); (2) void SysTick_Handler(void) (3) { /* 进入中断 */ rt_interrupt_enter(); (3)-1 /* 时基更新 */ rt_tick_increase(); (3)-2 /* 离开中断 */ rt_interrupt_leave(); (3)-3 }
(1)关中断。在程序开始的时候把中断关掉是一个好习惯,等系统初始化完毕,线程创建完毕,启动系统调度的时候会重新打开中断。
(2)初始化SysTick,调用固件库函数SysTick_Config来实现,配置中断周期为10ms,中断优先级为最低(无论中断优先级分组怎么分都是最低,因为这里把表示SysTick中断优先级的四个位全部配置为1,即15,在Cortex-M内核中,优先级越低,逻辑优先级最低),RT_TICK_PER_SECOND是一个在rtconfig.h中定义的宏,目前等于100。
(3)更新系统时基,该函数在clock.c中实现。
进入和离开中断,这两个函数在irq.c 中实现。
SysTick 初始化函数(在core_cm3.h 中定义)
__STATIC_INLINE uint32_t SysTick_Config(uint32_t ticks) { /* 非法的重装载寄存器值 */ if ((ticks - 1UL) > SysTick_LOAD_RELOAD_Msk) { return (1UL); } /* 设置重装载寄存器的值 */ SysTick->LOAD = (uint32_t)(ticks - 1UL); /* 设置 SysTick 的中断优先级 */ NVIC_SetPriority (SysTick_IRQn, (1UL << __NVIC_PRIO_BITS) - 1UL); /* 加载 SysTick 计数器值 */ SysTick->VAL = 0UL; /* 设置系统定时器的时钟源为 AHBCLK 使能 SysTick 定时器中断 使能 SysTick 定时器 */ SysTick->CTRL = SysTick_CTRL_CLKSOURCE_Msk | SysTick_CTRL_TICKINT_Msk | SysTick_CTRL_ENABLE_Msk; return (0UL); }
3.1系统时基更新函数
#include <rtthread.h> #include <rthw.h> static rt_tick_t rt_tick = 0; /* 系统时基计数器 */ (1) extern rt_list_t rt_thread_priority_table[RT_THREAD_PRIORITY_MAX]; void rt_tick_increase(void) { rt_ubase_t i; struct rt_thread *thread; rt_tick ++; (2) /* 扫描就绪列表中所有线程的 remaining_tick,如果不为 0,则减 1 */ for(i=0; i<RT_THREAD_PRIORITY_MAX; i++) (3) { thread = rt_list_entry( rt_thread_priority_table[i].next,
struct rt_thread,
tlist); if(thread->remaining_tick > 0) { thread->remaining_tick --; } } /* 系统调度 */ rt_schedule(); (4) }
(1)系统时基计数器,是一个全局变量,用来记录产生了多少次SysTick 中断。
(2)系统时基计数器加1操作
(3)扫描就绪列表中的remaining_tick,如果不为0,则减 1。
(4)进行系统调度
进入和离开中断函数
#include <rtthread.h> #include <rthw.h> /* 中断计数器 */ volatile rt_uint8_t rt_interrupt_nest; (1)
/** * 当 BSP 文件的中断服务函数进入时会调用该函数 * * @note 请不要在应用程序中调用该函数 * * @see rt_interrupt_leave */ void rt_interrupt_enter(void) (2) { rt_base_t level; /* 关中断 */ level = rt_hw_interrupt_disable(); /* 中断计数器 ++ */ rt_interrupt_nest ++; /* 开中断 */ rt_hw_interrupt_enable(level); } /** * 当 BSP 文件的中断服务函数离开时会调用该函数 * * @note 请不要在应用程序中调用该函数 * * @see rt_interrupt_enter */ void rt_interrupt_leave(void) (3) { rt_base_t level; /* 关中断 */ level = rt_hw_interrupt_disable(); /* 中断计数器-- */ rt_interrupt_nest --; /* 开中断 */ rt_hw_interrupt_enable(level); }
(1)中断计数器,是一个全局变量,用来记录中断嵌套次数。
(2)进入中断函数,中断计数器rt_interrupt_nest 加1 操作。当BSP文件的中断服务函数进入时会调用该函数,应用程序不能调用,切记。
(3)离开中断函数,中断计数器rt_interrupt_nest 减1操作。当BSP文件的中断服务函数进入时会调用该函数,应用程序不能调用,切记。