STM32之实时操作系统(FreeRTOS)

1、FreeRTOS简介 

FreeRTOS是一个迷你的实时操作系统内核,作为一个轻量级的操作系统,功能包括:任务管理、时间管理、信号量、消息队列、内存管理、记录功能、软件定时器、协程等,可基本满足较小系统的需要。

由于RTOS需占用一定的系统资源,尤其是RAM资源,只有μC/OS-II、embOS、salvo、FreeRTOS等少数实时操作系统能在小RAM单片机上运行,相对μC/OS-II、embOS等商业操作系统,FreeRTOS操作系统是完全免费的操作系统,具有源码公开、可移植、可裁剪、调度策略灵活的特点,可以方便的移植到各种单片机上运行。

 

2、轮询、前后台以及多任务间的区别

    轮询:while(1){执行列表} 轮询任务执行;

    前后台:

中断:作为前台响应

循环:作为后台

    多任务:

       通过实时操作系统多任务协调处理

    轮询、前后台以及多任务系统软件模型之间的区别见表1.1

模型

事件响应

事件处理

特点

轮询系统

主程序

主程序

轮询响应事件,轮询处理事件

前后台系统

中断

主程序

实时响应事件,轮询处理事件

多任务系统

中断

任务

实时响应事件,实时处理事件

表1.1

3、FreeRTOS原理与实现

    任务调度机制是嵌入式实时操作系统的一个重要概念,也是核心技术,对于可剥夺型内核,优先级高的任务一旦就绪就能剥夺优先级较低任务的CPU使用权,提高了系统的实时响应能力。不同于μC/OS-II,FreeRTOS对系统任务的数量没有限制,既支持优先级调度算法也支持轮换调度算法,因此FreeRTOS采样双向链表而不是采用查任务就绪表的方法进行任务调度。系统定义的链表和链表节点数据结构如下所示:

//定义链表结构

typedef struct xLIST{

unsigned portSHORPT usNumberOfItems; //usNumberOfItems为链表的长度,为0表示链表为空

volatile xListItem * pxHead; //pxHead为链表的头指针

volatile xListItem * pxIndex; //pxIndex指向链表当前结点的指针

volatile xListItem xListEnd; //xListEnd为链表尾结点

}xList;

//定义链表结点的结构

struct xLIST_ITEM {

port Tick type; //port Tick Type为时针节拍数据类型,

xItem Value; //xItem Value的值用于实现时间管理,可根据需要选择为16位或32位

volatile struct xLIST_ITEM * pxNext; //指向链表的前一个结点

void * pvOwner; //指向此链表结点所在的任务控制块

void * pvContainer; //指向此链表结点所在的链表

};

FreeRTOS中每个任务对应于一个任务控制块(TCB),其定义如下所示:

typedef struct tskTaskControlBlock {

portSTACK_TYPE * pxTopOfStack; //指向任务堆栈结束处

portSTACK_TYPE * pxStack; //指向任务堆栈起始处

unsigned portSHORT usStackDepth; //定义堆栈深度

signed portCHAR pcTaskName[tskMAX_TASK_NAME_LEN]; //任务名称

unsigned portCHAR ucPriority; //任务优先级

xListItem xGenericListItem; //用于把TCB插入就绪链表或等待链表

xListItem xEventListItem; //用于把TCB插入事件链表(如消息队列)

unsigned portCHAR ucTCBNumber; //用于记录功能

}tskTCB;

FreeRTOS定义就绪任务链表数组为:xList pxReady—TasksLists[portMAX_PRIORITIES]。其中portMAX_PRIORITIES为系统定义的最大优先级,若想使优先级为n的任务进入就绪态,需要把此任务对应的TCB中的节点xGenericListltem插入到链表pxReadyTasksLiStS[n]中,还要把xGenericListItem中的pvContainer指向pxReadyTasksLists[n]方可实现。

    当进行任务调度时,调度算法首先实现优先级调度。系统按照优先级从高到低的顺序从就绪任务链表数组中寻找usNumberOfItems第一个不为0的优先级,此优先级即为当前最高就绪优先级,据此实现优先级调度。若优先级下只有一个就绪任务,则此就绪任务进入运行态;若此优先级下有多个就绪任务,则需采用轮换调度算法实现多任务轮流执行。

    如果在优先级n下执行轮换调度算法,系统先通过执行(pxReadyTasksLists[n])→pxIndex=(pxReadyTasks-Lists[n])→pxlndex→pxNext语句得到当前节点所指向的下一个节点,再通过此节点的pvOwner指针得到对应的任务控制块,最后使此任务控制块对应的任务进入运行态。由此可见,在FreeRTOS中,相同优先级任务之间的切换时间为一个时钟节拍周期。

 

