大多数初级mcu工程师所使用的的程序框架多为前后台或者轮询方式,当整个系统中有多个任务时,且有高低优先级的划分,这个时候如果使用前后台或者轮询方式会使高优先级的任务不能及时的响应。所以RTOS是每个mcu工程师技术成长过程中必须掌握的一份技能,作为RTOS初学者, 我们刚开始接触freertos时,面对较多的代码都会有一种懵的感觉,感觉要拿下它实属不易。本文作者也是刚刚接触freertos不久,深感学习过程中的艰难,故自己手动编程实现一个简易的freertos实时操作系统。整个工程代码量很少,实现了任务创建、堆栈分配、任务启动、任务切换、任务抢占等功能,我们学习freertos就会知道,这些都是rtos核心,其他的大多数代码都是为这些功能的辅助或者在此基础上的扩展。我们了解了rtos的本质后,再去学习官方的rtos,会有拨开云雾见天日感觉。总之希望自己的学习总结能对众多初次接触rtos的小伙伴有所帮助。
1、链表实现
链表是RTOS学习必须掌握的技能,作为mcu工程师,虽然平时工作中很少用到复杂的数据结构算法,大家大学时应该多多少少都学习过简单的数据结构,freertos中的列表就是数据结构中的循环链表。
列表的声明如下:

struct xList;
/*define Full function list*/
typedef struct List_Item
{uint32_t ItemValue;               /*<!该列表元素在整个链表中的位置*/struct List_Item* pxNext;         /*<!指向下一个列表元素*/struct List_Item* pxPrevious;     /*<!指向上一个列表元素*/void* pvOwner;                    /*<!指向该列表元素所包含的任务*/struct xList* pxContainer;        /*<!指向该列表元素所包含哪一个列表头结点*/
}list_item;
/*define smart function list*/
typedef struct MinList_Item
{uint32_t ItemValue;               /*<!该列表元素在整个链表中的位置*/struct List_Item* pxNext;         /*<!指向下一个列表元素*/struct List_Item* pxPrevious;     /*<!指向上一个列表元素*/
}minlist_item;
/*define list struct*/
typedef struct xList
{uint32_t list_number;              /*<!列表中目前总共的列表数*/list_item* pxIndex;               /*<!所指向的列表元素*/minlist_item xListEnd;           /*<!列表的尾结点*/
}xlist;

这里的 minlist_item 其实就是数据结构中的头结点,无具体内容,在遍历整个链表时使用。因为是循环链表,这里的头结点即尾结点。

列表的函数定义如下:

/*!\brief      xList end insert new list function\param[in]  xlist* const pxList  \arg       xlist \param[in]  xlist* const pxList  \arg       insert new list\param[out] none\retval     none
*/
void vListInsertEnd(xlist* const pxList,list_item* const pxNewListItem)
{list_item* const pxIndex=pxList->pxIndex;/*list end insert new list*/pxNewListItem->pxNext=pxIndex;pxNewListItem->pxPrevious=pxIndex->pxPrevious;pxIndex->pxPrevious->pxNext=pxNewListItem;pxIndex->pxPrevious=pxNewListItem;pxNewListItem->pxContainer=pxList;/*pxList contain new list*/pxList->list_number+=1;/* pxList number add 1*/
}/*!\brief      list insert new list in any location\param[in]  xlist* const pxList  \arg       xlist \param[in]  xlist* const pxList  \arg       insert new list\param[out] none\retval     none
*/
void vListInsert(xlist* const pxList,list_item* const pxNewListItem)
{uint32_t Location_Value=pxNewListItem->ItemValue;/*insert list location*/list_item* pxIterator;/*find new list insert location*/if(0xffffffff==Location_Value){pxIterator=pxList->xListEnd.pxPrevious;}else{pxIterator=(list_item*)&(pxList->xListEnd);while(pxIterator->pxNext->ItemValue<Location_Value){pxIterator=pxIterator->pxNext;}      }/*insert new list*/pxNewListItem->pxNext=pxIterator->pxNext;pxNewListItem->pxNext->pxPrevious=pxNewListItem;pxNewListItem->pxPrevious=pxIterator;pxIterator->pxNext=pxNewListItem;pxNewListItem->pxContainer=pxList;/*pxList contain new list*/pxList->list_number+=1;/* pxList number add 1*/
}/*!\brief      list remove a list in any location\param[in]  list_item* const pxItemToRemove  \arg       remove list\param[out] remove list ItemValue\retval     pxItemToRemove->ItemValue
*/
uint32_t uxListRemove(list_item* const pxItemToRemove)
{/*The list item knows which list it is in*/xlist* const pxList=pxItemToRemove->pxContainer;/*delete list item*/pxItemToRemove->pxPrevious->pxNext=pxItemToRemove->pxNext;pxItemToRemove->pxNext->pxPrevious=pxItemToRemove->pxPrevious;/* Make sure the index is left pointing to a valid item. */if(pxList->pxIndex==pxItemToRemove){pxList->pxIndex=pxItemToRemove->pxPrevious;  }pxItemToRemove->pxContainer=NULL;/*contain list item is NULL*/pxList->list_number-=1;/* pxList number reduce 1*/return pxItemToRemove->ItemValue;/*return remove list ItemValue*/
}

