stm32学习笔记-UCOSIII任务管理

滴答定时器:在以前,操作系统以及所有使用了时基的系统,都必须由硬件定时器来产生“滴答”中断来作为系统时基。在STM32中SysTick就是用于产生系统时基的,SysTick有4个控制寄存器,如下表
地址****************寄存器*************描述
0XE000E010**SysTick->CTRL***控制及状态寄存器
0XE000E014**SysTick->LOAD***重装载值寄存器
0XE000E018**SysTick->VAL*****当前数值寄存器
0XE000E01C**SysTick->CALIB**校准数值寄存器  
滴答定时器的中断优先级为最低!在汇编文件os_cpu_a.asm文件中定义  
 
什么是任务?  
在我们设计复杂、大型程序的时候,将这些负责的程序分割成许多个简单的小程序,这些小程序就是单个的任务,所有的小任务和谐的工作,最终完成复杂的功能。在操作系统中这些小任务可以并发执行,从而提高CPU的使用效率。
UCOSIII就是一个可剥夺的多任务系统,我们使用UCOSIII的一个重要 的原因就是它的多任务处理能力。   
 
UCOSIII中的任务
在UCOSIII中任务就是程序实体,UCOSIII能够管理和调度这些小任务(程序)。
UCOSIII中的任务由三部分组成:任务堆栈、任务控制块和任务函数。
任务堆栈:上下文切换的时候用来保存任务的工作环境,就是STM32的内部寄存器值。
任务控制块:任务控制块用来记录任务的各个属性。
任务函数:由用户编写的任务处理代码,是实实在在干活的,一般写法如下:
 
UCOSIII中的任务函数模板:  
void XXX_task(void *p_arg)
   {
       while(1)
       {
        。。。。。//任务处理过程
        }
    }

可以看出用任务函数通常是一个无限循环,当然了,也可以是一个只执行一次的任务。任务的参数是一个void类型的,这么做的目的是可以可以传递不同类型的数据甚至是函数。
可以看出任务函数其实就是一个C语言的函数,但是在使用UCOIII的情况下这个函数不能有用户自行调用,任务函数何时执行执行,何时停止完全有操作系统来控制。
 
UCOSIII系统任务:
UCOSIII默认有5个系统任务:
1、空闲任务:UCOSIII创建的第一个任务,UCOSIII必须创建的任务,此任务有UCOSIII自动创建,不需要用户手动创建。
2、时钟节拍任务:此任务也是必须创建的任务。
3、统计任务:可选任务,用来统计CPU使用率和各个任务的堆栈使用量。此任务是可选任务,由宏OS_CFG_STAT_TASK_EN控制是否使用此任务。
4、定时任务:用来向用户提供定时服务,也是可选任务,由宏OS_CFG_TMR_EN控制是否使用此任务。
5、中断服务管理任务:可选任务,由宏OS_CFG_ISR_POST_DEFERRED_EN控制是否使用此任务。
 
从用户的角度看,UCOSIII的任务一共有5种状态:
1、休眠态:任务已经在CPU的flash中了,但是还不受UCOSIII管理。
2、就绪态:系统为任务分配了任务控制块,并且任务已经在就绪表中登记,这时这个任务就具有了运行的条件,此时任务的状态就是就绪态。
3、运行态:任务获得CPU的使用权,正在运行。
4、等待态:正在运行的任务需要等待一段时间,或者等待某个事件,这个任务就进入了等待态,此时系统就会把CPU使用权转交给别的任务。
5、中断服务态:当发送中断,当前正在运行的任务会被挂起,CPU转而去执行中断服务函数,此时任务的任务状态叫做中断服务态。
 
任务堆栈的创建:
任务堆栈是任务的重要部分,堆栈是在RAM中按照“先进先出(FIFO)”的原则组织的一块连续的存储空间。为了满足任务切换和响应中断时保存CPU寄存器中的内容及任务调用其它函数时的需要,每个任务都应该有自己的堆栈。
 
