FreeRTOS(一)

1、动态创建任务

freeRTOS自动分配内存。

void freertos_demo(void)
{
	创建:start_task;  优先级:1
	vTaskStartScheduler();
}

void start_task(void* pvParamters)
{
	创建:task1; 优先级:2; led亮灭;延时500毫秒;
	创建:task2; 优先级:3;led亮灭;延时500毫秒;
	创建:task3; 优先级:4; 延时10毫秒;
	vTaskDelete(NULL);
}

结果:先运行task1,再运行task2,再运行task3,一直运行task3…
分析任务的执行顺序:
创建完start_task任务后,开启了任务调度vTaskStartScheduler(),然后start_task就开始执行了,创建task1,task1的任务优先级为2,比start_task优先级高,会执行task1,阻塞500毫秒,释放自己的CPU使用权,给start_task执行,创建task2,task2任务优先级比start_yask高,运行task2,task2阻塞,回到start_task,创建任务3,优先级最高,抢占任务,延时10ms,阻塞了,然后回到vTaskDelete(NULL);

规律:创建完一个任务就开始调度。

若想要按照任务优先级高低来运行,就需要在进入start_task()时,关闭任务调度器。任务的切换是在中断中进行的,因此提供了临界区,用来关闭中断函数的。当任务创建完成后,才会开启任务调度器。

void freertos_demo(void)
{
	创建:start_task;  优先级:1
	vTaskStartScheduler();
}

void start_task(void* pvParamters)
{
	taskENTER_CRITICAL();
	创建:task1; 优先级:2; led亮灭;延时500毫秒;
	创建:task2; 优先级:3;led亮灭;延时500毫秒;
	创建:task3; 优先级:4; 延时10毫秒;
	vTaskDelete(NULL);
	taskEXIT_CRITICAL();
}

运行结果: task3,task2,task1,task3,task3…

规律:所以任务创建完再开始调度。

2、静态创建任务

手动分配内存。

(1)需要将宏configSUPPORT_STATIC_ALLOCATION配置为1。
找到FreeRTOSConfig.h找到宏定义将其改成1。
(2)实现2个函数。定义空闲任务与定时器任务的任务堆栈及TCB。
(3)实现2个接口函数;
定时器任务是可选的。
编译的时候,报错,只是定义了下面这2个函数,而没有实现。

空闲任务内存分配:

StaticTask_t idle_task_tcb;//空闲任务的任务控制块
StackType idle_task_stack[configMINIMAL_STACK_SIZE];//空闲任务的堆栈大小,定义一个数组
void vApplicationGetIdleTaskMemory(StaticTask_t ** ppxIdleTaskTCBBuffer, StackType_r ** ppxIdleTaskStackBuffer, unit32_t* pulIdleTaskStackSize)
{
	*ppxIdleTaskTCBBuffer = &idle_task_tcb;
	*ppxIdleTaskTCBBuffer = idle_task_stack;
	*pulIdleTaskStackSize = configMINIMAL_STACK_SIZE;
}

定时器任务内存分配:
找到任务调度器开启的函数vTaskStartScheduler();创建一个空闲任务,还会创建一个定时器任务xTimerCreateTimerTask();点进去,需要实现下面这个函数:

//软件定时器内存分配
StaticTask_t timer_task_tcb;//空闲任务的任务控制块
StackType timer_task_stack[configMINIMAL_STACK_SIZE];//空闲任务的堆栈大小,定义一个数组
void vApplicationGetTimerTaskMemory(StaticTask_t ** ppxTimerTaskTCBBuffer, StackType_t ** ppxTimerTaskStackBuffer,unit32_t * puiTimerTaskStackSize)
{
	*ppxTimerTaskTCBBuffer = &timer_task_tcb;
	*ppxTimerTaskTCBBuffer = timer_task_stack;
	*pulTimerTaskStackSize = configTIMER_STACK_SIZE;
}

(4)定义函数入口参数

void freertos_demo(void)
{
	xTaskCreateStatic();
	vTaskStartScheduler();
}

函数xTaskCreateStatic(),里面的参数前五个与动态的一样,后两个需要自己手动,

