前言
以此笔记记录学习之旅。此篇较长,欢迎大家插眼,找对应的部分观看。
感谢原子哥的文档和教程。
目录
基础知识
任务调度
调度器就是使用相关的算法来决定当前需要执行哪个任务
抢占式调度
针对优先级不同的任务,高优先级的任务可以抢占低优先级任务
任务优先级的数值越小,优先级越高
高优先级的任务不停止,低优先级任务无法执行
时间片调度
针对优先级相同的任务,当多个任务优先级相同且就绪时候,任务调度根据用户所设置的时间片轮流的运行任务
任务状态
五种状态
运行态
正在执行的任务
就绪态
改任务已经能够被执行,但还为被执行
挂起态
一个运行态的任务因延时或等待某一事件发生时被挂起,那么这个任务就处于挂起态
休眠态
已经存在CPU的内存中(任务被删除了),但是还没有交给UCOSIII内核管理(不能被调用了)
中断态
处于运行态的任务被中断打断,CPU跳转去执行中断服务函数,直到中断结束,在切换回运行态继续运行
转换图
被创建的任务,初始状态均为就绪态
被删除的任务,会转为休眠态
仅就绪态和中断态可转变为运行态
其他状态的任务想运行,必须先转变为就绪态
三大列表
就绪列表
准备运行的任务将放在就绪列表:OSRdyList[X] x代表优先级的数值
Tick列表
正在等待延时超时或挂起的对象超时的任务,将放在OSTickList
挂起列表
当任务等待信号量、事件时。任务将放置在挂起列表PendList
UCOS移植
UCOS3文件内容
名称 | 描述 |
---|---|
Cfg | 包含的是UCOS3配置文件的模板文件 |
Ports | 存放接口文件,是CPU和cos3的连接桥梁 |
Source | 是ucso的源码文件,也是核心文件 |
Template | 与动态Tick管理相关的文件 |
UC-CPU文件内容
主要包含了与ARM-Cortex-M内核的CPU相关的移植文件(前导置指令、大小端模式)
uC-LIB文件内容
官方提供的ASCII字符操作、数学、内存管理、字符串操作的库
移植步骤
创建目录结构及拷贝文件
创建一个目录,在改目录下分别创建目录
BSP、CORE、uC-CPU、uC-LIB、uCOS-III、USER
BSP
主要是固件库、启动代码、中断代码等
直接把FWLIB整个目录拷贝过来(包括子目录inc和src)
cstartup.s 启动代码
bsp.c/bsp.h/bsp_int.c/bsp_periph.c 与ucos中断管理相关的代码
CORE
包含一些与cpu内核相关的类型定义的头文件
core_cm4.h/core_cmFunc.h/core_cmInstr.h/core_cmSimd.h
uC-CPU
cpu_core.c cpu_core.h cpu_def.h
cpu.h cpu_a.asm cpu_c.c
uC-LIB
这个是ucos中与字符串、数学函数、内存拷贝等常用库函数代码
lib_ascii.c lib_ascii.h lib_def.h lib_math.c lib_math.h lib_mem.c
lib_mem.h lib_mem_a.asm lib_str.c lib_str.h
uCOS-III
ucos中实现操作系统功能代码的部分
把下载的ucos目录:Micrium_STM32F429II-SK_OS3\Micrium\Software\uCOS-III\Source下的文件全部拷贝过来
USER
在该目录下创建目录src,把应用层的相关的代码放到src子目录下去
st官方系统文件
stm32f4xx.h/stm32f4xx_conf.h/system_stm32f4xx.c/system_stm32f4xx.h
将文件添加到keil工程中,添加包含路径、生成HEX文件、增加全局宏定义(输入STM32F40_41xxx,USE_STDPERIPH_DRIVER)
系统配置文件
os_cfg.h
该文件主要用于配置UCOS-III内核的一些功能,比如消息队列、信号量、事件标志
os_cfg_app.h
用于系统的应用配置,如空闲任务、
cpu_cfg.h
配置cpu相关的一些宏定义,如时间戳、前导置指令相关等内容
lib_cfg.h
主要用于配置ucos/lib组件。使能内存库中优化的内存相关操作函数
UCOS3任务创建和删除
本质就是调用ucos3的api函数
API函数 | 描述 |
---|---|
OSTaskCreate() | 创建任务 |
OSTaskDel() | 删除任务 |
注:
使用OSTaskCreate()创建任务,任务的任务控制块以及任务栈空间所需要的内存,需要我们手动去分配。只要当任务被创建完成就会进入就绪态
使用OSTaskDel()删除任务。当不在需要使用该任务时,可以使用该函数删除任务。被删除的任务并不会被删除内存和任务栈,只是该任务不被ucos内核所管理
任务创建函数
void OSTaskCreate
(
OS_TCB *p_tcb, //指向任务控制块的指针
CPU_CHAR *p_name, //指向任务的名字
OS_TASK_PTR p_task, //指向任务函数的指针-真正实现逻辑的地方
void *p_arg, // 任务函数的入口参数
OS_PRIO prio, //任务的优先级,数字越小,优先级越高
CPU_STK *p_stk_base, //指向任务栈的起始地址的指针
CPU_STK_SIZE stk_limit, //任务栈的使用警戒线-防止溢出的风险
CPU_STK_SIZE stk_size, //任务栈的大小
OS_MSG_QTY q_size, //任务内嵌消息队列的大小
OS_TICK time_quanta, //任务的时间片
void *p_ext, //指向用户扩展内存的指针-堆栈不够用的情况下-一般不用
OS_OPT opt, //任务选项共
OS_ERR *p_err //判断任务是否创建成功
)
任务控制块OS_TCB
struct os_tcb
{
CPU_STk *StkPtr; //任务栈栈顶,必须为TCB的第一个成员
CPU_STK *StkLimitPtr; //指向任务栈警戒线指针
OS_TCB *NextPtr; //指向任务列表中下一个任务控制块指针
OS_TCB *PrevPtr; //指向 上
OS_TCB *TickNextPtr;//指向Tick任务列表中下一个任务控制块指针
OS_TCB *TickPrevPtr;//指向Tick任务列表中上一个任务控制块指针
OS_PRIO *Prio //保存任务的优先级
OS_TICK TickRemain;//任务延时的剩余时钟节拍数
OS_TICK TimeQuanta;//任务时间片
OS_TICK TimeQuantaCtr;//任务剩余时间片
.........
}
任务控制块类似于任务的身份证
OS_OPT
Opt | 描述 |
---|---|
OS_OPT_TASK_NONE | 没有选项 |
OS_OPT_TASK_STK_CHK | 是否允许对任务进行堆栈检查(对堆栈进行一个保护) |
OS_OPT_TASK_STK_CLR | 是否需要清除任务堆栈(创建任务时需要清除,原先位置有数据) |
OS_OPT_TASK_SAVE_FP | 是否保存浮点寄存器 |
OS_OPT_TASK_NO_TLS | 不需要对正在创建的任务提供TLS支持 |
OS_ERR*
p_err | 描述 |
---|---|
OS_ERR_NONE | 创建任务成功(返回none) |
OS_ERR_ILLIE_CREATE_RUN_TIME | 定义了OS_SAFETY_CRITICAL_IEC61508,且在OSStart()之后非法地创建内核对象 |
OS_ERR_PRIO_INVALID | 非法的任务优先级数值 |
OS_ERR_STAT_STK_SIZE_INVALID | 任务堆栈在初始化期间溢出 |
OS_ERR_STK_INVALID | 指向任务栈起始地址的指针为空 |
OS_ERR_STK_SIZE_INVAILD | 任务栈小于配置项 |
OS_ERR_STK_LIMIT_INVALID | 任务栈警戒线太小,大于或等于任务栈大小 |
OS_ERR_TASK_CREATE_ISR | 在中断中非法地创建任务 |
OS_ERR_TASK_INVALID | 指向任务函数的指针为空 |
OS_ERR_TCB_INVALID | 指向任务控制块的指针为空 |
任务删除函数
void OSTaskDel
(
OS_TCB* p_tcb,
OS_ERR* p_err
)
任务创建流程
1.定义函数入口参数(任务堆栈、任务优先级)
2.调用创建任务API函数
3.实现任务函数功能
注
1.在调用任何关于ucos3函数之前必须先初始化ucos3。 OSlnit()
2.任务在创建之后不会直接运行,需开启任务调度器,任务才能得到运行。 OSStart()
任务挂起和恢复
API函数
函数 | 描述 |
---|---|
OSTaskSuspend() | 挂起任务 |
OSTaskResume() | 恢复任务 |
挂起
挂起任务类似于暂停,可恢复
被删除的任务无法被恢复
注
这两个函数不允许在中断中调用
任务挂起函数
void OSTaskSuspend(OS_TCB* p_tcb,OS_ERR* p_err)
用于无条件地挂起任务,被挂起的任务不会参与任务调度
如果当前运行的任务被挂起,那么则会发起任务调度,将CPU的使用权交给下一个任务
任务变为挂起态本质将任务从就绪列表中移除
不会再参与运行,如果要运行必须要恢复操作
OS_TCB* 参数为0 ,代表挂起任务本身
任务恢复函数
void OSTaskResume(OS_TCB* p_tcb,OS_ERR* p_err)
用于挂起态的任务
若n次调用OSTaskSuspend()挂起同一个任务,那么必须也要调用n次的OSTaskResume()才可以解挂
是可嵌套的
不能挂起空闲任务
UCOS3中断管理
中断
让CPU打断正常运行的程序,转而去处理紧急的事情(程序)
三步走:
产生中断请求(CPIO外部中断,定时器中断)
响应中断(停止执行当前程序,转而去处理ISR)
退出中断(返回被打断的程序处)
中断优先级分组设置
cortex-M使用8位宽的寄存器来配置中断的优先级
stm32提供最大16级的中断优先级,只使用了中断优先级配置寄存器的高4位bit4-7
优先级分组 | 抢占优先级 | 响应优先级 | 配置寄存器高四位 |
---|---|---|---|
NVIC_PriorityGroup_0 | 0级抢占优先级 | 0-15 | None:用于抢占优先级 7-4:用于子优先级 |
NVIC_PriorityGroup_1 | 0-1 | 0-7 | 7:用于抢占 6-4用于子优先级 |
NVIC_PriorityGroup_2 | 0-3 | 0-3 | 7-6:用于抢占 5-4 |
NVIC_PriorityGroup_3 | 0-7 | 0-1 | 7-5 4 |
NVIC_PriorityGroup_4 | 0-15 | 0 | 7-4 None |
HAL_NVIC_SetPriorityGrouping(NVIC_PRIORITYGROUP_4)
特点
1.ucos3中断管理范围,通过宏CPU_CFG_KA_IPL_BOUNDA设置。
例:将该宏设为3,那么ucos3将会管理优先级为3-15的中断,0-2的中断则不受ucos3管理。
如果在中断中想要调用ucos3的函数,那么该中断必须受ucos3管理,即宏的设定要包含该中断
中断相关寄存器
3个系统中断优先级配置寄存器 SHPR1、SHPR2、SHPR3
SHPR1:0xE000ED18
SHPR2:0xE000ED1C
SHPR3:0xE000ED20
ucos3如何配置pendSV和systick中断优先级
pendsv中断优先级设置最低,防止系统任务切换打断中断
Systick设置管理最高:保证系统时钟节拍的精度
三个中断屏蔽寄存器
BAEPRI
屏蔽优先级低于某一个阈值的中断,当为0时,则不关闭任何中断。进入临界区和推出临界区,就是操作BASEPRI寄存器
PRIMASK
只有bit0是有效的,设置为1关中断,设置为0开中断
CPSIE I //清除PRIMASK = 0 (使能中断) CPSID I //清除PRIMASK = 0 (屏蔽中断)
临界段代码保护以及任务调度锁
临界段
指必须完澡运行,不能被打断的代码段
适用场合:
外设 需要严格按照时序初始化的外设 IIC、SPI等
系统
用户
中断和任务调度可以打断当前程序的运行
临界段代码保护函数
ucos3在进入临界段代码的时候需要关闭中断
函数 | 描述 |
---|---|
CPU_CRITICAL_ENTER() | 进入临界段 |
CPU_CRITICAL_EXIT() | 退出临界段 |
特点:
成对适用
不支持嵌套
尽量保持临界段耗时短
任务调度锁
用于对任务调度器上锁以及解锁。
上锁禁止任务调度,解锁才允许任务调度
支持嵌套
注
只影响任务调度器,并不影响中断的执行。只是不会执行任务切换
函数
函数 | 描述 |
---|---|
void OSSchedLock(OS_ERR* p_err) | 调度器加锁 |
void OSSchedUnlock(OS_ERR* p_err) | 调度器解锁 |
挂起调度器的方式,适用于临界区位于任务和任务之间,防止任务在被打断,确保系统的稳定性
特点
成对使用
支持嵌套
OSSchedLockNestingCtr 任务调度器嵌套计数器 0时代表解锁
任务调度
ucos3初始化
OSInit() 必须在调用任何其他ucos3函数之前调用,调用一次即可
1.对一些全局变量赋初始值
2.初始化就绪列表以及tick列表
3.创建三个任务:空闲任务(必须创建),统计任务(条件创建),软件定时器任务(条件创建)
空闲任务:任务优先级最低31,当系统无其他的就绪任务,那么空闲任务将会执行,空闲任务不能被阻塞
统计任务:优先级为30,用来统计CPU使用率和各个任务的堆栈使用量
软件定时器任务:事务优先级为29,主要用于在特定时间段内处理单次或周期性的软件定时器
开启任务调度器
OSStart() //用于启动任务调度器
1.进行一些安全关键代码判断
2.获取当前最高优先级任务
3.将调度器运行状态标志设置为开启状态
4.获取最高优先级任务的任务控制块
5.调用函数OSStartHighRdy(),启动第一个任务
注:在调用此函数之前必须创建一个任务
kernel_task_cnt 记录创建了多少个任务
前导置零指令
CPU_CntLeadZeros(OSPrioTbl[0]) 0001.....000000
实质:计算从左向右,第一个1前面有多少个0
目的:获取最高优先级
OSPrioTbl[0]对应的是一个就绪列表,是一个32为的变量,哪一位是1,代表哪一优先级有任务
启动第一个任务
如何启动第一个任务?将任务A的都寄存器值传到CPU的寄存器里面去
任务A的寄存器的值在创建的时候,就被保存在任务堆栈里面
高地址 P_stk初始位置
xPSR寄存器(0x01000000) 其中bit24被置1,表示使用thumb指令 |
---|
任务函数地址(PC寄存器) |
任务错误退出函数地址(LR寄存器) |
R12寄存器(0X12121212) |
R3寄存器(0x03030303) |
R2寄存器(0x02020202) |
R1寄存器(预留) |
任务函数参数(R0寄存器) |
EXEC_RETURN(0xFFFFFFFD) |
R11寄存器(0x11111111) |
R10寄存器(0x10101010) |
R9寄存器(0x09090909) |
R8寄存器(0x08080808) |
R7寄存器(0x07070707) |
R6寄存器(0x06060606) |
R5寄存器(0x05050505) |
R4寄存器(0x04040404) p_stk 更新后的地方 |
其他任务栈空间 |
p_stk_base 低地址
注
1.中断产生时,硬件将自动把xPSR、PC、LR、R12和R3-0出/入栈;而R4-R11需要手动操作
2.进入中断后硬件会强制使用MSP指针,此时LR的值将会被自动更新为特殊的EXC_RETURN
过程
OSStartHighRdy()
作用:用于启动第一个任务,在启动任务调度器中被执行
函数内部
1、屏蔽中断,防止在启动第一个任务时被打断
2、将pendsv设置为最低优先级
3、将PSP清0,PSP用于任务栈
4、将MSP设置为OS_CPU_ExceptStkBas,指向异常栈的栈底(8字节对齐)
5、过去当前最高优先级的任务它的任务优先级,以及任务控制块
6、将最高优先级任务的任务栈出栈
MSP与PSP
程序在运行过程中需要一定的栈空间来保存局部变量等一些信息,当有信息保存到栈时,mcu会自动更新sp指针
主堆栈指针(MSP):由os内核、中断服务程序来使用
进程堆栈指针(PSP):常规的应用程序代码(非中断)
在ucos3中,中断使用MSP,中断以外使用PSP
出栈/压栈汇编指令详解
1、出栈 方向为从下到上(低地址到高地址) 将任务栈出栈到cpu寄存器
LDMFD r0!,{r4-r6} 任务栈r0地址由低到高,将r0存储地址里面的内容手动加入r4、r5、r6寄存器里面
r0地址(0x04)内容加载到r4,此时地址r0=r0+4=0x08
r0地址(0x08)内容加载到r5,此时地址r0=r0+4=0x0c
r0地址(0x0c)内容加载到r6,此时地址r0=r0+4=0x10
2、压栈 方向为从上到下(高地址到地址)
STMFD r0!,{r4-r6}
r0 = r0-4 =0x0c,将r6的内容(寄存器值)存放到r0所指向的地址
r0 = r0-4 =0x08,将r5的内容(寄存器值)存放到r0所指向的地址
r0 = r0-4 =0x04,将r4的内容(寄存器值)存放到r0所指向的地址
任务切换
本质:CPU寄存器的切换
步骤
-
保存现场:暂停任务A的执行,并将任务A的寄存器保存到任务堆栈
-
恢复现场:将任务B的各个寄存器的值 恢复到CPU寄存器中
PendSv的触发
OSCtxSw()
OSIntCtxSw()
本质:通过向中断控制和状态寄存器ISR的bit28写入1 挂起PendSV中断
OSSched() 任务中使用
OSIntExit() 中断中使用
时间片调度
同等优先级任务轮流享有相应的运行时间(可设置),叫时间片 一个时间片就等于SysTick中断周期
1.同等优先级任务,轮流执行;时间片流转
2.一个时间片的大小,取决于滴答定时器中断频率
3.每个任务都可以定义自身的时间片长度
4.没有用完的时间片不会再使用
API函数
void OSSchedRoundRobinCfg(
CPU_BOLLEN en, //是否使能时间片调度
OS_TICK dflt_time_quanta, //默认的时间片长度 设为0代表默认是100个时间片长度
OS_ERR *p_err //接收错误信息的指针
)
//用于开启时间片调度,并设置时间片的默认值
注:使用时间片调度需要把宏 OS_CFG_SCHED_ROUND_ROBIN_EN 置1
创建任务中的tick参数设置为0代表时间片长度为dflt_time_quanta
时间管理
是一种建立在时钟节拍上,对任务运行时间管理的一种系统内核机制
实际延迟时间 取决于系统时钟节拍的频率 OS_CFG_TICK_RATE_HZ
时间API函数
OSTimeDly()
以系统时钟节拍为单位进行任务延时
OSTimeDly(OS_TICK dly, //任务延时的系统时钟节拍数为单位
OS_OPT opt, //延时选项
OS_ERR* p_err)
OPT
OPT | 描述 |
---|---|
OS_OPT_TIME_DLY | 任务延时的结束时刻为OSTickCtr = OSTickCtr +dly |
OS_OPT_TIME_TIMEOUT | 任务延时的结束时刻为OSTickCtr = OSTickCtr +dly |
OS_OPT_TIME_MATCH | 任务延时的结束时刻为OSTickCtr = dly(只执行一次 直到下一次溢出重新开始) |
OS_OPT_TIME_PERIODIC | 任务延时的结束时刻为OSTickCtr = OSTCBCurPtr->TickCtrPrev +dly |
OSTimeDlyHMSM()
OSTimeDlyHMSM(
CPU_INT16U hours, //任务延时的小时数
CPU_INT16U minutes, //任务延时的分钟数
CPU_INT16U seconds, //任务延时的秒数
CPU_INT32U milli, //任务延时的毫秒数
OS_OPT opt, //延时选项
OS_ERR * p_err ) //指向接收错误代码变量的指针
注:使用该函数将宏OS_CFG_TIME_DLY_HMSM_EN置1
OPT
opt | 描述 |
---|---|
OS_OPT_TIME_HMSM_STRICT | 延时时间参数严格按照实际的时间格式 任务延时的小时数(0-99) 分钟数(0-59) 秒数(0-59) 毫秒数(0-999) |
OS_OPT_TIME_HMSM_NON_STRICT | 延时时间参数无需严格按照实际的时间格式 任务延时的小时数(0-999) 分钟数(0-9999) 秒数(0-65535) 毫秒数(0-4294967295) |
OS_OPT_TIME_DLY | 任务延时的结束时刻为OSTickCtr = OSTickCtr +dly |
OS_OPT_TIME_TIMEOUT | 任务延时的结束时刻为OSTickCtr = OSTickCtr +dly |
OS_OPT_TIME_MATCH | 任务延时的结束时刻为OSTickCtr = dly(只执行一次 直到下一次溢出重新开始) |
OS_OPT_TIME_PERIODIC | 任务延时的结束时刻为OSTickCtr = OSTCBCurPtr->TickCtrPrev +dly |
OSTimeDlyResume()
OSTimeDlyResume(
OS_TCB * p_tcb, //任务控制块
OS_TCB * p_err //错误代码
)
队列
是任务到任务、中断到任务的数据交流机制(消息传递)
为什么不使用全局变量?
弊端:数据无保护,导致数据不安全,当多个任务同时对该变量操作的时候,数据容易受损
//写队列
OSQPost()
{
//进入临界区
写队列实际操作
//推出临界区
}
//读队列
OSQPend()
{
//进入临界区
读队列实际操作
//推出临界区
}
读写队列做好了保护,防止多任务同时访问冲突
注:中断不可以调用队列接收函数,但是可以调用队列发送函数
-
执行时间短:中断服务程序需要尽可能快地执行完毕,以减少对系统正常运行的影响。队列接收函数可能会使任务进入等待状态,这在中断中是不允许的,因为中断需要立即处理。
-
不能阻塞:中断服务程序不能执行任何可能导致任务阻塞的操作。队列接收函数在接收不到消息时可能会阻塞任务,这在中断中是不被允许的。
特点
1.通常采用FIFO的数据存储缓冲机制
2.队列数据是一个“万能指针”,可以指向任何数据,甚至是函数 发送方和接收方必须按照约定好的方式去发送和接收
3.队列不属于某一个任务,中断和任务都支持发送
4.当任务向一个队列读取消息时,可以指定一个阻塞时间(假设此时队列没有数据无法读取)
入队不会阻塞
流程
创建队列->写队列->读队列
API函数
函数 | 描述 |
---|---|
OSQCreat() | 创建一个消息队列 |
OSQDel() | 删除一个消息队列 |
OSQFlush() | 清空队列中的所有消息 |
OSQPend() | 获取消息队列中的消息 |
OSQPendAbort() | 终止任务挂起等待消息队列 |
OSQPost() | 发送消息到消息队列 |
OSQCreat()
void OSQCreat(
OS_Q* p_q, //指向消息队列结构体的指针
CPU_CHAR* p_name, //指向作为消息队列名的ACSII字符串的指针
OS_MSG_QTY max_qty, //消息队列的大小 由这个来决定
OS_ERR* p_err
)
OSQPost()
void OSQPost(
OS_Q* p_q,
void* p_void, //指向消息的指针
OS_MSG_SIZE msg_size, //消息的大小 单位:字节
OS_OPT opt, /*函数操作选项
OS_OPT_POST_FIFO 先进先出
OS_OPT_POST_LIFO 后进先出
OS_OPT_POST_ALL 将消息发送给所有等待该消息的任务
OS_OPT_POST_NO_SCHED 禁止在本函数内执行任务调度
以上的几种类型可以 或 组合 */
OS_ERR* p_err
)
OSQPend()
void *OSQPend(
OS_Q* p_q,
OS_TICK timeout, //任务挂起等待消息队列的最大允许时间 0表示一直等待
OS_OPT opt, /*函数操作选项
OS_OPT_PEND_BLOCKING 如果没有任何消息存在的话就阻塞任务
OS_OPT_PEND_NON_BLOCKING 如果消息队列没有任何消息
*/
OS_MSG_SIZE msg_size, //消息的大小 单位:字节
CPU_TS* p_ts, //接收消息队列接收时的时间戳的变量指针,为NULL 说明用户没有要求时间戳
OS_ERR* p_err //
)
信号量
信号传递的是一个状态,消息队列传递的是数据
量->二值(0 1) /计数
功能:解决同步问题的机制,可以实现对共享资源的有序访问
当计数值大于0,代表信号量有资源
当释放信号量,信号量计数值(资源数)加1,直到溢出
当获取信号量,信号量计数值减一,直到0
二值信号量
实际上只有0和1两种情况,所以称为二值
作用
常用于互斥访问或任务同步 (与互斥信号量比较类似,二值信号量可能会导致优先级翻转情况)
任务和中断可以释放二值信号量,但是只有任务能够获取二值信号量(与消息队列相似)
API函数
过程
创建二值信号量->释放二值信号量->获取二值信号量(获取不到信号量任务就会被阻塞)
函数 | 描述 |
---|---|
OSSemCreat() | 创建一个信号量 |
OSSemDel() | 删除一个信号量 |
OSSemPend() | 获取一个信号量 |
OSSemPendAbort() | 终止任务挂起等待信号量资源(接触阻塞态) |
OSSemPost() | 释放信号量资源(传入) |
OSSemSet() | 强制设置信号量资源数 |
OSSemCreate()
OSSemCreate(
OS_SEM * p_sem, //指向信号量结构体的指针
CPU_CHAR * p_name, //指向作为信号量名的ASCII字符串的指针
OS_SEM_CTR cnt, //信号量资源数的初始值
OS_ERR * p_err
)
OSSemPost()
OS_SEM_CTR OSSemPost(
OS_SEM* p_sem, //指向信号量结构体的指针
OS_OPT opt, /*函数操作选项
OS_OPT_POST_1: 只给一个任务(最高优先级)发信号
OS_OPT_POST_ALL:给所有等待该信号量的任务发信号
OS_OPT_POST_NO_SCHED:禁止在本函数内执行任务调度---->可与上面两个选项或起来使用
*/
OS_ERR* p_err
)
OS_SEM_CTR:返回值类型-->信号量更新后的值
OSSemPend()
OS_SEM_CTR OSSemPend(
OS_SEM * p_sem, //指向信号量结构体的指针
OS_TICK timeout, //任务挂起等待信号量的最大允许时间,当值为0的适合,表示该任务将一直等待,直到获取到信号量
OS_OPT opt, /*
OS_OPT_PEND_BLOCKING 如果信号量没有资源的话就阻塞任务
OS_OPT_PEND_NON_BLOCKING 如果信号量没有资源的话就直接返回
*/
CPU_TS* p_ts, //指向接收信号量接收时间戳的变量的指针
OS_ERR * p_err
)
记数型信号量
和二值信号量类似,但是资源数大于1
试用场合:事件计数、资源管理
二值信号量可以视为特殊的技术型信号量
优先级翻转
问题:高优先级的任务后执行,低优先级任务先执行
在RTOS中不允许出现优先级翻转,因为优先级翻转会破坏任务的预期顺序,可能会导致未知的严重后果
现象
现有任务A、B、C A的优先级最高 B的优先级次之 C的优先级最后
任务A和任务C都需要获取二值信号量,完成一系列操作后,再释放二值信号量。
当任务A和B都处于阻塞态,而任务C此时就绪,任务C获取了二值信号量,一段时间后任务A就绪了,因为信号量被任务C占有因此导致任务A被阻塞(直到获取到二值信号量)。任务B在此时处于就绪态,任务B的优先级比任务C高,因此执行任务B,以及任务C被挂起。任务B完成后,继续执行任务C,直到任务C完成后释放信号量,任务A此时获取到信号量从阻塞态恢复到就绪态进而执行。
造成了任务B比任务A先执行,导致高优先级的任务A迟迟得不到调度,但任务B却能抢占到CPU资源
从现象上看,就像是低优先级的比高优先级的先执行。(即优先级翻转)
而高优先级的往往意味着该任务更重要,需要立马被执行。
互斥信号量
其实就是一个拥有优先级继承的二值信号量,在同步应用中二值信号量最合适。适用于在需要互斥访问的应用中
优先级继承
当一个互斥信号量正在被一个低优先级的任务持有时,如果此时有一个
高优先级的任务也尝试获取这互斥量,那么这个高优先级的任务就会被阻塞。不过这个高优先级的任务会将低优先级的任务的优先级提升到与自己相同的优先级。
注:互斥信号量不能用于中断服务函数中(1)互斥信号量有任务优先级继承的机制,但中断不是任务,没有任务优先级(2)中断服务函数不能因为要等待互斥信号量而设置阻塞时间而进入阻塞态
API函数
OSMutexCreat()
void OSMutexCreat(
OS_MUTEX* p_mutex, //指向互斥信号量结构体的指针
CPU_CHAR * p_name,
OS_ERR* p_err )
OSMutexPend()
void OSMutexPend(
OS_MUTEX* p_mutex,
OS_TICK timeout,
OS_OPT opt,/*
OS_OPT_PEND_BLOCKING 如果信号量没有资源的话就阻塞任务
OS_OPT_PEND_NON_BLOCKING 如果信号量没有资源的话就直接返回
*/
CPU_TS* p_ts, //指向接收信号量接收时间戳的变量的指针
OS_ERR * p_err
)
OSMutexPost
void OSMutexPost(
OS_MUTEX* p_mutex, //指向互斥信号量结构体的指针
OS_OPT * opt, //OS_OPT_POST_NONE:不指定特定的选项
//OS_OPT_POST_NO_SCHED:禁止在本函数内执行任务调度
OS_ERR* p_err
)
任务内嵌信号量
介绍
任务内嵌信号量本质上就是一个信号量,任务信号量是分配于每一个任务的任务控制块结构体中的,因此每一个任务都有独自的任务内嵌信号量
不同
任务内嵌信号量,其中的信号量可以由任务或者中断释放,但是只能被这个任务本身获取,不可以被其他任务获取
注意任务内嵌信号量只能被该任务获取,但是可以由其他任务或中断释放
优势:内存更小 效率更高
劣势:无法广播给多个任务
API
OSTaskSemPend()
OS_SEM_CTR OSTaskSemPend(
OS_TTCK timeout, //任务挂起等待时间
OS_OPT opt, //任务配置选项
CPU_TS* p_ts, //接收时间戳的指针
OS_ERR* p_err )
OSTaskSemPendAbort()
终止任务挂起等待任务信号量
OSTaskSemPost()
OS_SEM_CTR OSTaskSemPost(
OS_TCB* p_tcb, //指向任务的控制块的指针
OS_OPT opt, //OS_OPT_POST_NONE 不指定特定的选项 OS_OPT_POST_NO_SCHED 禁止在本函数内执行任务调度
OS_ERR* p_err
)
OSTaskSemSet()
用于强制设置任务内嵌信号量的资源数
OS_SEM_CTR OSTaskSemSet(
OS_TCB* p_trb,
OS_SEM_CTR cnt, //指定的信号量资源数
OS_ERR* p_err
)
任务内嵌队列
简介
任务内嵌消息队列本质上也还是一个队列,任务队列是分配于每一个任务的任务控制块结构体中的
注意任务内嵌消息队列只能被该任务获取,但是可以由其他任务或中断释放
API函数
OSTaskQFlush()
侵清空任务内嵌消息队列中的所有消息
OSTaskQPend()
获取任务内嵌消息队列中的消息
void* OSTaskQPend(
OS_TICK timeout, //任务挂起等待任务内嵌消息队列的最大允许时间
OS_OPT opt, //OS_OPT_PEND_BLOCKING OS_OPT_PEND_NON_BLOCKING
OS_MSG_SIZE* p_msg_size, //用于接收消息大小变量的指针
CPU_TS* p_ts, //指向接受消息队列接收时的时间戳的变量的指针
OS_ERR* p_err
)
OSTaskQpendAbort()
终止任务挂起等待任务内嵌消息队列
OSTaskQPost()
发送消息到任务内嵌消息队列
void OSTaskPost(
OS_TCB* p_tcb, //指向任务控制块的指针
void* p_void, //指向消息的指针
OS_MSG_SIZE msq_size, //消息的大小 单位 字节
OS_OPT opt, /*
OS_OPT_POST_FIFO //先进先出
OS_OPT_POST_LIFO //后进先出
OS_OPT_POST_NO_SCHED //在本函数内禁止执行任务调度
*/
OS_ERR* p_err
)
事件标志
简介
是一个用于指示事件是否发送的比特位
事件标志组是由多个事件标志组成的,一位一位组合在一起
特点
1.它的每一个位表示一个事件(最多可以表示32个事件标志)
2.每一位事件的含义,由用户自己定义(值为1代表事件发生;值为0代表事件没有发送)
3.任意的任务或者中断都可以写这些位置,但是只能由任务去读取这些位
4.可以等待某一位成立,或者等待多位同时成立
等待某一位成立就返回对应 OR
等待多位同时成立就返回 对应AND
支持读取阻塞
API函数
流程:创建事件标志组->设置事件标志(写)->获取事件标志
OSFlagCreate()
创建一个事件标志组
void OSFlagCreate(
OS_FLAG_GRP* p_grp, //指向事件标志组结构体的指针
CPU_CHAR* p_name, //指向作为事件标志组名的ACII字符串的指针
OS_FLAGS flags, //事件标志组的初始值
OS_ERR* p_err
)
OSFlagDel()
删除一个事件标志组
OSFlagPend()
等待事件标志组中的事件
OS_FLAGS OSFlagPend (OS_FLAG_GRP *p_grp, //指向事件标志组结构体的指针
OS_FLAGS flags, //等待的事件标志
OS_TICK timeout, //任务挂起等待事件标志的最大允许时间
OS_OPT opt, //函数操作选项
/*
OS_OPT_PEND_FLAG_CLR_ALL 等待flag中的所有指定位被清0
OS_OPT_PEND_FLAG_CLR_ANY 等待flag中的任意指定位被清0
OS_OPT_PEND_FLAG_SET_ALL 等待flag中的所有指定位被置1
OS_OPT_PEND_FLAG_SET_ANY 等待flag中的任意指定位被置1
OS_OPT_PEND_FLAG_CONSUME 等待到指定位后,清0指定位
OS_OPT_PEND_BLOCKING 不满足条件时候挂起任务
OS_OPT_PEND_NON_BLOCKING 不满足条件时候不挂起任务
*/
CPU_TS *p_ts, //指向接收等待到时间时的时间戳的变量的指针
OS_ERR *p_err)
OSFlagPendAbort()
终止挂起等待事件标志组中的事件
OSFlagPendGetFlagRdy()
获取任务等待到的事件
OSFlagPost()
设置事件标志组的事件
OS_FLAGS OSFlagCreate(
OS_FLAG_GRP* p_grp, //指向事件标志组结构体的指针
OS_FLAGS flags, //设置事件指定位清0或置1
OS_OPT opt, //函数操作选项:
OS_OPT_POST_FLAG_SET置1 OS_OPT_POST_FLAG_CLR清0
OS_ERR* p_err )
OS_FLAGS 事件标志组跟新后的返回值
软件定时器
简介
从指定的时刻开始,经过一个指定时间,然后触发一个超时事件,用户可自定义定时器的周期
硬件定时器:芯片自身自带的定时器模块,精度一般很高,每次在定时时间达到后就会自动触发一个中断,用户在中断服务函数中处理信息
软件定时器:具有定时功能的软件,可设置定时周期,当指定时间达到后要调用回调函数,用户在回调函数中处理信息
优点:内存限制 使用简单 成本低
缺点:精度不高
单次定时器
单词定时器的设定一旦超时,就会执行超时回调函数,只不过只执行一次(效果只有一次 用完就结束)不会自动重新开启定时,不过可以被手动重新开启
单词软件定时器的时序图
是一个向下递减的过程 dly设置必须要大于0
在没超时之前又调用了OSTmrStart(),dly重新计时 直到超时
周期定时器
周期性的执行,效果有多次(持久) 启动以后就会在执行完回调函数以后自动的重新启动,周期性地执行软件定时器回调函数
周期软件定时器的时许图
dly=0时候,从period处开始执行
dly大于0时,第一次超时时间为dly,后面超时时间为period
状态
未使用态
软件定时器被定义但还未创建或软件定时器被删除时,软件定时器就处于未使用态
停止态
软件定时器被创建但还未开启定时器或被停止时,软件定时器处于停止状态
运行态
正在运行的软件定时器,当到达指定时间后,超时回调函数会被调用
完成态
单词定时器超时后,软件定时器处于完成态
软件定时器的周期定时器是不存在完成态的。周期定时器在每次定时周期结束时会自动重新加载周期值并继续运行,因此它一直处于运行状态
注意:新创建的软件定时器处于停止状态
特点
1.支持裁剪:如需使能软件定时器,需要将OS_CFG_TMR_EN配置项配置为1
2.软件定时器的超时回调函数是由软件定时器服务任务调用,软件定时器的超时回调函数本身不是任务,因此不能在该回调函数中使用可能会导致任务阻塞的API函数(Pend)
3.软件定时器的频率由宏OS_CFG_TMR_TASK_RATE_HZ决定,注意软件定时器的频率并不等于系统时钟节拍的频率,但软件定时器是依靠系统节拍实现定时的,因此需要换算
软件定时器时间分辨率=OS_CFG_TICK_RATE_HZ(系统节拍频率) / OS_CFG_TMR_TASK_RATE_HZ
100ms = 1000 / 10。正是因为软件定时器的定时频率与系统时钟节拍的节拍频率不同,因此导致如果设置一个软件定时器的时间为10,软件定时器并不会在10个系统时钟节拍后超时,而是会在10*软件定时器分辨率 系统时钟节拍后超时。
API函数
OSTmrCreate()
创建一个软件定时器
void OSTmrCreate (OS_TMR *p_tmr, //指向软件定时器结构体指针
CPU_CHAR *p_name, //指向软件定时器的字符串指针
OS_TICK dly, // 软件定时器的开启延时时间,必须大于0
OS_TICK period, //周期定时器的定时周期时间,必须大于0
OS_OPT opt, //OS_OPT_TMR_ONE_SHOT单次定时器 OS_OPT_TMR_PERIODIC周期定时器
OS_TMR_CALLBACK_PTR p_callback, //指向超时回调函数的指针
void *p_callback_arg, //超时回调函数入口参数
OS_ERR *p_err)
OSTmrDel()
删除一个软件定时器
CPU_BOOLEAN OSTmrDel(OS_TMR* p_tmr,OS_ERR* p_err)
//删除成功返回OS_TURE 失败返回OS_FALSE
OSTmrRemainGet()
获取软件定时器的剩余超时时间
OSTmrSet()
设置软件定时的延时时间、周期、超时回调函数及其参数
OSTmrStart()
开启软件定时器定时
CPU_BOOLEAN OSTmrStart(OS_TMR* p_tmr,OS_ERR* p_err)
//删除成功返回OS_TURE 失败返回OS_FALSE
OSTmrStateGet()
获取软件定时器的状态
OSTmrStop()
停止软件定时器定时
CPU_BOOLEAN OSTmrStop(
OS_TMR* p_tmr,
OS_OPT opr, // OS_OPT_TMR_CALLBACK 执行一次回调函数,代入原始参数
//OS_OPT_TMR_CALLBACK_ARG 执行一次回调函数,代入指定参数
//OS_OPT_TMR_NONE 仅停止软件定时器
void* p_callback_arg,
OS_ERR* p_err
)
内存管理
简介
是指软件运行时,对内存资源的分配和使用的一种技术。为了能够高效且快速地分配,并且在释放的时候释放不在使用的内存空间
标准C库的动态内存管理方法缺点:占用大量的代码空间 没有线程安全的相关机制 运行有不确定性 内存碎片化
内存碎片
由于多次申请和释放内存 (内存不连续)
uocs3提供了一个内存管理的方案
将一个大内存作为一个内存区,一个内存去中有多个大小均相同的内存块组成
特点
1.由于每一个内存块大小相同,所以分配时间一定
2.内存块大小相同,所以申请以及释放不会产生内存碎片
用户可以根据实际的需求,创建多个不同的内存区,每个内存区中内存的数量和大小都可以是不同的
注意:1.需要用户提供内存区,并且保证该内存区不被释放(通常采用数组的形式)
内存管理相关结构体
struct os_mem
{
void *AddrPtr; //指向内存区起始地址指针
void *FreeListPtr; //指向空闲的内存块链表指针
OS_MEM_SIZE BlkSize; //单个内存块的大小
OS_MEM_QTY NbrMax; //内存区中内存块的总量
OS_MEM_QTY NbrFree; //内存区中空闲内存的块数量
}
API函数
OSMemCreate()
创建一个内存区
void OSMemCreate (OS_MEM *p_mem, //指向内存区结构体的指针
CPU_CHAR *p_name, //指向作为内存区名的ASCII字符串的指针
void *p_addr, //指向内存区起始地址的指针
OS_MEM_QTY n_blks, //内存区中内存块的数量
OS_MEM_SIZE blk_size, //内存区中内存块的大小
OS_ERR *p_err)
OSMemGet()
从内存区中获取一个内存块
void *OSMemGet(OS_MEM* p_mem,OS_ERR* p_err)
返回类型 指向内存块的起始地址,如果返回NULL,代表申请失败
OSMemPut()
释放内存到内存区中
void OSMemPut(OS_MEM* p_mem,
void* p_blk, //待释放的内存块
OS_ERR* p_err)
结尾
如果你能成功看到这里,并且对上面的内容都能理解,那么祝贺你(撒花),你已经成功的了解ucos的应用,千里之行始于足下,快去实战一个项目,更加深入的理解和巩固吧!!!