4、算法实现任务切换

    当前共有三个任务,每个任务的优先级均是1,在任务执行中通过指针pxIndex可知任务1为当前任务,而任务1的pxNext节点指向任务2,因此系统把pxIndex指向任务2并执行任务2来实现任务调度。当下一个时钟节拍到来时,若最高就绪优先级仍未1,系统会把pxIndex指向任务3并执行任务3。

    为了加快任务调度,FreeRTOS通过变量ucTopReadyPriotity跟踪当前就绪的最高优先级,当把一个任务加入就绪链表时,如果此任务的优先级高于ucTopReadyPriority,则把这个任务的优先级赋予ucTopReadyPriority。这样当进行优先级调度时,调度算法不是从portMAX_PRIORITIES而是从ucTopReady-Priority开始搜索,这就加快了搜索的速度,同时缩短了内核关断时间。

 

5、任务管理的实现

    实现多个任务的有效管理是操作系统的主要功能。FreeRTOS下可实现创建任务、删除任务、挂起任务、恢复任务、设定任务优先级、获得任务相关信息等功能,当调用xTaskCreate()函数创建一个新的任务的时候,FreeRTOS首先为新任务分配所需的内存,如果内存分配成功,则初始化任务控制块的任务名称、堆栈深度和任务优先级,然后根据堆栈的增长方向初始化任务控制块的堆栈。接着FreeRTOS把当前创建的任务加入到就绪任务链表。若当前此任务的优先级最高,则把此优先级赋值给变量ucTopReadyPriorlty。若任务调度程序已经运行且当前创建的任务优先级为最高,则进行任务切换。

    不同于μC/OS-II,FreeRTOS任务删除分两步进行,当用户调用vTaskDelete()函数后,执行任务删除的第一步;FreeRTOS先把要删除的任务从就绪任务链表和事件等待链表中删除,然后把此任务添加到删除链表,若删除的任务是当前运行任务,系统就执行任务调度函数,至此完成任务删除的第一步。当系统空闲任务运行时,若发现任务删除链表中有等待删除的任务,则进行删除任务的第二步,即释放该任务占用的内存空间,并把该任务从任务删除链表中删除,这样才彻底删除了这个任务。值得注意的是,在FreeRTOS中,当系统被配置为不可剥夺内核时,空闲任务还有实现各个任务切换的功能。

   

6、时间管理的实现

    FreeRTOS提供的典型的时间管理函数是vTaskDelay(),调用此函数可以实现将任务延时一段特定时间的功能,在FreeRTOS中,若一个任务要延时xTicksToDelay个时钟节拍,系统内核会把当前系统已运行的时钟节拍总数(定义为xTickCount,32位长度)加上xTickToDelay得到任务下次唤醒时的时钟节拍数xTimeToWake。然后,内核把此任务的任务控制块从就绪链表中删除,把xTimeToWake作为节点值赋予任务的xItemValue,再根据xTimeToWake的值把任务控制块按照顺序插入不同的链表。若xTimeToWake>xTickCount,即计算中没有出现溢出,内核把任务控制块插入到pxDelayedTaskList链表;若xTimeToWake每发生一个时钟节拍,内核就会把当前的xTick-Count加1.若xTickCount的结果为0,即发生溢出,内核会把pxOverflowDelayedTaskList作为当前链表,否则,内核把pxDelaycdTaskList作为当前链表。内核依次比较xTickCotlrtt和链表各个节点的xTimeToWake。若xTick-Count等于或大于xTimeToWake,说明延时时间已到,应该把任务从等待链表中删除,加入就绪链表。

    由此可见,不同于μC/OS—II,FreeRTOS采样“加”的方式实现时间管理,其优点是时间节拍函数的执行时间与任务的数量无关,而μC/OS—II的OSTimcTick()的执行时间正比于应用程序中建立的任务数,因此当任务较多时,FreeRTOS采用的时间管理方式能有效加快时钟节拍中断程序的执行速度。

 