StackType_t start_task_stack[START_TASK_STACK_SIZE];
StaticTask_t start_task_tcb;
xTaskCreateStatic(.........(StackType_t*)start_task_stack,(StaticTask_t*) &start_task_tcb);
void freertos_demo(void)
{
	starttask_handler = xTaskCreateStatic(......);
	vTaskStartScheduler();
}
void start_task(void* pvParamters)
{
	taskENTER_CRITICAL();
	task1_handler=创建:task1; //每创建一个任务都要读取它的返回值
	task2_handler=创建:task2; 
	task3_handler=创建:task3; 
	vTaskDelete(NULL);
	taskEXIT_CRITICAL();
}

获取控制块内存(首地址);
获取堆栈内存(首地址);
标记使用的静态的方式申请的TCB和堆栈内存;
调用pvInitialiseNewTask初始化任务块,并将控制块信息返回给任务句柄,以便后续返回句柄信息;
调用prvAddBewTaskToReadyList添加新创建任务到就绪列表中;

3、任务挂起、恢复

挂起:暂停;
删除:无法恢复,堆栈内存空间都被释放掉了;

vTaskSuspend();//挂起任务
vTaskResume();//恢复被挂起的任务
xTaskResumeFromISR();//在中断中恢复被挂起的任务,有返回值

无论优先级如何,被挂起的任务都将不再被执行,直到任务被恢复;
挂起不支持嵌套,无论挂起多少次,只需要在任务中调用vTaskResume()即可使被恢复的任务进入就绪态。

任务恢复函数介绍:
返回值为pdTRUE:任务恢复后需要进行任务切换;要恢复任务的优先级大于当前执行任务的优先级;
返回值为pdFALSE:任务恢复后不需要进行任务切换;

注意:中断服务程序中要调用freeRTOS的API函数,则中断优先级不能高于FreeRTOS所管理的最高优先级(5-15),可在FreeTOSConfig.h中找到。

实验:4个任务:
start_task:用来创建其他的三个任务;
task1:实现LED0每500ms闪烁一次;
task2:实现LED1每500ms闪烁一次;
task3:判断按键按下逻辑,KEY0按下,挂起task1,按下KEY1在任务中恢复task1,按下KEY2,在中断中恢复task1(外部中断线实现);

实现:
使用动态创建任务,前面的任务创建,不需要修改,只需要修改task3。
注意是在任务中恢复的。

void task3(void * pvParameters)
{
	uint8_t key = 0;
	while(1)
	{
		//peintf("task3正在运行!\r\n");
		key = key_scan(0);
		if(key == KEY0_PRESS)
		{
			printf("挂起task1\r\n");
			vTaskSuspend(task1_handler);
		}else if(key == KEY1_PRES)
		{
			printf("在任务中恢复task1\r\n");
			vTaskResume(task1_handler);
		}
		vTaskDelay(10);
	}
}

下面代码是在中断中恢复的:
KEY2外部中断,中断优先级是(2,2)。此段代码可以参考freeRTOS官网API函数的使用方法中的例程。

extren TaskHandle_t task1_handler;
void HAL_GPIO_EXTI_Callback(unit16_t GPIO_Pin)
{
	delay_ms(20);
	switch(GPIO_Pin)
	{
		BaseType_t xYieldRequired;
		case KEY2_INT_GPIO_PIN:
			if(KEY2==0)
			{
				xYieldRequired = xTaskResumeFromISR(task1_handler);
				printf("在中断中恢复task1\r\n");
			}
			if(xYieldRequired == pdTRUE)
			{
				portYIELD_FROM_ISR(xYieldRequired );//任务切换
			}
			break;
		default:break;
	}
}

在串口打印中,当按下KEY2的时候会报错,port.c 807行。
在这里插入图片描述
将分组中的2修改为4,编译下载,再次打开串口,报错791

在这里插入图片描述
在这里插入图片描述
将优先级中的(2,2)修改为(5,0)。

4、中断

4.1、中断优先级分组设置

