嵌入式实时操作系统(一)

编程入门 行业动态 更新时间:2024-10-13 22:27:12

<a href=https://www.elefans.com/category/jswz/34/1770281.html style=嵌入式实时操作系统(一)"/>

嵌入式实时操作系统(一)

一、RTOS

嵌入式系统是对对象进行自动控制而使其具有智能化并可嵌入对象体系中的专用计算机系统。嵌入式系统硬件大体上可分为MCU和SoC两个阶段。MCU(Micro Controller Uint)的发展方向是不断在一个芯片上扩展满足宿主对象系统所要求的的各种外围电路与接口电路,以增强其对宿主对象的智能化控制能力。典型产品就是51单片机。SoC(System on Chip)的发展动因是人不断寻找应用系统在芯片上的最大化解决方案。在USB,DSP,TCP/IP通信模块,GPRS通信模块。蓝牙模块等功能模块出现之后,人们又根据应用把这些功能模块与MCU进行有机结合,制造出集成度更高的系统级芯片。

嵌入式操作系统是运行在嵌入式硬件平台上,对整个系统及其所操作的部件,装置等资源进行统一协调,指挥和控制的系统软件。按照外部事件的响应能力来分类,嵌入式操作系统有实时操作系统(Real Time Operation System)和分时操作系统两类。

  • RTOS:操作系统能使计算机系统及时响应外部时间的请求,并及时控制所有实时设备与实时任务协调运行(功能正确),且能在规定时间内完成对事件的处理(时间正确)。
  • 分时操作系统:按照管理的任务数把CPU分成若干个时间片,将每个时间片分配给一个任务,CPU按照时间片轮流执行任务。

RTOS应满足以下三个条件:

  1. 实时操作系统必须是多任务系统。
  2. 任务的切换时间应与系统中的任务数无关。
  3. 中断延时的时间可预知并尽可能短。 

 多任务实时操作系统的内核分为可剥夺型和不可剥夺型两种。大多数嵌入式实时操作系统是可剥夺型内核。在可剥夺型内核中,CPU总是运行任务中优先级最高的那个任务,高优先级可以剥夺低优先级的使用权。不可剥夺型内核要求每个任务必须能够主动放弃CPU使用权防止某个任务始终霸占CPU使用权。

任务之间的切换是由操作系统的调度器来完成的。调度器的运行时间应该是固定的,不能受应用程序的任务数量或者其他影响。

自CPU响应中断到CPU转向中断服务程序之间所用的时间叫做中断延时。显然,中断延时要影响系统的实时性。因此,缩短中断延时也是实时操作系统需要解决的一项课题。

二、任务

μC/OS-II就是一个能对任务进行管理和调度的多任务操作系统。从应用程序设计的角度来看,μC/OS-II的任务就是一个线程,就是一个解决用户问题的C语言函数和与之相关联的一些数据结构而构成的一个实体。从任务的存储结构来看,μC/OS-II由三部分构成:任务程序代码、任务堆栈和任务控制块。

任务程序代码是任务的执行部分;任务堆栈用来保存任务的工作环境;任务控制块用来保存任务属性。

μC/OS-II有用户任务和系统任务两种。μC/OS-II把每个任务都作为一个节点,然后把它们连接成一个任务链表。

 由于嵌入式系统只有一个CPU,所以在一个具体时刻只能允许一个任务占用CPU。根据任务是否占用CPU,以及是否处于被中断、等待等情况,任务在μC/OS-II中可能处于5种状态之一。

  1. 睡眠状态:任务只是以代码的形式驻留在程序空间(ROM或RAM),还没有交付给操作系统管理。简单地说,任务在没有配备任务控制块或被剥夺了任务控制块时的状态叫做任务的睡眠状态。
  2. 就绪状态:如果系统为任务配备了任务控制块且在任务就绪表中进行了就绪登记,则任务就具备了运行的充分条件。
  3. 运行状态:处于就绪状态的任务如果经调度器判断获得了CPU使用权,则进入运行状态。任何时刻这只能有一个任务处于运行状态,就绪的任务只有当所有优先级高于本任务的任务都转为等待状态时,才进入运行状态。
  4. 等待状态:正在运行的任务,需要等待一段时间或者需要等待一个事件发生再运行时,该任务就会把CPU的使用权让给其他任务而使任务进入等待状态。
  5. 中断服务状态:一个正在运行的任务响应中断申请就会中止运行而去执行中断服务程序。

 1.用户任务代码的一般结构:

任务执行代码通常是一个无限循环结构,并且在这个循环中可以响应中断。

void MyTask( void * pdata)

{

        for(; ;)

        {

                可以被中断的用户代码;

                OS_ENTER_CRITICAL(); //进入临界段(关中断)

                不可以被中断的用户代码;

                OS_EXIT_CRITICAL(); //退出临界段(开中断)

                可以被中断的用户代码;

        }

}

μC/OS-II任务的代码就是一个C语言函数,为了可以选择不同类型的数据甚至函数,把参数设置为一个void类型的指针。使用  OS_ENTER_CRITICAL();和OS_EXIT_CRITICAL();来控制中断开启或者中断关闭。两者之间的程序是受保护的,叫做临界段。所以,μC/OS-II的任务代码结构是一个带有临界段的无限循环。

2.用户应用程序的一般结构:

用户任务不是一般的C语言函数,他是一个线程。因此,它不是被主函数或者其他函数调用的,主函数只负责创建和启动它们,而由操作系统负责来调度运行它们。

void main()

{

        ......

        OSInit();//初始化μC/OS-II

        ......

        OSTaskCreate(MyTask1,......);//创建用户任务1

        OSTaskCreate(MyTask2,......);//创建用户任务2

        OSTaskCreate(MyTask3,......);//创建用户任务3

        .......

        OSStart();//启动任务

        ......

}

其中,  OSTaskCreate()为操作系统μC/OS-II提供的用来创建任务的函数;OSStart()是启动任务的函数。使用他之后,启动各个任务,任务就交给操作系统来管理和调度了。

3.系统任务

μC/OS-II预定义了两个为应用程序服务的系统任务:空闲任务和统计任务。其中空闲任务是每个应用程序必须使用的,统计任务可根据实际情况选用。

1.空闲任务:在多任务系统运行时,系统会在某个时间内无用户任务可运行而处于所谓的空闲状态。为了使CPU在没有用户任务可执行时有事可做,μC/OS-II提供了一个OSTaskIdle()的系统任务。

void OSTaskIdle(void* pdata)

{

#if OS_CRITICAL_METHOD ==3

        OS_CPU_SRcpu_sr;

#endif

        pdata = padta;//防止某些编译器报错

        for(; ;)

        {

                OS_ENTER_CRITICAL();//关闭中断

                OSdleCtr++;                  //计数

                OS_EXIT_CRITICAL(); //开放中断

        }

}

μC/OS-II规定,一个用户程序必须使用这个空闲任务,而且不能用软件删除。  pdata = padta;是因为任务中没有用到参数pdata,某些编译器会对这种情况报错。空闲任务几乎就是不做什么事情。如果用户认为有必要,那么可以在空闲任务中编写一些做用户工作的代码。

2.统计任务:OSTaskStat();每秒计算一次CPU在单位时间内被使用的时间,并把计算结果以百分比的形式存放在变量OSC_PUUsage中,以便应用程序通过访问它来了解CPU的利用效率。

如果用户程序需要使用这个统计任务,则必须把定义在头文件OS_CFG.H的常数OS_Task_STAT_EN设置为1,并且必须在创建统计任务之前调用函数OSStatInit()对统计任务初始化。

4.任务的优先权和优先级别

μC/OS-II的每个任务都必须具有一个唯一的优先级。μC/OS-II有64个优先级别,数字越大,优先级越低。在文件OS_CFG.H中通过给表示最低优先级别的常数OS_LOWEST_PRIO赋值的方法,来说明应用程序中任务优先级别的数目。该常数一旦被定义,就意味着系统中可供使用的优先级别是0~OS_LOWEST_PRIO,一共OS_LOWEST_PRIO+1个。系统会自动把OS_LOWEST_PRIO赋给空闲任务。如果使用了统计任务,操作系统会将OS_LOWEST_PRIO-1自动赋值给统计任务.所以用户任务可以使用的优先级别是0~OS_LOWEST_PRIO-2

给某个用户任务定义优先级别,需要在调用系统函数OSTaskCreate()来创建任务时,用该函数的第4个参数prio来指定。

三、任务堆栈

堆栈,就是存储器中按数据“后进先出”的原则组织的连续存储空间。每个任务都应该配有自己的堆栈,为了满足任务切换和响应中断时保存CPU寄存器中的内容以及存储任务私有数据的需要。