任务堆栈创建很简单:
例:
#define START_STK_SIZE 512 //堆栈大小
CPU_STK START_TASK_STK[START_STK_SIZE]; //定义一个数组来作为任务堆栈
 
任务堆栈的大小:
PU_STK为CPU_INT32U类型,也就是unsigned int类型,为4字节的,那么任务堆栈START_TASK_STK的大小就为:512 X 4=2048字节  
 
任务堆栈初始化:
在创建一个新任务时,必须把系统启动这个任务时所需的CPU各个寄存器初始值事先存放在任务堆栈中。
这样当任务获得CPU使用权时,就把任务堆栈的内容复制到CPU的各个寄存器,从而可以任务顺利地启动并运行。
把任务初始数据存放到任务堆栈的工作就叫做任务堆栈的初始化,UCOSIII提供了完成堆栈初始化的函数:OSTaskStkInit()。
CPU_STK  *OSTaskStkInit (OS_TASK_PTR    p_task,
                         void          *p_arg,
                         CPU_STK       *p_stk_base,
                         CPU_STK       *p_stk_limit,
                         CPU_STK_SIZE   stk_size,
                         OS_OPT         opt)
一般不会直接操作堆栈初始化函数,任务堆栈初始化函数由任务创建函数OSTaskCreate()调用。不同的CPU对于的寄存器和对堆栈的操作方式不同,因此在移植UCOSIII的时候需要用户根据各自所选的CPU来编写任务堆栈初始化函数。
 
创建的任务堆栈:
作为任务创建函数OSTaskCreate()的参数,函数OSTaskCreate()如下:
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)


堆栈增长方式:
根据堆栈的增长方式,堆栈有两种增长方式:
向上增长:堆栈的增长方向从低地址向高地址增长。
向下增长:堆栈的增长方向从高地址向低地址增长。
函数OSTaskCreate()中的参数p_stk_base是任务堆栈基地址,那么如果CPU的堆栈是向上增长的话那么基地址就&START_TASK_STK[0],如果CPU堆栈是向下增长的话基地址就是&START_TASK_STK[START_STK_SIZE-1]STM32的堆栈是向下增长的!


任务控制块结构:
任务控制块是用来记录与任务相关的信息的数据结构,每个任务都要有自己的任务控制块。
任务控制块由用户自行创建,如下代码为创建一个任务控制块:
OS_TCB StartTaskTCB;  //创建一个任务控制块
OS_TCB为一个结构体,描述了任务控制块,任务控制块中的成员变量用户不能直接访问,更不可能改变他们。
OS_TCB为一个结构体,其中有些成员采用了条件编译的方式来确定
struct os_tcb 
{
    CPU_STK             *StkPtr;                            
    void                *ExtPtr;                            
    CPU_STK             *StkLimitPtr
    OS_TCB              *NextPtr; 
    OS_TCB              *Prev
    …… //此处省略N个成员变量
 #if OS_CFG_DBG_EN > 0u
    OS_TCB              *DbgPrevPtr;
    OS_TCB              *DbgNextPtr;
    CPU_CHAR            *DbgNamePtr;
 #endif
}


任务控制块初始化:
函数OSTaskCreate()在创建任务的时候会对任务的任务控制块进行初始化。
函数OS_TaskInitTCB()用与初始化任务控制块。用户不需要自行初始化任务控制块。


优先级:
UCOSIII中任务优先级数由宏OS_CFG_PRIO_MAX来配置,UCOSIII中数值越小,优先级越高,最低可用优先级就是OS_CFG_PRIO_MAX-1。


就绪表:
UCOSIII中就绪表由2部分组成:
1、优先级位映射表OSPrioTbl[]:用来记录哪个优先级下有任务就绪。
2、就绪任务列表OSRdyList[]:用来记录每一个优先级下所有就绪的任务。
OSPrioTbl[]在os_prio.c中有定义:
CPU_DATA   OSPrioTbl[OS_PRIO_TBL_SIZE]; 
 在STM32中CPU_DATA为unsigned int,有4个字节,32位。因此表OSPrioTbl每个参数有32位,其中每个位对应一个优先级。