ARM Cortex-M 使用了8位宽的寄存器来配置中断的优先等级,这个寄存器就是中断优先级配置寄存器,2的8次方=256,最大能支持256个中断优先级。
在这里插入图片描述
抢占优先级相同的话,子优先级中,优先级高的、低的都不能打断。
中断优先级数值越小越优先。
在这里插入图片描述
freeRTOS为了方便管理,使用了Group4来分组,通过调用函数HAL_NVIC_SetPriorityGrouping(NVIC_PRIOPITYGROUP_4)来完成设置。
FreeRTOS官网关于中断说明:http://www.freertos.org/RTOS-Cortex-M3-M4.html
在这里插入图片描述

在这里插入图片描述
一个地址你是8位,4个组成32位的。
在这里插入图片描述
PendSV和SysTick设置为最低优先级,为了保证系统任务切换不会阻塞系统其他中断的响应。中断可以打断任务,任务不可以打断中断。PendSV处理任务切换,

在这里插入图片描述
在这里插入图片描述

4.2、实验

两个定时器,一个优先级为4,另一个为6,注意:系统所管理的优先级范围是:5~15,现象:两个定时器每1s,打印一段字符串,当关中断的时候,停止打印,开中断时持续打印。
任务1中测试:

void task1(void * pvParameters)
{
	uint8_t task1_num = 0;
	while(1)
	{
		if(++task1_num == 5)
		{
			task1_num = 0;
			printf("关中断!!\r\n");
			portDISABLE_INTERRUPTS();
			delay_ms(5000);
			printf("开中断!!\r\n");
			portENABLE_INTERRUPTS();
		}
		vTaskDelay(1000);
	}
}

关中断的时候,优先级为4的正在运行,6的不运行,开中断,4、6都在运行。
portDISABLE_INTERRUPTS();会把低于优先级为5的都给关闭,5秒之后,就开中断,
vTaskDelay(1000)也会调用portDISABLE_INTERRUPTS();只不过参数设置为0了。

5、临界段

临界段代码也叫临界区,是指,那些必须完整运行,不能被打断的代码段。

应用场景:
(1)外设:需要严格按照时序初始化的外设,IIC、spi,IIC中有个几微秒的延时,这个时候不能被打断。
(2)系统:系统自身需求。
(3)用户:用户需求。

什么可以打断当前程序的运行?中断、任务调度(PendSV:切换任务)。
关中断、将PendSV优先级设为最低。
在这里插入图片描述
比较强悍!!
因为:临界区是直接屏蔽了中断,系统任务调度靠中断,ISR也靠中断。

6、任务调度器的挂起

任务调度器就是执行任务切换的。任务切换就是PendSV
挂起任务调度器,调用此函数不需要关闭中断。

vTaskSuspendAll();
{
	......//内容
}
xTaskResumeAll();

内容就是:不被其他任务打断,但是中断可以打断。防止任务与任务之间的一个抢夺。
挂起调度器的方式,适用于临界区位于任务与任务之间,既不用去延时中断,又可以做到临界区的安全。

挂起任务调度器:vTaskSuspendAll(),调用一次挂起调度器,该变量uxSchedulerSuspended就加1,变量uxSchedulerSuspended的值不为0,将会导致Systick无法触发PendSV中断,即挂起调度器。

恢复任务调度器:
在这里插入图片描述

7、列表和列表项

在这里插入图片描述
比如:就绪列表,列表项就是各个任务。

在这里插入图片描述
列表项:列表中用于存放数据的地方,在list.h文件中,
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

列表初始化的时候,*pxPrevious指向xListEnd,*pxNext指向xListEnd。

7.2、列表相关API函数介绍(掌握)

vListInitialise();//初始化列表
vListInitialiseItem();//初始化列表项
vListInserEnd();//列表末尾插入列表项
vListInsert();//列表插入列表项
uxListRemove();//列表移除列表项

参考文档:《FreeRTOS开发指南》第七章–“FreeRTOS列表和列表项”。在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
列表项的插入:将列表项插入列表中,按照升序的方式。

函数vListInsertEnd();是将待插入的列表项插入到列表pxIndex指针指向的列表项前面;无序的插入方法!

