当前位置:首页 > 单片机 > 单片机
[导读]摘要:针对在51单片机上移植实时操作系统μC/OS-II的目的,以μC/OS-II工作原理为基础,结合51单片机堆栈空间少的情况,采用改变堆栈指针到不同任务寄存器组的方法,通过改变堆栈指针的实验,得出在堆栈空间较

摘要:针对在51单片机移植实时操作系统μC/OS-II的目的,以μC/OS-II工作原理为基础,结合51单片机堆栈空间少的情况,采用改变堆栈指针到不同任务寄存器组的方法,通过改变堆栈指针的实验,得出在堆栈空间较少的情况下,也能够实现μC/OS-II在51单片机上的运行的结论。
关键词:μC/OS;单片机;实时操作系统;堆栈

    μC/OS-II是一种公开源代码、结构小巧、具有可剥夺实时内核的嵌入式开发系统,代码简短、条理清晰、实时性及安全性能很高,绝大部分代码用C编写,现已被移植到多种处理器的构架中。随着51单片机片内资源的日益丰富,在51单片机上移植μC/OS-II已成为可能,植入系统后,由系统来管理软件与硬件资源,简化应用程序的设计,并且使应用系统功能更加完善。因此在51单片机上移植μC/OS-II具有十分重要的意义。

1 μC/OS实时操作系统概述
     μC/OS-II实时操作系统是一种可移植、可固化、可裁剪即可剥夺型的多任务实时内核,适用于各种微处理器和微控制器。μC/OS-II主要包括任务调度、时间管理、内存管理、事件管理(信号量、邮箱、消息队列)4大部分。它的移植与4个文件相关:汇编文件(OS_CPU_A.A SM)、处理器相关C文件(OS_CPU.H、OS_CPU_C.C)和配置文件(OS_CFG.H)。有64个优先级,系统占用8个,用户可创建56任务,不支持时间片轮转。
    它的基本思路就是“近似地每时每刻总是让优先级最高的就绪任务处于运行状态”。为了保证这一点,它在调用系统函数、中断结束、定时中断结束时总是执行调度算法。原作者通过事先计算好数据,简化了运算量,通过精心设计就绪表结构,使得延时可预知。任务的切换是通过模拟一次中断实现的。

2 任务调度的实现原理
    任务调度是μC/OS-II的重要部分,和具体的微处理器关系紧密。必须移植的5个函数有4个都和任务有关。任务调度就是保存当前任务的寄存器和PC指针(即当前任务的断点),然后把将要执行的任务的寄存器值返回给寄存器并把PC指向将要执行任务的断点。这些的实现要借助于堆栈和中断,为了简便起见,先看函数调用时堆栈的使用情况。在函数调用时,堆栈的一个重要功能就是保存被调函数的断点地址。若有4个函数,Fun1调用Fun2,Fun2调用Fun3,Fun3调用Fun4,Fun4为叶子程序(无子程序调用)。
   
    假设现在从Fun1一直运行到Fun4,此时堆栈结构如图1所示,中间的ADD_A到ADD_D为堆栈中的数据,左边的SP到SP-7为堆栈指针,右边的Fun1到Fun4为对应的调用函数。运行Fun4时,此时SP与SP-1所存的值为ADD_D,而ADD_D为Fun3中子函数Fun4的下一行的地址,即Fun3中3-2行的地址,以此类推,ADD_C为2-2行地址,ADD_B 图1函数运行及堆栈结构图为1-2行地址。


    当函数A调用函数B时,进入函数B时就会把函数A的断点地址压栈,而当函数B运行结束时则把堆栈中函数A的断点地址弹出到PC指针,程序接着从函数A的断点开始运行。如果在函数B中更改SP及SP-1中的数据,则函数B运行结束时就不会再返回函数A中,而返回到SP及SP-1更改后的数据所代表的地址。
    以上是函数调用时的基本情况,如果是中断则堆栈不仅保存断点地址还会自动保存寄存器的值。任务调度就是靠中断来实现,中断中所保存的断点地址就是任务的断点地址,当本任务要再次执行时就把断点地址赋给PC就可以接着任务被中断时地址顺序执行。

3 头文件移植
    与移植相关的4个文件中有2个头文件,这2个头文件的移植比较简单,可以参考其它的移植程序。其中OS_CPU.H中主要是数据类型的定义、堆栈生长方向的定义、开关中断的定义以及函数级任务切换的宏定义。OS_CFG.H中主要是任务数、优先级数、事件数、每秒中断节拍数以及各种系统函数的使能定义。

4 汇编与C文件的移植
    在要移植的汇编与C的两个文件中有14个函数,其中9个是接口函数,可根据实际需要来决定,有5个是必须写的。这5个函数分别是:OS_CPU_C.C文件中的OSTaskStkInit()和OS_CPU_A.ASM文件中的OSStartHighRdy()、OSCtxSw()、OSIntCtxSw()与OSTickISR()。下面就这5个函数来做具体分析。