7、内存分配策略

    每当任务、队列和信号量创建的时候,FreeRTOS要求分配一定的RAM,虽然采样malloc()和free()函数可以实现申请和释放内存的功能,但这两个函数存在以下缺点:并不是在所有的嵌入式系统中都可以用,要占用不定的程序空间,可重入性欠缺以及执行时间具有不可确定性,为此,除了可采用malloc()和free()函数之外,FreeRTOS还提供了另外两种内存分配的策略,用户可以根据实际需要选择不同的内存分配策略。

  1. 第一种方法是,按照需求内存的大小简单的把一大块内存分割成若干个小块,每个小块的大小对应于所需求内存的大小,这样做的好处是比较简单的,执行时间可以严格确定,适用于任务和队列全部创建完毕后再进行内核调度额系统;这样做的缺点是,由于内存不能有效的释放,系统运行时应用程序并不能实现删除任务或队列。
  2. 第二种方式是采用链表分配内存,可实现动态的创建、删除任务或队列。系统根据空闲内存块的大小按从小到大的顺序组织空闲内存链表,当应用程序申请一块内存时,系统根据申请内存的大小按顺序搜索空闲内存链表,找到满足申请内存要求的最小空闲内存块,为了提高内存的使用效率,在空闲内存块比申请内存大的情况下,系统会把此空闲内存块一分为二。一块满足申请内存的要求,一块作为新的空闲内存块插入到链表中。该方法的缺点是无法把应用程序释放的内存和原有的空闲内存混合为一体,因此,若应用程序频繁的申请与释放随机大小的内存,就可能造成大量的内存碎片,这就要求应用程序申请与释放内存的大小为有限个固定的值,而且这一方法的程序执行时间具有一定的不确定性。

 

8、FreeRTOS的系统功能

    FreeRTOS作为一个轻量级的操作系统,FreeRTOS提供的功能包括:任务管理、时间管理 、信号量、消息队列、内存管理、记录功能,可基本满足较小系统的需要,FreeRTOS内核支持优先级调度算法,每个任务可根据重要程序的不同被赋予一定的优先级,CPU总是让处于就绪态、优先级最高的任务先运行,FreeRTOS内核同时支持轮换调度算法,系统允许在不同任务使用相同的优先级,在没有最高优先级就绪的情况下,同一优先级的任务共享CPU的使用时间。

FreeRTOS的内核可根据用户需要设置为可剥夺型内核或不可剥夺型内核。当FreeRTOS被设置为可剥夺型内核时,处于就绪态的高优先级任务能剥夺低优先级任务的CPU使用权,这样可保证系统满足实时性的要求;当FreeRTOS被设置为不可剥夺型内核时,处于就绪态的高优先级任务只有等当前运行任务主动释放CPU的使用权后才能获得运行,这样可提高CPU的运行效率。

void Bsp_int(void)
{
	LED_Config();
	beep_config();
	key_config1();
	usart_config(115200);
	//GPIOC->ODR |=(0x01<<5);
}

