写在前面:
由于时间的不足与学习的碎片化,写博客变得有些奢侈。
但是对于记录学习(忘了以后能快速复习)的渴望一天天变得强烈。
既然如此
不如以天为单位,以时间为顺序,仅仅将博客当做一个知识学习的目录,记录笔者认为最通俗、最有帮助的资料,并尽量总结几句话指明本质,以便于日后搜索起来更加容易。
标题的结构如下:“类型”:“知识点”——“简短的解释”
部分内容由于保密协议无法上传。
点击此处进入学习日记的总目录
2024.03.05
- 十六、UCOSIII:介绍SysTick
- 1、SysTick简介
- 2、初始化SysTick
- 3、SysTick中断服务函数
- 十七、UCOSIII:任务时间片运行
- 1、对main()函数修改
- 2、关闭中断函数CPU_IntDis()
- 3、仿真测试
十六、UCOSIII:介绍SysTick
1、SysTick简介
RTOS需要一个时基来驱动,系统任务调度的频率等于该时基的频率。
通常该时基由一个定时器来提供,也可以从其他周期性的信号源获得。 刚好Cortex-M内核中有一个系统定时器SysTick,它内嵌在NVIC中,是一个24位的递减的计数器,计数器每计数一次的时间为1/SYSCLK。
当重装载数值寄存器的值递减到0的时候,系统定时器就产生一次中断,以此循环往复。
因为SysTick是嵌套在内核中的, 所以使得OS在Cortex-M器件中编写的定时器代码不必修改,使移植工作一下子变得简单很多。
所以SysTick是最适合给操作系统提供时基, 用于维护系统心跳的定时器。
SysTick寄存器汇总如下:
寄存器名称 | 寄存器描述 |
---|---|
CTRL | SysTick控制及状态寄存器 |
LOAD | SysTick重装载数值寄存器 |
VAL | SysTick当前数值寄存器 |
SysTick控制及状态寄存器如下:
位段 | 名称 | 类型 | 复位值 | 描述 |
---|---|---|---|---|
16 | COUNTFLAG | R/W | 0 | 如果在上次读取本寄存器后, SysTick 已经计到了 0,则该位为 1。 |
2 | CLKSOURCE | R/W | 0 | 时钟源选择位,0=AHB/8,1=处理器时钟AHB |
1 | TICKINT | R/W | 0 | 1=SysTick倒数计数到 0时产生 SysTick异常请求,0=数到 0 时无动作。也可以通过读取COUNTFLAG标志位来确定计数器是否递减到0 |
0 | ENABLE | R/W | 0 | SysTick 定时器的启用位 |
SysTick 重装载数值寄存器如下:
位段 | 名称 | 类型 | 复位值 | 描述 |
---|---|---|---|---|
23:0 | RELOAD | R/W | 0 | 当倒数计数至零时,将被重装载的值 |
SysTick当前数值寄存器如下:
位段 | 名称 | 类型 | 复位值 | 描述 |
---|---|---|---|---|
23:0 | CURRENT | R/W | 0 | 读取时返回当前倒计数的值,写它则使之清零,同时还会清除在SysTick控制及状态寄存器中的COUNTFLAG 标志 |
2、初始化SysTick
使用SysTick非常简单,只需一个初始化函数搞定,OS_CPU_SysTickInit函数在os_cpu_c.c中定义
我使用的是野火的教材,SysTick初始化函数野火没有使用μC/OS-III官方的,野火是自己另外编写了一个。
区别是uC/OS-III官方的OS_CPU_SysTickInit函数里面涉及 SysTick寄存器都是重新在cpu.h中定义,
而野火自己编写的则是使用ARMCM3.h(记得在os_cpu_c.c的开头包含ARMCM3.h这个头文件) 这个固件库文件里面定义的寄存器,仅此区别而已。
#include "ARMCM3.h"
#if 0 /* 不用μC/OS-III自带的 */
void OS_CPU_SysTickInit (CPU_INT32U cnts)
{
CPU_INT32U prio;
/* 填写 SysTick 的重载计数值 */
CPU_REG_NVIC_ST_RELOAD = cnts - 1u;
/* 设置 SysTick 中断优先级 */
prio = CPU_REG_NVIC_SHPRI3;
prio &= DEF_BIT_FIELD(24, 0);
prio |= DEF_BIT_MASK(OS_CPU_CFG_SYSTICK_PRIO, 24);
CPU_REG_NVIC_SHPRI3 = prio;
/* 启用 SysTick 的时钟源和启动计数器 */
CPU_REG_NVIC_ST_CTRL |= CPU_REG_NVIC_ST_CTRL_CLKSOURCE |
CPU_REG_NVIC_ST_CTRL_ENABLE;
/* 启用 SysTick 的定时中断 */
CPU_REG_NVIC_ST_CTRL |= CPU_REG_NVIC_ST_CTRL_TICKINT;
}
#else /* 直接使用头文件ARMCM3.h里面现有的寄存器定义和函数来实现 */
void OS_CPU_SysTickInit (CPU_INT32U ms)
{
/* 设置重装载寄存器的值 */
SysTick->LOAD = ms * SystemCoreClock / 1000 - 1;
/* 配置中断优先级为最低 */
NVIC_SetPriority (SysTick_IRQn, (1<<__NVIC_PRIO_BITS) - 1);
/* 复位当前计数器的值 */
SysTick->VAL = 0;
/* 选择时钟源、启用中断、启用计数器 */
SysTick->CTRL = SysTick_CTRL_CLKSOURCE_Msk |
SysTick_CTRL_TICKINT_Msk |
SysTick_CTRL_ENABLE_Msk;
}
#endif
-
SysTick->LOAD = ms * SystemCoreClock / 1000 - 1;
配置重装载寄存器的值,我们配合函数形参ms来配置,如果需要配置为10ms产生一次中断,形参设置为10即可。 -
NVIC_SetPriority (SysTick_IRQn, (1<<__NVIC_PRIO_BITS) - 1);
配置SysTick的优先级,这里配置为15,即最低。
3、SysTick中断服务函数
SysTick中断服务函数也是在os_cpu_c.c中定义
/* SysTick 中断服务函数 */
void SysTick_Handler(void)
{
OSTimeTick();
}
SysTick中断服务函数很简单,里面仅调用了函数OSTimeTick()。
OSTimeTick()是与时间相关的函数, 在os_time.c文件中定义。
#include "os.h"
void OSTimeTick (void)
{
/* 任务调度 */
OSSched();
}
OSTimeTick()很简单,里面仅调用了函数OSSched,OSSched函数暂时没有修改,还是手动切换任务
十七、UCOSIII:任务时间片运行
1、对main()函数修改
main()函数与之前区别不大
- 加入了SysTick相关的内容
- 注释掉任务里的OSSched()函数
/*
************************************************************************************************************************
* 包含的头文件
************************************************************************************************************************
*/
#include "os.h"
#include "ARMCM3.h"
/*
************************************************************************************************************************
* 宏定义
************************************************************************************************************************
*/
/*
************************************************************************************************************************
* 全局变量
************************************************************************************************************************
*/
uint32_t flag1;
uint32_t flag2;
/*
************************************************************************************************************************
* TCB & STACK & 任务声明
************************************************************************************************************************
*/
#define TASK1_STK_SIZE 20
#define TASK2_STK_SIZE 20
static CPU_STK Task1Stk[TASK1_STK_SIZE];
static CPU_STK Task2Stk[TASK2_STK_SIZE];
static OS_TCB Task1TCB;
static OS_TCB Task2TCB;
void Task1( void *p_arg );
void Task2( void *p_arg );
/*
************************************************************************************************************************
* 函数声明
************************************************************************************************************************
*/
void delay(uint32_t count);
/*
************************************************************************************************************************
* main函数
************************************************************************************************************************
*/
/*
* 注意事项:1、该工程使用软件仿真,debug需选择 Ude Simulator
* 2、在Target选项卡里面把晶振Xtal(Mhz)的值改为25,默认是12,
* 改成25是为了跟system_ARMCM3.c中定义的__SYSTEM_CLOCK相同,确保仿真的时候时钟一致
*/
int main(void)
{
OS_ERR err;
/* 关闭中断 */
CPU_IntDis();
/* 配置SysTick 10ms 中断一次 */
OS_CPU_SysTickInit(10);
/* 初始化相关的全局变量 */
OSInit(&err);
/* 创建任务 */
OSTaskCreate ((OS_TCB*) &Task1TCB,
(OS_TASK_PTR ) Task1,
(void *) 0,
(CPU_STK*) &Task1Stk[0],
(CPU_STK_SIZE) TASK1_STK_SIZE,
(OS_ERR *) &err);
OSTaskCreate ((OS_TCB*) &Task2TCB,
(OS_TASK_PTR ) Task2,
(void *) 0,
(CPU_STK*) &Task2Stk[0],
(CPU_STK_SIZE) TASK2_STK_SIZE,
(OS_ERR *) &err);
/* 将任务加入到就绪列表 */
OSRdyList[0].HeadPtr = &Task1TCB;
OSRdyList[1].HeadPtr = &Task2TCB;
/* 启动OS,将不再返回 */
OSStart(&err);
}
/*
************************************************************************************************************************
* 函数实现
************************************************************************************************************************
*/
/* 软件延时 */
void delay (volatile uint32_t count)
{
for(; count!=0; count--);
}
/* 任务1 */
void Task1( void *p_arg )
{
for( ;; )
{
flag1 = 1;
delay( 100 );
flag1 = 0;
delay( 100 );
/* 任务切换,这里是手动切换 */
//OSSched();
}
}
/* 任务2 */
void Task2( void *p_arg )
{
for( ;; )
{
flag2 = 1;
delay( 100 );
flag2 = 0;
delay( 100 );
/* 任务切换,这里是手动切换 */
//OSSched();
}
}
2、关闭中断函数CPU_IntDis()
关闭中断。
因为在OS系统初始化之前我们启用了SysTick定时器产生10ms的中断,在中断里面触发任务调度。
如果一开始我们不关闭中断,就会在OS还有启动之前就进入SysTick中断,然后发生任务调度,既然OS都还没启动,那调度是不允许发生的, 所以先关闭中断。
系统启动后,中断由OSStart()函数里面的OSStartHighRdy()重新开启。
CPU_IntDis()在cpu.c中被声明
一个关闭中断,一个开启中断
代码在cpu_a.asm中被定义
3、仿真测试
从图可以看到,两个任务轮流的占有CPU,享有相同的时间片。
其实目前的实验现象与上一章的实验现象还没有本质上的区别, 加入SysTick只是为了后续章节做准备。
上一章两个任务也是轮流的占有CPU,也是享有相同的时间片,该时间片是任务单次运行的时间。
不同的是本章任务的时间片等于SysTick定时器的时基,是很多个任务单次运行时间的综合。即在这个时间片里面任务运行了非常多次, 如果我们把波形放大,就会发现大波形里面包含了很多小波形