这部分主要是列表的尾部插入、任意位置插入、将列表元素从包含的列表删除等。因为这块主要是数据结构方面的知识,就不做过多的讲解。

2、任务创建、堆栈分配、启动、切换、抢占实现
任务结构体声明

/*define task control struct*/
typedef struct TaskControl{uint32_t* pxTopOfStack;/*该任务堆栈的栈顶位置*/list_item xStateListItem;/*任务的状态列表,表示此刻任务所处的状态:就绪、挂起、阻塞*/ list_item xEventListItem;/*任务的事件列表,用于任务等待某一个信号量,解除阻塞*/uint32_t  uxPriority;/*该任务的优先级*/uint32_t* pxStack;/*栈的起始位置,用于任务堆栈溢出检测*/  uint32_t  TaskNumber;/*该任务是第几个被创建*/uint32_t  uxBasePriority;/*用于任务优先级被临时提高时,保存任务原有的优先级*/uint32_t  ulRunTimeCounter;/*用于记录任务任务运行的时间*/
}Task;

官方RTOS源码任务的声明代码量很多,本文做了一定的删减,但是像任务堆栈溢出检测、任务阻塞(绝对延时和相对延时)、信号量等还没有实现,只是实现了任务创建、堆栈分配、启动、切换、抢占等,打算后期手动实现,做相关实验后会更新博客。
任务创建

/*!\brief      任务初始化\param[in]  pxTaskCode  \arg       该任务执行的函数地址\param[in]  usStackDepth  \arg       任务堆栈的深度\param[in]  uxPriority  \arg       任务的优先级\param[in]  pxTCB  \arg       任务结构体指针\param[out] none\retval     none
*/
void Creat_Task(TaskFunction_t pxTaskCode,const uint32_t usStackDepth,uint32_t uxPriority,Task* pxTCB,char* stack_address)
{pxTCB->uxPriority=uxPriority;/*任务优先级赋值*/pxTCB->uxBasePriority=uxPriority;/*优先级翻转或继承时,保存任务原有的优先级*/vListInitialiseItem(&(pxTCB->xStateListItem));/*任务状态列表的初始化*/vListInitialiseItem(&(pxTCB->xEventListItem));/*任务事件列表的初始化*/pxTCB->xEventListItem.pvOwner=(void*)pxTCB;/*任务事件列表包含的任务指向本任务*/pxTCB->xEventListItem.ItemValue=uxPriority;/*状态链表保存任务优先级*/pxTCB->xStateListItem.pvOwner=(void*)pxTCB;/*任务状态列表包含的任务指向本任务*/pxTCB->pxTopOfStack=(uint32_t*)(stack_address+(usStackDepth * sizeof(uint32_t)));/*计算栈顶的位置*/pxTCB->pxStack=(uint32_t*)stack_address;/*栈顶的起始地址*/pxTCB->pxTopOfStack=TaskInitialStack(pxTCB->pxTopOfStack,pxTaskCode);/*任务栈的初始化*/AddNewTaskToReadyList(pxTCB);/*添加本任务到就绪链表中*/
}

任务堆栈的初始化