int main(void)
{
	BaseType_t xReturn = pdPASS;
	Bsp_int();
	xReturn = xTaskCreate((TaskFunction_t)AppTaskCreate,"AppTask",512,NULL,1,&AppTaskCreatHandle);//接收串口数据
	if(pdPASS == xReturn)
	{
		vTaskStartScheduler();
	}
	while(1)
	{
		
	}
}
/*************
函数名称:AppTaskCreate
函数功能:创建任务
函数参数:无
函数返回值:无
*************/
TaskHandle_t AppTaskCreatHandle = NULL;
TaskHandle_t LED1TaskCreatHandle = NULL;
TaskHandle_t BeepTaskCreatHandle = NULL;
TaskHandle_t KEYsendCreatHandle = NULL;
TaskHandle_t KEYreceiveCreatHandle = NULL;
SemaphoreHandle_t BinarySem_Handle =NULL;
EventGroupHandle_t Event_Handle =NULL;
TaskHandle_t QueueTask_Handle = NULL;
SemaphoreHandle_t semapphore_Handle=NULL;//计数信号量句柄
TimerHandle_t Time_Handle=NULL;
QueueHandle_t testtask;
#define Queue_Len 1
#define Queue_Size 10
void AppTaskCreate(void)
{
	BaseType_t xReturn = pdPASS;
	
	taskENTER_CRITICAL();//进入临界区
	
	xReturn = xTaskCreate(LEDTask,"LED1",56,NULL,1,&LED1TaskCreatHandle);
	if(xReturn ==pdPASS)
	{
		printf("LED1任务创建成功\r\n");
	}
	//xReturn = xTaskCreate(BeepTask,"Beep",56,NULL,2,&BeepTaskCreatHandle);//创建蜂鸣器任务
	testtask=xQueueCreate(Queue_Len,Queue_Size);//创建队列
	if(testtask!=NULL)
	{
		printf("创建队列成功 \r\n");
		vTaskDelay(10);
	}
	xReturn = xTaskCreate(USARTTask,"USART",200,NULL,9,&QueueTask_Handle);//接收串口数据
	BinarySem_Handle=xSemaphoreCreateBinary();//创建信号量
	if(BinarySem_Handle!=NULL)
	{
		printf("信号量创建成功 \r\n");
	}
	xReturn = xTaskCreate(KEYCreatTask,"KEYsend",56,NULL,7,&KEYsendCreatHandle);//二值信号量的释放任务
	xReturn = xTaskCreate(KEY_BeepCreatTask,"KEYreceive",56,NULL,3,&KEYreceiveCreatHandle);//二值信号量的获取任务
	Event_Handle = xEventGroupCreate();
	if(Event_Handle!=NULL)
	{
		printf("创建事件成功 \r\n");
	}
	xReturn = xTaskCreate(Event_Usart_CreatTask,"Event_UsartCreat",56,NULL,8,&Event_Usart_CreatHandle);//事件置位
	xReturn = xTaskCreate(Event_Usart_WaitTask,"Event_WaitCreat",56,NULL,4,&Event_Usart_WaitHandle);//事件等待
	
	//创建计数信号量
	semapphore_Handle =xSemaphoreCreateCounting(5,5);
	if(semapphore_Handle!=NULL)
	{
		printf("计数信号量创建成功\r\n");
	}
	//创建软件定时器
	Time_Handle =xTimerCreate("TIMER",(TickType_t)3000,pdTRUE,(void *)1,(TimerCallbackFunction_t)TimeTask);
	if(Time_Handle!=NULL)
	{
		printf("创建软件定时器成功\r\n");
		xTimerStart(Time_Handle,0);
	}
	vTaskDelete(AppTaskCreatHandle);
	
	taskEXIT_CRITICAL();//退出临界区
	
}
/*************
函数名称:LEDTask
函数功能:创建LED任务
函数参数:pvParame
函数返回值:无
*************/
void LEDTask(void * pvParame)
{
	while(1)
	{
		LED1(1);
		vTaskDelay(500);
	}
}
/*************
函数名称:BeepTask
函数功能:创建Beep任务
函数参数:pvParame
函数返回值:无
*************/
void BeepTask(void * pvParame)
{
	while(1)
	{
		beep_con(0);
		vTaskDelay(400);
	}
}
/*************
函数名称:USARTTask
函数功能:创建USART任务
函数参数:pvParame
函数返回值:无
*************/
void USARTTask(void * pvParame)
{
	char receive_usart;
	BaseType_t queue_receive_flag;
	while(1)
	{
		//memset(receive_usart,0,sizeof(usartch));
		queue_receive_flag = xQueueReceive(testtask,(void *)&receive_usart,portMAX_DELAY);
		if(queue_receive_flag==pdTRUE)
		{
			printf("消息队列传递的数据为:%c \r\n",receive_usart);
		}
		else
			printf("error \r\n");
		//memset(usartch,0,sizeof(usartch));
		vTaskDelay(10);
	}
		
}