OS_PRIO_TBL_SIZE=((OS_CFG_PRIO_MAX - 1u) / DEF_INT_CPU_NBR_BITS)+ 1)
OS_CFG_PRIO_MAX由用户自行定义,默认为64。
DEF_INT_CPU_NBR_BITS= CPU_CFG_DATA_SIZE * DEF_OCTET_NBR_BITS
CPU_CFG_DATA_SIZE=CPU_WORD_SIZE_32=4。
DEF_OCTET_NBR_BITS=8。
所以,当系统有64个优先级的时候:
OS_PRIO_TBL_SIZE=((64-1)/(4*8)+1)=2。


函数OS_PrioGetHighest()用于找到就绪了的最高优先级的任务
OS_PRIO  OS_PrioGetHighest (void)
{
    CPU_DATA  *p_tbl;
    OS_PRIO    prio;


    prio  = (OS_PRIO)0;
    p_tbl = &OSPrioTbl[0];
    while (*p_tbl == (CPU_DATA)0) {
prio += DEF_INT_CPU_NBR_BITS; 
p_tbl++;
    }
    prio += (OS_PRIO)CPU_CntLeadZeros(*p_tbl); 
    return (prio);
}


就绪任务列表:
通过上一步我们已经知道了哪个优先级的任务已经就绪了,但是UCOSIII支持时间片轮转调度,同一个优先级下可以有多个任务,因此我们还需要在确定是优先级下的哪个任务就绪了
struct  os_rdy_list {
    OS_TCB           *HeadPtr //用于创建链表,指向链表头
    OS_TCB           *TailPtr;   //用于创建链表,指向链表尾
    OS_OBJ_QTY       NbrEntries; //此优先级下的任务数量
};
同一优先级下如果有多个任务的话最先运行的永远是HeadPtr所指向的任务!
 
可剥夺型任务调度:
任务调度就是中止当前正在运行的任务转而去执行其他的任务。
UCOSIII是可剥夺型内核,因此当一个高优先级的任务准备就绪,并且此时发生了任务调度,那么这个高优先级的任务就会获得CPU的使用权!
UCOSIII中的任务调度是由任务调度器来完成的,任务调度器有2种:任务级调度器和中断级调度器。
任务级调度器为函数OSSched()。
中断级调度器为函数OSIntExit(),当退出外部中断服务函数的时候使用中断级任务调度。
 
任务调度点:
  1、释放信号量或者发送消息,也可通过配置相应的参数不发生任务调度。
  2、使用延时函数OSTimeDly()或者OSTimeDlyHMSM()。
  3、任务等待的事情还没发生(等待信号量,消息队列等)。
  4、任务取消等待。
  5、创建任务。
  6、删除任务。
  7、删除一个内核对象。
  8、任务改变自身的优先级或者其他任务的优先级。
  9、任务通过调用OSTaskSuspend()将自身挂起。
  10、任务解挂某个挂起的任务。
  11、退出所有的嵌套中断。
  12、通过OSSchedUnlock()给调度器解锁。
  13、任务调用OSSchedRoundRobinYield()放弃其执行时间片。
  14、用户调用OSSched()。 
 
调度器上锁和解锁:  
有时候我们并不希望发生任务调度,因为始终有一些代码的执行过程是不能被打断的。此时我们就可以使用函数OSSchedLock()对调度器加锁,当我们想要恢复任务调度的时候就可以使用函数OSSchedUnlock()给已经上锁的任务调度器解锁
 
时间片轮转调度  

UCOSIII允许一个优先级下有多个任务,每个任务可以执行指定的时间(时间片),然后轮到下一个任务,这个过程就是时间片轮转调度,当一个任务不想在运行的时候就可以放弃其时间片。