1.创建任务堆栈:

在文件OS_CPU.H中专门定义了一个OS_STK数据类型。在应用程序中定义任务堆栈就非常简单,即定义一个OS_STK类型的一个数组即可。

#define TASK_STK_SIZE    512

OS_STK TaskStk [ TASK_STK_SIZE ];

当调用函数OSTaskCreate()来创建一个任务时,把数组的指针传递给函数OSTaskCreate()中的堆栈栈顶参数ptos,就可以把该数组与任务关联起来而成为该任务的任务堆栈。

INT8U OSTaskCreate(void (*task)(void *pd),    //指向任务的指针void *pdata,                //传递给任务的参数OS_STK *ptos,              //任务堆栈栈顶的指针INT8U prio                //指定任务优先级别的参数);

堆栈的增长方向随系统使用的处理器的不同而不同。有的处理器的堆栈增长是往上的,有的是往下的。在使用函数创建任务时,注意所使用的处理器对堆栈的增长方向的支持是向上(由低地址到高地址)还是向下(由高地址到低地址)的。

  • 如果是向上增长,应写成OSTaskCreate( MyTask , &MyTaskAgu ,&MyTaskStk[0] , 20);
  • 如果是向下增长,OSTaskCreate( MyTask , &MyTaskAgu ,&MyTaskStk[MyTaskStkN-1] , 20);

2.任务堆栈的初始化

在系统启动任务时,CPU的个寄存器需要预置一些初始数据,这些初始数据从任务堆栈中获取。为此,应用程序在创建一个任务时,就必须把CPU需要的初始数据放在任务堆栈中,以便任务获得CPU使用权时,任务可以顺利进行。

任务堆栈的初始化是由操作系统完成的。μC/OS-II在创建任务函数OSTaskCreate()中通过调用任务堆栈初始化函数OSTaskStkInit()来完成任务堆栈的初始化。

四、任务控制块及任务控制块链表

μC/OS-II用来记录任务的堆栈指针、任务的当前状态、任务的优先级别等一些与任务管理有关的属性的表叫做任务控制块OS_TCB。它负责把任务代码,任务堆栈进行关联,而使任务控制块、任务代码、任务堆栈成为一个整体,并且系统通过这个任务控制块来感知和管理一个任务,没有任务控制块是不能被系统承认和管理的。

μC/OS-II把所有任务的控制块链接为两条链表,并通过这两条链表管理各任务控制块。

1.任务控制块的结构

当用户应用程序调用OSTaskCreate()函数创建一个任务时,这个函数会对任务控制块中的成员赋予与该任务有关的数据,并驻留在RAM中。

typedef struct os_tcb
{OS_STK *OSTCBStkPtr;//指向任务堆栈栈顶的指针#if OS_TASK_CREATE_EXT_ENvoid *OSTCBExtPtr;//指向任务控制块扩展的指针OS_STK *OSTCBStkBottom;//指向任务堆栈栈底的指针INT32U OSTCBStkSize;//任务堆栈的长度INT16U OSTCBOpt;//创建任务时的选择项INT16U OSTCBId;//目前该域未被使用#endifstruct os_tcb *OSTCBNext;//指向后一个任务控制块的指针struct os_tcb *OSTCBPrev;//指向前一个任务控制块的指针#if(OS_Q_EN&&(OS_MAX_OS>=2)||OS_MBOX_EN||OS_Sem_EN)OS_EVENT *OSTCBEventPtr;//指向事件控制块的指针#endif#if(OS_Q_EN&&(OS_MAX_OS>=2))||OS_MBOX_ENvoid *OSTCBMsq;//指向传递给任务消息的指针#endifINT16U OSTCBDly;//任务等待的时限INT8U OSTCBStat;//任务的当前状态INT8U OSTCBPrio;//任务的优先级别INT8U OSTCBX;//用于快速访问就绪表的数据INT8U OSTCBY;//用于快速访问就绪表的数据INT8U OSTCBBitX;//用于快速访问就绪表的数据INT8U OSTCBBitY;//用于快速访问就绪表的数据#if OS_TASK_DEL_ENBOOLEAN OSTCBDelRea;//请求删除任务时用到的标志#endif}OS_TCB;

OS_TCBStat用来存放任务的当前状态:

OS_STAT_RDY就绪状态、OS_STAT_SEM等待信号量、OS_STAT_MBOX等待消息邮箱、OS_STAT_Q等待消息队列、OS_STAT_SUSPEND被挂起状态、OS_STAT_MUTEX等待互斥型信号量

2.任务控制块链表

μC/OS-II用两条链表来管理任务控制块。一条是空任务链表,其中所有的任务控制块还没有分配任务;另一条是任务块链表,其中的任务控制块已经分配给任务。空任务块链表是在应用程序调用函数OS_Init()对系统初始化时建立的,而任务控制块链表是在调用OS_TaskCreate()创建任务时建立的。建立任务控制块链表的具体做法是:从空任务控制块链表摘取一个空任务控制块,然后填充上任务属性后,再形成新的链表。

在OSIinit()初始化时,在RAM中建立一个OS_TCB类型的数组OSTCBTbl[ ],这样每个数组元素就是一个任务控制块,然后把这些任务控制块连接成一个链表。此时,为空链表。初始化时,建立的空链表元素是OS_MAX_TASKS+OS_N_SYS_TAKS个。

  • OS_MAX_TASKS定义在OS_CFG.H文件中,表示用户任务的最大数目;
  • OS_N_SYS_TAKS定义在UCOS_H.H中,表示系统任务的数目(一个空闲任务,一个统计任务)

每当应用程序调用OSTaskCreate()或者OSTaskCreateExt()创建任务时,系统会将空任务控制块链表头指针OSTCBFreeList指向任务控制块分配给该任务。给任务控制块的个成员赋值之后,就按任务控制块联表的头指针OSTCBList将其加入到任务控制块链表中。

  • OSTCB *OSTCBPrioTbl [ ] 以任务的优先级别为顺序在各个数组元素里存放各个任务控制块的指针
  • OSTCB *OSTaskCur存放当前正在运行的任务控制块指针

μC/OS-II允许用函数OSTaskDel()删除一个任务。实质上是将任务从任务控制块链表中删除,把它归还给空任务控制块链表。

3.任务控制块的初始化 

给用户任务分配任务控制块及其进行初始化也是操作系统的职责。当应用程序调用OSTaskCreate()创建任务时,这个函数会调用OSTCBInit()来进行任务控制块的初始化。这个函数首先从空任务控制块链表中获取一个任务控制块,对其成员进行赋值;最后将这个任务控制块链入到任务控制块链表的头部。

INT8U OSTCBInit(INT8U prio,//任务优先级
OS_STK * ptos,//任务堆栈栈顶指针
OS_STK * pbos,//任务堆栈栈底指针
INT16U id,//任务标识符
INT16U stk_size,//任务堆栈长度
void * pext,//任务控制块的扩展指针
INT16U opt//任务控制块的选择项);

五、任务就绪表及任务调度

RTOS的核心工作就是任务调度。调度就是通过一个算法在多个任务中确定哪个任务来运行。完成这项任务的函数叫做调度器。μC/OS-II进行调度的思想是,每时每刻让优先级最高的就绪任务处于运行状态。它在系统或用户任务调用系统函数及执行中断服务程序结束时,总是调用调度器来确定应该运行的任务并运行它。μC/OS-II进行调度的依据就是任务就绪表。

μC/OS-II用INT8U的数组OSRdyTbl[ ] 作为就绪表。那么,数组的一个元素就可以表示8个任务的就绪状态。又使用数据类型为INT8U的变量OSRdyGrp,这个变量的每一位对应OSRdyTbl[ ]的1个元素(一个任务组)。当OSRdyTbl[ ]中的一个元素(一个任务组)中有一个任务为就绪状态(置1)时,对应的OSRdyGrp的位置1。如果OSRdyGrp=00000001,那么,OSRdyTbl[0]任务组中有任务就绪。这种格式下,μC/OS-II可以最多管理8*8=64个任务。

如何根据优先级来确定任务在就绪表中的位置?

1.对任务就绪表的操作 

(1)把优先级为prio的任务置为就绪状态

OSRdyGrp | = OSMapTbl[prio>>3];
OSRdyTbl[prio>>3] | = OSMapTbl[prio&0x07];

(2)把优先级为prio的任务脱离就绪状态

if((OSRdyTbl[prio>>3]&= - OSMapTbl[prio&0x07])==0)OSRdyGrp&=-OSMapTbl[prio>>3];

(3)从任务就绪表中获取优先级别最高的就绪任务

y=OSUnMapTal[OSRdyGrp];
x=OSUnMapTal[OSRdyTbl[y]];
prio=(y<<3)+x;

2.任务的调度

在多任务系统中,令CPU中止当前正在运行的任务而去运行另一个任务的工作叫做任务切换,而按照某种规则进行任务切换的工作叫做任务的调度

在μC/OS-II中,任务的调度是由任务调度器来完成的。它的工作有两项:1.查找最高优先级的就绪任务;2.实现任务切换。μC/OS-II有两种调度器,一是任务级的调度器,二是中断级的调度器。

任务级的调度器由函数OSSched()来实现,中断级的调度器由函数OSIntExt()来实现

查找最高优先级就绪任务的代码在上一节中说明,现在来学习如何实现任务切换。任务切换工作分为两个步骤:1.获得正在运行的任务的TCB指针。2.进行断点数据的切换。

(1)获得待运行就绪任务控制块的指针

调度器需要获得待运行任务的任务控制块指针和当前任务的任务控制块指针。因为被终止的任务的任务控制块指针存放在全局变量OSTCBCur中,所以调度器这部分的工作主要是获得待运行任务的任务控制块指针。

void OSSched(void)
{
#if OS_CIRTICAL_METHOD==3OS_CPU_SR cpu_sr;
#endifINT8U y;OS_ENTER_CRITICAL();if ((OSLockNesting|OSIntNesting)==0){y=OSUnMapTbl[OSRdyGrp];OSPrioHighRdy=(INT8U)((y<<3)+UnMapTbl[OSRdyTbl[y]]);//得到最高优先级任务if( OSPrioHighRdy!=OSPrioCur){OSPrioHighRdy=OSTCBPrioTbl[OSPrioHighRdy] ;//得到任务控制块指针OSCtxSwCtr++;//统计任务切换次数的计数器+1OS_TASK_SW();}}OS_EXIT_CRITICAL();
}

μC/OS-II允许应用程序通过调用函数OSSchedLock()和OSSchedUnlock()给调度器上锁和解锁。为了记录被锁和解锁的情况,定义了一个变量OSLockNesting。被上锁一次,+1;被解锁一次,-1.调度器OSSched()在确认未被上锁并且不是中断服务程序调佣调用器的情况下,查找最高优先级,然后确认这个任务不是当前运行的任务。最后,获得待运行任务控制块和当前任务控制块指针,在 OS_TASK_SW()这个宏中实施任务切换。

(2)任务切换宏 OS_TASK_SW()

实际上,任务切换的工作是靠OSCtxSw()来完成的。任务切换就是中止当前正在运行的任务,转而去运行另外一个任务。

任务切换的过程及其原理如果把任务被中止的位置叫做断点,把当时存放在CPU的PC、PSW和通用寄存器等各个寄存器中的数据叫做断点数据,那么当任务恢复运行时,必须在断点处以断点数据作为初始数据接着运行,才能实现无缝接续。因此,必须把断点数据保存到堆栈中。被中止的任务要恢复运行,关键在于正确地恢复断点数据,CPU的堆栈指针SP是否指向正确。如果用另外一个任务的堆栈指针,就实现任务切换了。为了防止被中止的任务堆栈指针丢失,保存断点时,把SP值保存到该任务控制块的成员OSTCBStkPtr中。任务的切换就是断点数据的切换,也就是CPU堆栈指针的切换,被中止运行的任务的任务堆栈指针要保存到该任务的任务控制块中,待运行任务的任务堆栈指针要由该任务的任务控制块转存到CPU的SP中。

 OSCtxSw()要完成以下工作:

  1. 把被中止任务的断点指针保存到任务堆栈中
  2. 把CPU通用寄存器的内容保存到任务堆栈中
  3. 把被中止任务的任务堆栈指针当前值保存到该任务的任务控制块的OSTCBStkPtr中
  4. 获得待运行任务的任务控制块
  5. 使CPU通过任务控制块获得待运行任务的任务堆栈指针
  6. 把待运行任务堆栈中通用寄存器的内容恢复到CPU的通用寄存器中
  7. 使CPU获得待运行任务的断点指针(该指针是待运行任务在上一次被调度器中止运行时保留在任务堆栈中的)

μC/OS-II中指针OSTCBCur指向当前正在运行任务的任务控制块,调度器的前面代码中已经获得了待运行任务的任务控制块指针OSTCBHighRdy。完成2~6项工作非常容易:

用压栈指令把CPU适用寄存器R1、R2...压入堆栈;OSTCBCur->OSTCBStkPtr=SP;      //把SP保存在中止任务控制块中
OSTCBCur=OSTCBHighRdy;          //使系统获得待运行任务控制块
SP=OSTCBHighRdy->OSTCBStkPtr;    //把待运行任务堆栈指针赋给SP用出栈指令把R1、R2...弹入CPU的通用寄存器;

完成第1项和第7项:CPU的PC寄存器保存了断点指针,如果要运行待运行任务,那么需要将任务堆栈中上次任务被中止时存放在堆栈中的中断指针推入PC寄存器。但是目前的处理器没有对PC寄存器的出战和入栈指令。另一个办法是:想办法引发一次中断,或者一次调用,并让中断向量指向OSCtxSw()(其实这是一个中断服务程序。利用IRET中断返回指令,把断点指针推入CPU的PC寄存器,恢复待运行任务的断点,实现断点的保存和恢复。

void OSCtxSw(void)
{用压栈指令把CPU适用寄存器R1、R2...压入堆栈;OSTCBCur->OSTCBStkPtr=SP;      //把SP保存在中止任务控制块中OSTCBCur=OSTCBHighRdy;          //使系统获得待运行任务控制块OSPrioCur=OSPrioHighRdy;SP=OSTCBHighRdy->OSTCBStkPtr;    //把待运行任务堆栈指针赋给SP用出栈指令把R1、R2...弹入CPU的通用寄存器;IRET;//中断返回,使PC指向待运行任务
}

六、任务的创建

  1. OSTaskCreate()
  2. OSTaskCreateExt(),是OSTaskCreate()的扩展,提供了一些附加功能

创建任务实际上是创建一个任务控制块,通过任务控制块将任务代码和任务堆栈联系起来。还要使创建的任务进入就绪状态,接着引发一次任务调度。

1.OSTaskCreate()创建任务

入口参数:指向任务的指针,传递给任务的参数,任务堆栈栈顶的指针,任务优先级
INT8U OSTaskCreate(void(*task)(void*pd),void*pdata,OS_STK*ptos,INT8U prio)
{
#if OS_CRITICAL_METHOD==3OS_CPU_SR cpu_sr;
#endifvoid *psp;INT8U err;if(prio>OS_LOWEST_PRIO)//检查优先级是否合法{return (OS_PRIO_INVALID);}OS_ENTER_CRITICAL();if(OSTCBPrioTbl[prio]==(OS_TCB*)0)//确认优先级未被使用{OSTCBPrioTbl[prio]=(OS_TCB*)1;//保留优先级OS_EXIT_CRITICAL();psp=(void*)OSTaskStkInit(task,pdata,ptos,0);//初始化任务堆栈err=OSTCBInit(prio,psp,(void*)0,0,0,(void*)0,0);//获得并初始化任务控制块if(err==OS_NO_ERR){OS_ENTER_CRITICAL();OSTaskCtr++;   //任务计数器+1if(OSRunning){OSSched();  //任务调度}}else{OS_ENTER_CRITICAL();OSTCBPrioTbl[prio]=(OS_TCB*)0 ;//放弃任务OS_EXIT_CRITICAL(); }return (err);}else{OS_EXIT_CRITICAL(); return (OS_PRIO_EXIST);  }}

2.OSTaskCreateExt()创建任务

 3.创建任务的一般方法

μC/OS-II有一个规定:在调用OSStart()之前,必须已经创建了一个至少一个任务,并且优先级最高,使它成为起始任务,然后在这个起始任务中,再创建其他任务。如果要使用统计任务,统计任务的初始化函数也必须在这个起始任务中来调用。μC/OS-II不允许在中断服务程序中创建任务。

56

更多推荐

嵌入式实时操作系统(一)

本文发布于:2024-02-13 14:02:40,感谢您对本站的认可!
本文链接:https://www.elefans.com/category/jswz/34/1759070.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
本文标签:嵌入式   实时   操作系统

发布评论

评论列表 (有 0 条评论)
草根站长

>www.elefans.com

编程频道|电子爱好者 - 技术资讯及电子产品介绍!