4.1 任务堆栈初始化函数OSTaskStkInit()
    此函数是在任务创建函数OSTaskCreat()或OSTaskCreatExt()中调用的。因为系统为每个任务申请了一个数组作为栈,当一个任务运行时,就把堆栈指针指向本任务的栈,任务堆栈初始化函数就是在任务创建时将要创建任务的堆栈进行初始化。但C51的堆栈指针SP是8位的,只能在片内RAM的256个字节内寻址。因其寻址空间有限且SP唯一,不能像DSP或ARM那样为每一段程序或每一种模式定义堆栈,需小心管理堆栈空间。为了适应上述情况,需要换一种思路,不是让SP去指向各任务堆栈空间,而是把各任务堆栈空间的内容复制到系统栈中。至于堆栈数组空间要有多大以及堆栈数组空间里放些什么内容,可以借鉴keil中中断函数的压栈情况,当中断函数不指定寄存器组时,编译器一般将PC、ACC、B、DPTR、PSW、R0~R7寄存器入栈,其中PC和DPTR是双字节的,其它都是单字节的,一共15个字节,所以把堆栈数组设计成至少15个字节的,以保证任务所用的寄存器都在堆栈数组中包含着。因为每个数组里放的是寄存器的值,在此就把这每个任务的堆栈数组叫做寄存器数组,暂且把寄存器数组设计成15个字节,依次存放PC、ACC、B、DPTR、PSW、R0~R7。
    函数OSTaskStkInit()传递4个参数,第1个参数task是所创建任务的起始地址,这个参数须保存到PC在寄存器数组的对应位置,第2个参数ppdata是所创建任务的参数,C51规则中用R1~R3来传递参数指针,这个参数须存放到R1~R3在寄存器数组中的对应位置。第3个参数ptos是栈底指针,从当前地址开始初始化堆栈指针,第4个参数opt是附加参数,一般不用。
4.2 运行等待任务中优先级最高任务函数OSStartHighRdy()
    此函数在启动操作系统函数OSStart()的最后一行调用,且此函数不返回,经过此函数后μC/OS接管系统。OSStartHighRdy()不是去调用用户任务函数,而是让PC指针指向任务函数首地址。且任务函数的传递参数只有一个,若此参数正确,则可保证任务函数运行正确。在调用OSStartHighRdy()之前OSStart()已经把最高优先级任务的任务表准备好了,只要把最高优先级任务表的数据恢复到堆栈中,再执行返回指令即可,以上最关键的是如何让其返回到最高优先级任务中而不是返回到被调函数中。
    当函数OSStart()调用函数OSStartHighRdy()时,断点地址入栈;当OSStartHighRdy()执行完之后,返回断点。在OSStartHighRdy()中把SP及SP-1的值改为最高优先级任务的地址,这样OSStartHighRdy()就会返回到最高优先级任务中去运行。
4.3 任务级的任务切换函数OSCtxSw()
    此函数是保存当前任务的状态,然后运行处于就绪态中的最高优先级任务。前面介绍过不是更改SP去指向寄存器数组,而是把寄存器数组的数复制到堆栈中。先看下一般的情况,在用户任务MyTask(void*ppdtat)中调用TimeDly(),TimeDly()中调用OSSched(),在OSSched()中有一个宏OS_TASK_SW(),这个宏的目的是让程序进人函数OSCtxSw()。参看图1,就如Fun4为OSCtxSw(),Fun3为OSSched(),Fun2为TimeDly(),Fun1为MyTask()。ADD_D存的是OSSched()的断点,ADD_C为TimeDly()的断点,ADD_B为MyTask()的断点。如果进行任务切换,应该把高优先级任务的地址值赋给ADD_B(即SP-4与SP-5)。
    以上考虑的是最简单的情况,当任务比较复杂时,可能更改了ACC、PSW、DPTR或R0~R7的值,在进入高优先任务时,寄存器并不是此任务的寄存器值,运行的结果可能不正确。
    在上述情况下如何保证CPU寄存器的值正确,要分两个阶段。第一个阶段是把CPU寄存器值保存到要挂起任务的寄存器数组中,当刚进入OSCtxSw()时,CPU寄存器的值是要挂起任务的寄存器值,所以一开始就要锁定CPU寄存器的值。如果OS_TASK_SW()定义为中断的话,在进入OSCtxSw()时,CPU寄存器的值被自动压栈;如果把OS_TASK_SW()定义为函数时,在进入函数时使用内嵌汇编的方法把CPU寄存器入栈。这时堆栈中又压入了13个字节,就如在图1的ADD_D上又压入了13个字节的数据,然后从堆栈中把值取出来放到相应任务的寄存器数组中。第二个阶段是把将要执行任务的寄存器数组的值复制到堆栈中。此时PC指针在堆栈中对应的位置是SP-17与SP-18,SP到SP-12的13个字节对应ACC、B、DPTR、PSW、R0~R7。