7.3、列表项的插入和删除实验

设计三个任务:
start_task:用来创建其他的2个任务;
task1:LED0每500ms闪烁一次;
task2:调用列表和列表项相关API函数,并且通过串口输出相应的信息。

编写任务task2:
初始化列表,列表、列表项的函数在list.c和list.h中,定义的是结构体,因此需要定义一个结构体变量:

List_t 		TestList;//定义测试列表
ListItem_t 	ListItem1;//定义测试列表项1
ListItem_t 	ListItem2;
ListItem_t 	ListItem3;

在task2中初始化列表和列表项:
vListInitialiseItem(&ListItem1);这个函数里面是NULL,因此要给出一个初始值。
打印列表和列表项的地址。指向的都是地址,

void task2(void * pvParameters)
{
	vListInitialise(&TestList);//初始化列表
	vListInitialiseItem(&ListItem1);//初始化列表项
	vListInitialiseItem(&ListItem2);//初始化列表项
	vListInitialiseItem(&ListItem3);//初始化列表项
	ListItem1.xItemValue = 40;
	ListItem2.xItemValue = 60;
	ListItem2.xItemValue = 50;
	
	//打印列表和列表项的地址
	printf("TestList\t\t\t0x%p\t\r\n", &TestList);
	printf("TestList->pxIndex\t0x%p\t\r\n",TestList.pxIndex);//打印列表项所指向的地址,列表项的末尾插入与这个所指向的有关系
	printf("TestList->xListEnd\t0x%p\t\r\n",(&TestList.xListEnd));
	printf("ListItem1\t\t\t0x%p\t\r\n", &ListItem1);
	printf("ListItem2\t\t\t0x%p\t\r\n", &ListItem2);
	printf("ListItem3\t\t\t0x%p\t\r\n", &ListItem3);
	
	//插入列表项1
	vListInsert((List_t* 	)&TestList,)
				(ListItem_t*)&ListItem1);
	printf("TestList->xListEnd->pxNext\t0x%p\r\n", (TestList.xListEnd.pxNext));
	printf("ListItem1->pxNext\t0x%p\r\n", (ListItem1.pxNext));
	printf("TestList->xListEnd->pxPrevious\t0x%p\r\n", (TestList.xListEnd.pxPrevious));
	printf("ListItem1->pxPrevious\t0x%p\r\n", (ListItem1.pxPrevious));
	while(1)
	{
		vTaskDelay(1000);
	}
}

插入列表项1=40的结果是:
TestList->xListEnd->pxNext=ListItem1;
ListItem1->pxNext=TestList->xListEnd;列表项1的下一个就是末尾列表项
TestList->xListEnd->pxPrevious=ListItem1;末尾列表项的上一个,指向列表项1
ListItem1->pxPrevious=ListItem1;列表项的上一个就是默认列表项

就绪列表、阻塞列表、挂起列表…
当任务由就绪态转成为阻塞态时,就绪列表就删除了一个任务,这个任务插入阻塞列表中,因此就用到了列表的插入与移除。

8、任务调度

在这里插入图片描述
滴答定时器的重装载的值为180M/1000=180000,根据Cort-M内核手册中的SysTick控制及状态寄存器的说明,当180000减到0的时候,就会触发滴答定时器中断,滴答定时器的时钟源有2种,外部时钟源180M/8,内核时钟源180M,这里使用的是180M,因此计数一次花费的时间为1/180M,那么1/180*180000(计数次数)=1/1000s=1ms
滴答定时器的中断是1ms1次。

8.1、启动第一个任务

在这里插入图片描述

prvStartFirstTask();//开启第一个任务
vPortSVCHandler();//SVC中断服务函数

将任务A的寄存器值加载到CPU寄存器,CPU就会执行任务A。
创建任务A的时候,我们把任务A的寄存器值,放在任务堆栈里面!
此时CPU寄存器就会执行任务A。

1、当中断产生的时候,硬件自动将xPSR,PC(R15),LR(R14),R12,R3-R0保存和恢复;R4-R11需要手动保存和恢复;
出中断,从中断里面会直接恢复到CPU寄存器;
2、进入中断后,硬件会强制使用MSP指针,此时LR(R14)的值将会被自动更新为特殊的EXC_RETURN。
在这里插入图片描述