/*************
函数名称:KEYCreatTask
函数功能:创建按键任务,当按键中断来了之后,释放信号量
函数参数:pvParame
函数返回值:无
*************/
void KEYCreatTask(void * pvParame)
{
	BaseType_t xReturn =pdPASS;
	while(1)
	{
		if(keyflag ==1)
		{
			xReturn=xSemaphoreGive(BinarySem_Handle);
			if(xReturn == pdTRUE)
			{
				printf("二值信号量释放成功 \r\n");
			}
//			else
//				printf("二值信号量释放失败\r\n");
		}
		vTaskDelay(20);
	}
}
/*************
函数名称:KEY_BeepCreatTask
函数功能:创建按键任务,当按键来了之后,获取信号量进行相应的操作
函数参数:pvParame
函数返回值:无
*************/
void KEY_BeepCreatTask(void * pvParame)
{
	BaseType_t xReturn = pdPASS;
	while(1)
	{
		xReturn = xSemaphoreTake(BinarySem_Handle,portMAX_DELAY);
		if(pdTRUE == xReturn)
		{
			printf("二值量获取成功 \r\n");
			beep_con(0);
			vTaskDelay(400);
			xReturn =pdPASS;
		}
		vTaskDelay(10);
	}
}

/*************
函数名称:Event_Usart_CreatTask
函数功能:创建事件组置位函数任务
函数参数:pvParame
函数返回值:无
*************/
#define Usart_Event1 (0x01<<0)
#define Usart_Event2 (0x01<<1)
TaskHandle_t Event_Usart_CreatHandle = NULL;
void Event_Usart_CreatTask(void * pvParame)
{
	while(1)
	{
		if(usartch=='1')
		{
			printf("事件1发生!!!\r\n");
			xEventGroupSetBits(Event_Handle,Usart_Event1);
		}
		if(usartch=='2')
		{
			printf("事件2发生!!!\r\n");
			xEventGroupSetBits(Event_Handle,Usart_Event2);
		}
		usartch=0;
		//memset(usartch,0,sizeof(usartch));
		vTaskDelay(20);
	}
}
/*************
函数名称:Event_Usart_WaitTask
函数功能:创建等待事件函数任务
函数参数:pvParame
函数返回值:无
*************/
TaskHandle_t Event_Usart_WaitHandle = NULL;
void Event_Usart_WaitTask(void * pvParame)
{
	EventBits_t x_Return;
	while(1)
	{
		x_Return = xEventGroupWaitBits(Event_Handle,Usart_Event1 | Usart_Event2,pdTRUE,pdTRUE,portMAX_DELAY);
		if((x_Return & (Usart_Event1|Usart_Event2))==((Usart_Event1|Usart_Event2)))
		{
			printf("事件1与事件2均发生!\r\n");
			LED2(0);
		}
		else
			printf("事件错误 \r\n");
		vTaskDelay(10);
	}
	
}

/*************
函数名称:TimeTask
函数功能:创建软件定时器回调函数
函数参数:pvParame
函数返回值:无
*************/
uint32_t Timer_Count=0;
uint32_t Systick_Count=0;
void TimeTask(void * pvParame)
{
	BaseType_t xReturn =pdTRUE;
	Timer_Count++;
	printf("回调函数执行 %d 次\r\n",Timer_Count);
	printf("滴答定时器数值为:%d\r\n",Systick_Count);
	Systick_Count =xTaskGetTickCount();//获取滴答定时器的计数值
	xReturn = xSemaphoreGive(semapphore_Handle);
	if(xReturn ==pdTRUE)
	{
		printf("软件定时器3s已到达,释放一个车位\r\n");
	}
	else
		printf("软件定时器3s已到达,但无车位可以释放\r\n");
	
}

 

你可能感兴趣的:(FreeRTOS,stm32)