4.4 中断级的任务切换函数OSIntCtxSw()
    此函数和上一个函数基本思想一致,都要保存当前任务的状态,运行处于就绪态中的优先级最高的任务。二者的不同在于,上个函数的堆栈中SP-17与SP-18是PC值的位置,SP到SP-12是13个寄存器的位置。当中断来时,在中断中调用函数OSIntExit(),函数OSIntExit()调用函数OSIntCtxSw(),在OSIntCtxSw()中实现任务切换。在进入函数OSIntExit()之前寄存器的值已经入栈,所以运行到本函数时堆栈中SP-17与SP-18是PC值的位置。SP-4到SP-16是13个寄存器的位置。在图1上,上个函数的13个寄存器的值被压入ADD_D上面的13个字节中,而本函数是在ADD_B于ADD_C之间压入的这13个寄存器。
4.5 周期节拍中断函数OSTickISR()
    这个函数是给系统提供一个节拍,一般每秒10~100次。如果节拍频率太高,μC/OS系统会占用大量硬件资源;如果太低,任务间的切换又会很慢。
    此函数首先要保证产生一个周期性的中断,可以使用硬件定时器,也可以从交流电中获得50/60Hz的时钟频率。这个函数至少要做3件事:1)进入中断时,把中断嵌套层数计数器加1,说明又进入一次中断,也可以直接调用OSIntEnter()函数;2)调用时钟节拍函数OSTimeTick(),告知系统又经过了一个节拍;3)调用OSIntExit()函数,说明要退出中断了,此函数会自动处理。

5 结束语
    文中阐述了在堆栈空间有限的51单片机上运行μC/OS-II系统的移植过程,利用系统栈SP作为数据交换的枢纽。在实际应用中,如果用系统栈来移植,只需根据文中的基本思想进行适当的改写,即可运行于其他处理器上。如果处理器的堆栈指针寻址空间足够大,也可以为每个任务开辟一个栈,通过改变堆栈指针指向不同任务的栈空间,来实现任务调度。
    通过在51单片机上的运行,可以看出μC/OS-II也能在堆栈空间比较少的CPU上运行。

本站声明: 本文章由作者或相关机构授权发布,目的在于传递更多信息,并不代表本站赞同其观点,本站亦不保证或承诺内容真实性等。需要转载请联系该专栏作者,如若文章内容侵犯您的权益,请及时联系本站删除。
换一批
延伸阅读

上海2024年4月17日 /美通社/ -- 在2024 F1中国站即将拉开帷幕之际,高端全合成润滑油品牌美孚1号今日举办了品牌50周年庆祝活动。三届F1年度车手总冠军马克斯•维斯塔潘也亲临现场,共同庆祝这一里程...

关键字: BSP 汽车制造 行业标准 产品系列

北京2024年4月17日 /美通社/ -- 2024年4月13日,由北京康盟慈善基金会主办的"县域诊疗,规范同行"——肿瘤诊疗学术巡讲项目首站在广州隆重召开。本次会议邀请全国多位肺癌领域专家和县域同道...

关键字: AI技术 医疗服务 BSP 互联网

海口2024年4月16日 /美通社/ -- 4月14日,在中法建交60周年之际,科学护肤先锋品牌Galenic法国科兰黎受邀入驻第四届中国国际消费品博览会(以下简称"消博会")法国馆。Galenic法...

关键字: NI IC BSP ACTIVE

上海2024年4月17日 /美通社/ -- 每年4月17日是世界血友病日。今年,世界血友病日以"认识出血性疾病,积极预防和治疗"为主题,呼吁关注所有出血性疾病,提升科学认知,提高规范化诊疗水平,让每一位出血性疾病患者享有...

关键字: VII 动力学 软件 BSP

伦敦2024年4月16日 /美通社/ -- ATFX宣布任命Siju Daniel为首席商务官。Siju在金融服务行业拥有丰富的经验和专业知识,曾在全球各地的高管职位上工作了19年以上。Siju之前担任FXCM首席商务官...

关键字: NI AN SI BSP

STM32与51单片机之间有什么差异呢?两者可以说是一场科技与性能的较量了。在科技飞速发展的今天,微控制器(MCU)已广泛应用于各类电子设备和系统中,发挥着举足轻重的作用。其中,STM32和51单片机作为两种常见的微控制...

关键字: STM32 51单片机 MCU

51单片机将是下述内容的主要介绍对象,通过这篇文章,小编希望大家可以对51单片机的相关情况以及信息有所认识和了解,详细内容如下。

关键字: 单片机 51单片机

在嵌入式系统开发中,单片机是不可或缺的重要组成部分。其中,STM32单片机和51单片机是两种常见的单片机芯片。本文将对比分析这两种单片机的区别,并探讨STM32单片机的优势。

关键字: stm32单片 51单片机

51单片机是指由美国INTEL公司生产的一系列单片机的总称,这一系列单片机包括了许多品种,如8031,8051,8751,8032,8052,8752等,其中8051是最早最典型的产品,该系列其它单片机都是在8051的基...

关键字: 51单片机 串行通信
关闭
关闭