时间片轮转调度器为:OS_SchedRoundRobin()。

 
任务切换:
当UCOSIII需要切换到另外一个任务时,它将保存当前任务的现场到当前任务的堆栈中,主要是CPU寄存器值,然后恢复新的现场并且执行新的任务,这个过程就是任务切换。
任务切换分为两种:任务级切换和中断级切换。
任务级切换函数为:OSCtxSw()。
中断级切换函数为:OSIntCtxSw()。  
 
任务控制块结构:  
任务控制块是用来记录与任务相关的信息的数据结构,每个任务都要有自己的任务控制块。任务控制块由用户自行创建,如下代码为创建一个任务控制块:  
OS_TCB StartTaskTCB;  //创建一个任务控制块
OS_TCB为一个结构体,描述了任务控制块,任务控制块中的成员变量用户不能直接访问,更不可能改变他们。
OS_TCB为一个结构体,其中有些成员采用了条件编译的方式来确定
 
UCOSIII系统初始化:  
在使用UCOSIII之前我们必须先初始化UCOSIII,函数OSInit()用来完成UCOSIII的初始化,而且OSInit()必须先于其他UCOSIII函数调用,包括OSStart()。
int main(void)
{
   OS_ERR err;
   ……
   //其他函数,一般为外设初始化函数
   ……
   OSInit(&err);
   ……
   //其他函数,一般为创建任务函数
   ……
   OSStart(&err);
}
 
系统启动:  
使用函数OSStart()来启动UCOSIII,函数如下:
void  OSStart (OS_ERR  *p_err)
{
    if (OSRunning == OS_STATE_OS_STOPPED) {
        OSPrioHighRdy   = OS_PrioGetHighest(); 
        OSPrioCur       = OSPrioHighRdy;
        OSTCBHighRdyPtr = OSRdyList[OSPrioHighRdy].HeadPtr;
        OSTCBCurPtr     = OSTCBHighRdyPtr;
        OSRunning       = OS_STATE_OS_RUNNING;
        OSStartHighRdy(); 
        *p_err           = OS_ERR_FATAL_RETURN; 
     } else {
       *p_err           = OS_ERR_OS_RUNNING; 
     }
}
 
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
STM32是一款非常流行的嵌入式微控制器系列,它具有强大的性能和丰富的外设资源。在学习STM32时,掌握如何进行Flash读写是非常重要的。 Flash是一种非易失性存储器,可以用来存储程序代码和数据。在STM32中,Flash存储器通常用来存储应用程序代码。下面是一个简单的Flash读写程序的示例: 1.首先,我们需要包含适用于所使用的STM32型号的头文件。例如,对于STM32F4系列,我们需要包含"stm32f4xx.h"。 2.然后,我们需要定义一个指向Flash存储器的指针变量。例如,可以使用如下代码:`uint32_t* flash_address = (uint32_t*)0x08000000;`其中0x08000000是Flash存储器的起始地址。 3.要读取Flash存储器中的数据,我们可以通过以下代码实现:`data = *flash_address;`其中data是一个变量,用于存储读取到的数据。 4.要写入数据到Flash存储器中,我们可以通过以下代码实现:`*flash_address = data;`其中data是要写入的数据。 需要注意的是,STM32的Flash存储器是有写保护机制的,因此在写入数据之前,我们需要禁用写保护。可以使用以下代码禁用写保护:`FLASH->KEYR = 0x45670123; FLASH->KEYR = 0xCDEF89AB;`然后才能进行数据写入。 另外,为了确保数据的完整性,我们可以使用CRC校验来验证Flash存储器中的程序代码的正确性。可以使用库函数来计算校验和,然后将其与预期的校验和进行比较以进行验证。 综上所述,掌握STM32的Flash读写操作对于嵌入式系统的开发非常重要。上述示例代码可以帮助我们快速进行Flash读写操作,同时注意写保护和数据校验可以提高数据的安全性和可靠性。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值