/*!\brief      任务初始化\param[in]  pxTopOfStack  \arg       任务的栈顶位置\param[in]  pxTaskCode\arg       任务执行的函数指针\param[out] none\retval     初始化后栈顶的位置
*/
uint32_t* TaskInitialStack(uint32_t* pxTopOfStack,TaskFunction_t pxTaskCode)
{pxTopOfStack--;*pxTopOfStack=portINITIAL_XPSR; /* xPSR */pxTopOfStack--;*pxTopOfStack=(uint32_t)pxTaskCode;/* PC */pxTopOfStack--;*pxTopOfStack=0;                   /* LR */pxTopOfStack--;*pxTopOfStack=0;                   /* R12 */pxTopOfStack--;*pxTopOfStack=0;                   /* R3 */pxTopOfStack--;*pxTopOfStack=0;                   /* R2 */pxTopOfStack--;*pxTopOfStack=0;                   /* R1 */pxTopOfStack--;*pxTopOfStack=0;                   /* R0 */pxTopOfStack--;*pxTopOfStack=0;                   /* R11 */pxTopOfStack--;*pxTopOfStack=0;                   /* R10 */pxTopOfStack--;*pxTopOfStack=0;                   /* R9 */pxTopOfStack--;*pxTopOfStack=0;                   /* R8 */pxTopOfStack--;*pxTopOfStack=0;                   /* R7 */pxTopOfStack--;*pxTopOfStack=0;                   /* R6 */pxTopOfStack--;*pxTopOfStack=0;                   /* R5 */pxTopOfStack--;*pxTopOfStack=0;                   /* R4 */return pxTopOfStack;
}

freertos 实时操作系统的本质并不是多个任务并行执行,每个时刻其实只有一个任务在执行,其每个任务都有自己的堆栈空间。在任务执行时,从堆栈空间出栈,将保存的数据赋值给PC、LR、R0~R3、R4 ~ R11等,PC从该任务中断处继续执行。在任务切换时,将当前PC、LR、R0 ~R3、R4 ~R11等值入栈,保存到该任务的堆栈中,方便下次任务执行时出栈使用。
portINITIAL_XPSR是表示使用ARM指令集还是Thumb指令集,包括堆栈初始化的顺序,我们可以参考cortex-M3手册中断异常章节。R4 ~R11需要我们自己手动入栈或出栈,R0 ~R3、PC、LR、xPSR这些在进入退出异常中断时,由硬件自行入栈或出栈。这块内容大家要结合cortex-M3内核手册理解,因为很多都是内核相关定死的,说白了我们的堆栈初始化就是伪造一个现场,既然是伪造,那肯定的按照人家的规矩来啦。 portINITIAL_XPSR 是一个宏定义,它的值为0x01000000,查阅内核手册,我们可以得知其24bit置1表明使用thumb指令。任务堆栈初始化,初次PC指针指向的地址肯定为任务函数的地址,当任务被中断时,则是保存任务函数中断时执行的地址。
任务加入当前就绪列表