在这里插入图片描述

通过地址0xE000ED08找到向量表VTOR储存的地址,通过向量表地址,找到第一个元素,这个元素r0就是MSP的初始值。上电的时候默认MSP就是堆栈指针,经过入栈出栈,msp就已经不是当时的msp了,把r0重新赋值给msp,这样msp就回到了原点初始值。
下面使能全局中断,调用SVC启动第一个任务。SVC_Handler

在这里插入图片描述

出栈(恢复现场):将内存里面的值出栈到CPU。出栈的方向:低地址往高地址。
压栈:将CPU的东西保存到内存里。

8.2、任务切换

本质是:CPU寄存器的切换。
假设由任务A切换到任务B时,主要分2种:
(1)暂停任务A的执行,并将此时任务A的寄存器保存到任务堆栈,这个过程叫做保存现场。任务A的寄存器就是CPU的寄存器,因为此时任务A在运行。
(2)将任务B的各个寄存器值(被存放于任务堆栈中)恢复到CPU寄存器中,这个过程叫做恢复现场
对于任务A保存现场,任务B恢复现场,这个整个过程称之为:上下文切换
在这里插入图片描述
PendSV中断是如何触发的?
(1)滴答定时器中断调用;
(2)执行FreeRTOS提供的相关API函数:portYIELD();
本质:通过向中断控制器和状态寄存器ICSR(地址:0xE000_ED04)的bit28写入1挂起PendSV来启动PendSV中断。

查找最高优先级:

vTaskSwitchContext();//查找最高优先级
taskSELECT_HIGHEST_PRIORITY_TASK();//通过这个函数完成

在这里插入图片描述
获取最高优先级任务的任务控制块:

listGET_OWNER_OF_NEXT_ENTRY();

任务运行是在中断以外,使用PSP指针寄存器。无论是压栈还是出栈,用的都是PSP指针。
在这里插入图片描述

9、时间片调度

9.1、简介:

给出一个时间片,只给出一个时间片去运行Task1,这个时间片过了之后,就开始运行Task2,同样的,也给Task2时间片运行,Task2没有运行完,也会切换到下个时间片运行Task3,Task1/2/3是同等优先级的,只给一个时间片的时间去执行,不管有没有执行完,下次再回到这个任务的时候,就从上次打断的时间点继续运行下去。
假如运行到Task3,这个时候一个时间片还没有运行完,就到了阻塞时间,此时放弃CPU的使用权,这个时候就直接切换到Task1了,此时Task3执行的不到一个时间片的时间,下次再回到Task3怎么办?直接给一个时间片运行。

9.2、实验:

将设计三个任务:start_task、task1、task2,其中task1和task2优先级相同均为2。为了使现象明显,将滴答定时器的中断频率设置为50ms中断一次,即一个时间片50ms。
start_task:用来创建其他的2个任务;
task1:通过串口打印task1的运行次数;
task2:通过串口打印task2的运行次数;
使用时间片调度需把宏 configUSE_TIME_SLICING 和 configUSE_PREEMPTION 置1

在FreeRTOSConfig.h中,#define configSYSTICK_CLOCK_HZ 1000定义系统节拍是1000,是1ms,现在是50ms,所以1000/50=20,
滴答定时器是50ms中断一次,时间片是50ms一次。

void task1(void* pvParameters)
{
	uint32_t task_num = 0;
	while(1)
	{
		printf("task1运行次数:\r\n", ++task1_num);
		~~vTaskDelay(10);~~ 
		delay_ms(10);
	}
}

调用这个函数vTaskDelay(10);会引起任务调度的,会把当前运行的任务挂到阻塞列表,进入阻塞后,就立马进行下一个任务了,这样的话,就观察不到是50ms了,因此要用delay_ms(10)。一个时间片是50ms

为了防止乱码,可在打印的前后加上临界区:

taskENTER_CRITICAL();
taskEXIT_CRITICAL();
  • 9
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值