/*!\brief      添加任务到就绪链表\param[in]  pxTCB \arg       任务的结构体指针\param[out] none\retval     none
*/
void AddNewTaskToReadyList(Task* pxTCB)
{uint32_t uxPriority;uxCurrentNumberOfTasks+=1;/*当前就绪任务数加1*//*There are no other tasks*/if(NULL==pxCurrentTCB){pxCurrentTCB=pxTCB;/* 没有任务被创建,该任务是第一个被创建*/MAX_PRIORITIES=pxTCB->uxPriority;if(1==uxCurrentNumberOfTasks){/*第一个任务被创建、就绪列表数组元素初始化、阻塞延时列表、挂起列表初始化*/for(uxPriority=0;uxPriority<configMAX_PRIORITIES;uxPriority++){vListInitialise(&pxReadyTasksLists[uxPriority]);}vListInitialise(&xDelayedTaskList);vListInitialise(&xSuspendedTaskList);}uxTaskNumber+=1;/* 第几个任务被创建*/ pxTCB->TaskNumber=uxTaskNumber; vListInsertEnd(&pxReadyTasksLists[pxTCB->uxPriority],&pxTCB->xStateListItem);/*将该任务添加到对应优先级的就绪链表中*/    }else{/* 该任务不是第一个被创建,当前任务调度未开始 */if(0==xSchedulerRunning){/*new task priority more than runing task */if(pxCurrentTCB->uxPriority<=pxTCB->uxPriority){pxCurrentTCB=pxTCB;MAX_PRIORITIES=pxTCB->uxPriority;/*MAX_PRIORITIES记录当前*/}else{}                }uxTaskNumber+=1;pxTCB->TaskNumber=uxTaskNumber;/*task statelist insert readylist same priority list end*/vListInsertEnd(&pxReadyTasksLists[pxTCB->uxPriority],&pxTCB->xStateListItem);/* 当任务在执行过程中被创建*/if(xSchedulerRunning!=0){if(pxCurrentTCB->uxPriority<=pxTCB->uxPriority){//pxCurrentTCB=pxTCB; /* 此处代码不被屏蔽,就会出现很奇怪的问题,当初调试时,费了很长时间,后面会更新博客具体讲解*/MAX_PRIORITIES=pxTCB->uxPriority;/*task switch*/*( portNVIC_INT_CTRL ) = portNVIC_PENDSVSET;/*在任务执行过程中更高优先级的任务被创建,此时就需要强制执行一次任务切换*/}}       }
}

任务的启动

/*!\brief      task start\param[in]  none \arg       none\param[out] none\retval     none
*/
void Task_Start(void)
{systick_config();/*systick 中断初始化,10ms中断一次*/NVIC_SetPriority(PendSV_IRQn, 0x07U);/*挂起中断、和systick中断的优先级设为最低*/xSchedulerRunning=1;/*任务启动标志位置1 *//*start first task*/vStartFirstTask();/*执行任务启动函数*/
}

通过GD官方手册可知,NVIC异常有3bit,所以中断优先级为0~7,我们这里将systick和Pendsv中断设为7最低优先级的目的是为了防止systick和Pendsv挂起中断打断外设中断,造成外设中断处理被延时。这种情况是万万不可的,外设的中断优先级一定是最高的。通过了解freertos任务启动过程,我们可以知道通过触发一个SVC任务调度中断,这部分的代码我们需要用汇编去实现。

vStartFirstTask PROC  ldr R0, =0xE000ED08  ;查阅Cortex-M3手册可知0xE000ED08为保存中断向量表地址寄存器ldr r0, [r0]  ;将中断向量表起始地址保存在RO中     ldr r0, [r0]  ; 向量表起始地址保存的是栈顶指针,取栈顶指针 msr msp,  r0  ; 将栈顶指针的内容赋值给mspcpsie i ;使能全局中断cpsie fdsb ;指令集隔离isbsvc 0  ;触发一个SVC中断nopnop    ENDP
SVC_Handler    PROCEXPORT  SVC_Handler                    IMPORT  pxCurrentTCB ;表示该变量是全局变量,不在本文件中定义ldr r3, =pxCurrentTCB ;将存储该指针变量的地址赋值给R3ldr r1,  [r3] ;取变量指针值赋值给R1ldr r0,  [r1] ;任务变量的起始地址存储的是该任务栈顶指针ldmia r0!, {r4 - r11} ;将任务栈中的r4-r11出栈赋值给r4-r11msr psp, r0 ;将此时的栈顶指针赋值给用户堆栈指针,用于退出异常时,硬件出栈恢复R0~R3、PC、LR、xPSR的值isbmov r0, #0msr basepri, r0;屏蔽优先级大于0的中断,因为cortex-m3中0的优先级最大,其实就是不屏蔽任何中断orr r14, #0xd ;将LR链接地址的值改为0xFFFFFFFdbx r14 ;返回线程模式,并使用线程堆栈指针ENDP

以上的内容还是要结合Cortex-M3手册来学习理解。当我们Debug模式进入异常handler时,查看内核LR寄存器,可以看到此时LR的值为0xFFFFFFF9,通过查阅内核手册可知,此时SP指针为MSP主堆栈,那理解 orr r14, #0xd 这句代码的含义就不难了。这段汇编代码执行完后,就会执行当前最高优先级的任务。
Systick中断函数:

#define portNVIC_INT_CTRL                    ( ( volatile uint32_t * ) 0xe000ed04 ) /*interrupt control state register*/
#define portNVIC_PENDSVSET                  ( 0x10000000 ) /*trigger PendSv interrupt bit 28 */
/*!\brief      SysTick Handler\param[in]  none \arg       none\param[out] none\retval     none
*/
void SysTick_Handler(void)
{if(xSchedulerRunning!=0){/* Pend a context switch. */*( portNVIC_INT_CTRL ) = portNVIC_PENDSVSET;}
}

查阅内核手册,要触发一个PendSv中断,只需将中断状态控制寄存器的bit28置1即可,中断状态寄存器的地址为0xe000ed04,本程序是每隔10ms执行一次任务切换。
PendSv挂起中断

在这里插入代码片PendSV_Handler\PROCEXPORT  PendSV_Handler                    [WEAK]IMPORT  pxCurrentTCBIMPORT  vTaskSwitchContext ;声明vTaskSwitchContext为一个全局函数,不在本文件中定义mrs r0, psp ;将线程堆栈指针赋值给R0,此时的R0~R3、PC、LR、xPSR的值已经由硬件自动入栈到该任务的堆栈空间isbldr r3, =pxCurrentTCBldr r2, [r3];将当前运行任务的指针赋值给R2stmdb r0!, {r4 - r11};将R11-R4的值入栈到该任务堆栈空间str r0, [r2];保存当前任务的栈顶指针stmdb sp!, {r3,r14};将R14、R3的值入栈mov r0, #7msr basepri, r0;因为后面要进行一个任务切换,所以将优先级大于PendSv的中断都屏蔽dsbisbbl vTaskSwitchContext;跳转到任务切换C函数去执行mov r0, #0msr basepri, r0;打开所有中断ldmia sp!, {r3,r14};将R3、R14的值出栈ldr r1, [r3];取pxCurrentTCB变量指针值赋值给R1ldr r0, [r1];任务变量的起始地址存储的是该任务栈顶指针ldmia r0!, {r4 - r11};将任务栈中的r4-r11出栈赋值给r4-r11msr psp, r0;将此时的栈顶指针赋值给用户堆栈指针,用于退出异常时,硬件出栈恢复R0~R3、PC、LR、xPSR的值isbbx r14;跳转到当前运行任务上次中断的地址出继续执行nop ENDP

stmdb sp!, {r3,r14};将R14、R3的值入栈,这句代码的原因是此时我们要去跳转到C代码vTaskSwitchContext函数去执行代码,这个过程可能会改变LR的值,R3保存的是pxCurrentTCB变量指针,所以这两个都得入栈。这块我们得去查阅内核手册C函数和汇编相互调用的规则方面的一些知识,在此就不多讲了。
任务切换函数

/*!\brief      task switch\param[in]  none \arg       none\param[out] none\retval     none
*/
void vTaskSwitchContext(void)
{uint32_t Max_Priority=MAX_PRIORITIES;/*find max priority readlist */while(0==pxReadyTasksLists[Max_Priority].list_number){Max_Priority-=1;}        MAX_PRIORITIES=Max_Priority;//printf("Max_Priority is %d\n",Max_Priority);pxReadyTasksLists[Max_Priority].pxIndex=pxReadyTasksLists[Max_Priority].pxIndex->pxNext;if((void*)pxReadyTasksLists[Max_Priority].pxIndex==(void*)&(pxReadyTasksLists[Max_Priority].xListEnd)){pxReadyTasksLists[Max_Priority].pxIndex=pxReadyTasksLists[Max_Priority].pxIndex->pxNext;}pxCurrentTCB=pxReadyTasksLists[Max_Priority].pxIndex->pvOwner;
}

这部分代码主要实现的是在就绪列表数组中找出当前优先级最大的链表,然后按时间片轮流执行相同优先级的任务。

3、实验验证
以上我们的主体代码已经全部讲完,下面就是实验验证过程,代码如下

#include "gd32f1x0.h"
#include <stdio.h>
#include "main.h"
#include "gd32f1x0_eval.h"
#include "task.h"__align(4) static char Task_Stack_A[512]={0};
__align(4) static char Task_Stack_B[512]={0};
__align(4) static char Task_Stack_C[512]={0};
void Task_A(void);
void Task_B(void);
void Task_C(void);
__align(4) Task TCB_A,TCB_B,TCB_C;
/*!\brief      main function\param[in]  none\param[out] none\retval     none
*/
int main(void)
{gd_eval_com_init(EVAL_COM1); Creat_Task(Task_A,128,2,&TCB_A,Task_Stack_A);Creat_Task(Task_B,128,2,&TCB_B,Task_Stack_B);//Creat_Task(Task_C,128,3,&TCB_C,Task_Stack_C);Task_Start();//while (1);
}void Task_A(void)
{while(1){printf("taskA is running!!!\n");}
}void Task_B(void)
{uint16_t num=0;while(1){printf("taskB is running!!!\n");num++;if(num==10){Creat_Task(Task_C,128,3,&TCB_C,Task_Stack_C);     }}
}void Task_C(void)
{while(1){printf("taskC is running!!!\n");}
}
/* retarget the C library printf function to the USART */
int fputc(int ch, FILE *f)
{usart_data_transmit(EVAL_COM1, (uint8_t)ch);while(RESET == usart_flag_get(EVAL_COM1, USART_FLAG_TBE));return ch;
}

以上测试代码主要创建了3个任务TCB_A,TCB_B,TCB_C,任务A和任务B的优先级相同,任务C的优先级高于任务A、B,任务C是任务B在执行10次后被创建。任务A、B、C的堆栈空间我们是用最简单的数组分配来定义,不像RTOS代码中有静态分配和动态分配两种。程序运行结果我们可以在串口助手上看到刚开始是任务A和任务B轮流运行,当B运行10次后,任务C被创建,因为任务C的优先级是高于任务A和任务B的,所以后面我们可以看到只有任务C一个在执行。我们从freertos代码实现的原理可知,高优先级的任务永远不可能被低优先级的任务打断,那我们在实际的项目应用中,不可能所有的任务的优先级都设为最高,那怎么办呢?这个时候就得引入任务阻塞延时概念了,让高优先级的任务进入阻塞,此时是将高优先级的任务从就绪列表中移除,移入到阻塞延时列表中,那么此时低优先的任务不就可以执行了么。当延时时间到,又将高优先级的任务从阻塞延时列表中移除再次加入就绪列表,那么高优先级的任务不就可以再次运行了。所谓风水轮流转,不能因为你级别高,就一直霸占资源吧,哈哈!(开个玩笑)。

以上所讲的源代码我都放到https://download.csdn.net/download/qq_31446727/85103590地址了,大家可以去下载学习,因为本人也刚刚接触FreeRtos不久,有些地方理解可能不到位,如果哪里理解不对,可以在博文下面留言,大家共同学习进步!!!

基于GD32F10x手动编程实现简易freertos实时操作系统相关推荐

  1. 基于NXP iMX7 ARM处理器部署FreeRTOS实时操作系统

    1). 简介 FreeRTOS是广泛使用的开源实时操作系统, 被众多芯片厂商包括NXP所支持, 本文就展示在NXP iMX7 ARM处理器上面的M4核心上面部署FreeRTOS. NXP iMX7 A ...

  2. FreeRTOS实时操作系统(七)时间片调度及RTOS的滴答定时器

    系列文章目录 FreeRTOS实时操作系统(一)RTOS的基本概念 FreeRTOS实时操作系统(二)任务创建与任务删除(HAL库) FreeRTOS实时操作系统(三)任务挂起与恢复 FreeRTOS ...

  3. STM32CubeMX学习笔记(28)——FreeRTOS实时操作系统使用(任务管理)

    一.FreeRTOS简介 FreeRTOS 是一个可裁剪.可剥夺型的多任务内核,而且没有任务数限制.FreeRTOS 提供了实时操作系统所需的所有功能,包括资源管理.同步.任务通信等. FreeRTO ...

  4. 基于正点原子探索者使用STM32CubeMX+FreeRTOS+LWIP

    开发板是使用正点原子的探索者为例,PHY芯片可以是LAN8720A和IP101GR,因为有两份代码参考,一份是LAN8720A,一份是IP101GR. 首先第一步:我们使用移植好的功能,请参使用STM ...

  5. Intewell工业实时操作系统亮相2022 第二届工控中国大会

    11月3日-5日,由中国电子信息产业发展研究院等单位举办的 " 2022 第二届工控中国大会暨工业软件产业链供需对接会" 在苏州太湖国际会议中心举办.工控中国大会是国内工控领域里的 ...

  6. 基于STM32的实时操作系统FreeRTOS移植教程(手动移植)

    前言:此文为笔者FreeRTOS专栏下的第一篇基础性教学文章,其主要目的为:帮助读者朋友快速搭建出属于自己的公版FreeRTOS系统,实现后续在实时操作系统FreeRTOS上的开发与运用.操作系统的学 ...

  7. 基于STM32的简易示波器的UCOS II嵌入式实时操作系统实现

    基于STM32的简易示波器的UCOS II嵌入式实时操作系统实现 在基于STM32的示波器的实现的基础上,在STM32上移植UCOS II嵌入式实时操作系统. 在UCOS II操作系统中将各个功能分发 ...

  8. stm32中用到的实时系统_基于STM32平台的实时操作系统

    基于STM32平台且满足实时控制要求操作系统,有以下5种可供移植选择,分别为μClinux.μC/OS-II.eCos.FreeRTOS和都江堰操作系统(djyos). 下面分别介绍这五种嵌入式操作系 ...

  9. 【JAVA小游戏+水果售卖系统】基于GUI界面编程的水果“人生”模拟系统

    [JAVA]基于GUI界面编程的水果"人生"模拟系统 一.系统主要功能及简介 二.系统体系结构 三.系统设计技术 四.编码说明 五.效果展示 一.系统主要功能及简介 该系统以JAV ...

  10. px4原生源码学习四--Nuttx 实时操作系统编程

    前面说到px4是基于Nuttx实时操作系统上的,那么px4也是由一些程序所构成,这些程序实现了飞行器的自主控制,只不过这些程序并不是我们通常所见到的单片机或者windows编程那样的程序,但基本编程思 ...

最新文章

  1. 静态页转换平台(StaticPol)-静态页生成终极解决方案
  2. 计算机上能玩vr游戏吗,VR设备是什么? 听说可以用来玩电脑游戏的?
  3. 服务器被入侵了?反手溯源出入侵者画像【网络安全】
  4. 千里眼摄像头支持对象存储吗_【手机技术】专业相机应用Halide全面支持苹果iPhone SE 2人像模式...
  5. 【转载】PHP的(EOT)在PHP中添加html
  6. char[]和char*的区别(转)
  7. python学习高级篇(part7)--特殊属性和特殊方法
  8. C. Longest Simple Cycle
  9. P1046 [NOIP2005 普及组] 陶陶摘苹果
  10. Java停车场管理系统使用栈和队列任务台程序
  11. 信贷三类业务风险如何把控
  12. android return 如何跳出两个循环_PHP跳出循环的方法
  13. RocketDock 安装
  14. win10系统开启扫描仪服务器,win10怎么安装扫描仪 win10扫描仪怎么扫描
  15. 苹果怎么滚动截屏_30个小技巧,带你感受苹果系统到底有多好用
  16. 记录一次被Paypal坑的经过
  17. 计算H时M分S秒以后是_最全的风机计算公式,学习了!
  18. python 搜索引擎 词位置加权_如何提高python中的词移动距离相似度并利用加权senten提供相似度评分...
  19. 抑或运算符(位运算和逻辑运算符详解)----Java
  20. 《三体》-- 刘慈欣

热门文章

  1. java高级实训输出张三李四_假设某数据库表中有一个姓名字段,查找姓名为张三和李四的条件是...
  2. 小红书6.18种草拔草投放攻略,品牌制胜决策时刻
  3. 华为xpro重装linux,HUAWEI MateBook X Pro 2019款重装win10系统以及Bios设置方法
  4. SpringCloud第十章zuul路由网关
  5. 电脑版微信多开的三种方法
  6. 计算机IP名词解释,IP地址的名词解释
  7. network secruity studay day4
  8. word怎么只删除英语保留汉语或删除汉语保留英文
  9. python win7 安装失败 Service Pack 1
  10. 掂清自己在别人心中的分量,是人际…