我的free-rtos学习笔记

1、数据类型

        每个移植版本都有的官方的portmacro.h文件里定义了两种数据类型。

TickType_t:

        freertos配置了一个周期性时钟中断,Tick interrupt,每发生一次Tick count就增加一次,这个Tick count类ickType_t;

BaseType_t:

        架构中最高效的数据类型,根据架构类型,是uint32_t或uint16_t;

2、空闲任务和钩子函数

        空闲任务优先级为0,执行清理工作,

        比如有任务“自杀时”,他就负责释放这个任务的内存。  如果是其他任务调用vtaskdelete删除,就由调用的函数释放内存

        空闲任务里还设置了钩子函数,可以让空闲任务在执行时,执行这个钩子函数,达到自己想要的效果。但是钩子函数不能阻塞或暂停,否则空闲任务就无法被唤醒。

3、调度策略

        是否允许抢占

        若允许抢占,是否允许时间片轮转

        允许抢占和时间片轮转,空闲任务是否让步

4、队列的使用

       1:特点

        先进先出 ,队列本质是环形缓冲区和两个链表

        2:相关内部结构

       队列有很多指针,头指针指向队列头部,read指针指向上次读取到的位置,write指针指向上次写的位置;

        读队列时,readfrom指针会先++(就指向了当前位置),然后再读。写队列时,先写,写完之后把指针指向下一个位置。

      当队列写满时,不会覆盖当前元素(但是队列长度为1时,可以使用xQueueOverwrite() 或 xQueueOverwriteFromISR()来“覆盖”,所以不会被阻塞。同理,也可“偷看”)

        3:创建队列

xQueueHandle1 =xQueueCreate(2,sizeof(int));//创建队列,参数为元素个数,和元素的size
BaseType_t xQueueReset( QueueHandle_t pxQueue);//队列恢复为初始状态

int i;
xQueueSend(xQueueHandle1,&i,portMAX_DELAY);//发送数据,第三个参数为等待时间,portMAX_DELAY表示一直阻塞

xQueueSet1=xQueueCreateSet(2);//队列集,参数为队列的总长度
xQueueAddToSet(xQueueHandle1,xQueueSet1);//添加队列进队列集

xQueueSetMemberHandle hadle;
hadle=xQueueSelectFromSet(xQueueSet1,portMAX_DELAY);//当队列集中队列发送数据时,会被唤醒,并返回队列的hadle

int re;
xQueueReceive(hadle,&re,0);//接收数据

                 队列集。........

    4.5、信号量

               底层基于队列实现

        1:既然有队列,那为什么还要用信号量

                队列可以传输数据,所以涉及拷贝复制操作; 

                队列传输数据需要消耗内存,涉及了传输数据和读取数据操作;对于数据很大的时候,它的拷贝复制操作都很耗时。

        2:基础操作

                

SemaphoreHandle_t semHadle;
int sum=0;

void Task4(void *parme)
{
	int i;
	while(1)
	{
		for(i=0;i<100000;i++)
		{sum++;}
		xSemaphoreGive(semHadle);//信号量资源+1
		taskYIELD();//主动退出一次调度
	}
}

void Task5(void *parme)
{
	while(1)
	{	
		xSemaphoreTake(semHadle, portMAX_DELAY);//取出信号量,信号量资源-1
		printf("%d\r\n",sum);
	}
}

void main()
{
    //semHadle=SemaphoreCreateBinary(10,0);//技术型信号量,10代表最大资源计数,0代表初始资源数,返回值为一个hadle,不为null时创建成功
	
	semHadle=xSemaphoreCreateBinary();//二进制信号量
	if(semHadle==NULL)
	{
		printf("create semaphore fail!\r\n");
	}
	
	xTaskCreate(Task4,"Task4",100,NULL,1,NULL);//创建任务
	xTaskCreate(Task5,"Task5",100,NULL,1,NULL);
	
	//idletask3=xTaskCreateStatic(vTask3,"Task3",100,NULL,1,xTask3Stack,&xTask3TCB);
	
	vTaskStartScheduler();
}

5、互斥量

        1:与二元信号量的对比

                     可以解决优先级反转的问题:比如有三个优先级A<B<C的任务,A拿到锁之后,到C执行,因为C拿不到锁,就会阻塞,然后就到B执行了,B优先级低反而先执行,这个现象就叫优先级反转。而互斥量可以把保证,C拿不到锁,他就会把自己的优先级继承给A,A就会比B先执行,这个就叫优先级继承。

                      可以递归继承锁:比如这个程序拿到锁之后,这个程序内部的代码还要获取这个锁,这时候就会阻塞。递归继承锁就可以让它再次获取锁而不阻塞。但是要保证释放锁,因为lock和unlock是成对出现的。这个使用递归锁实现

                        二元信号量初始值为0,互斥量为1;其他的give、take操作都一样

        2:基本使用

SemaphoreHandle_t semHadle;
int sum=0;

void Task4(void *parme)
{
	int i;
	while(1)
	{
		xSemaphoreTake(semHadle, portMAX_DELAY);
		for(i=0;i<100000;i++)
		{
            sum++;
        }
		xSemaphoreGive(semHadle);
		taskYIELD();
	}
}

void Task5(void *parme)
{
	while(1)
	{	
		xSemaphoreTake(semHadle, portMAX_DELAY);//获取
		printf("%d\r\n",sum);
		xSemaphoreGive(semHadle);//释放
		taskYIELD();
	}
}

void main()
{
    semHadle=xSemaphoreCreateMutex();//互斥量
	
	if(semHadle==NULL)
	{
		printf("create semaphore fail!\r\n");
	}
	
	xTaskCreate(Task4,"Task3",100,NULL,1,NULL);
	xTaskCreate(Task5,"Task3",100,NULL,1,NULL);

	vTaskStartScheduler();
}

        3、互斥量缺陷

                并不是谁上锁,谁就解锁,其他任务也可以解锁,这时候程序就会出现bug;而递归锁是谁上锁,谁解锁,可以用递归锁来实现这种效果。

SemaphoreHandle_t semHadle;
int sum=0;

void Task4(void *parme)
{
	int i;
	while(1)
	{
		xSemaphoreTakeRecursive(semHadle, portMAX_DELAY);
		for(i=0;i<100000;i++)
		{sum++;}
		xSemaphoreGiveRecursive(semHadle);
		taskYIELD();
	}
}

void Task5(void *parme)
{
	//int i=0;
	while(1)
	{	
		xSemaphoreTakeRecursive(semHadle, portMAX_DELAY);
		printf("%d\r\n",sum);
		xSemaphoreGiveRecursive(semHadle);
		taskYIELD();
	}
}


void main()
{
    semHadle=xSemaphoreCreateRecursiveMutex();//互斥量
	
	if(semHadle==NULL)
	{
		printf("create semaphore fail!\r\n");
	}
	
	xTaskCreate(Task4,"Task3",100,NULL,1,NULL);
	xTaskCreate(Task5,"Task3",100,NULL,1,NULL);
	
	vTaskStartScheduler();



}

 6、事件组

        1、概念

                    事件组等待时,要么等待事件组中的某个事件,要么等待所有事件。    

                    事件组用一个整数来表示,其中的高8位留给内核使用,只能用其他的位来表示事件。

              (  如果configUSE_16_BIT_TICKS是1,那么这个整数就是16位的,低8位用来表示事件
                如果configUSE_16_BIT_TICKS是0,那么这个整数就是32位的,低24位用来表示事件
                configUSE_16_BIT_TICKS是用来表示Tick Count的,怎么会影响事件组?这只是基于效率来考虑
                如果configUSE_16_BIT_TICKS是1,就表示该处理器使用16位更高效,所以事件组也使用16位
                如果configUSE_16_BIT_TICKS是0,就表示该处理器使用32位更高效,所以事件组也使用32位)  

        2、基础使用(等待事件和同步点)

SemaphoreHandle_t semHadle;
EventGroupHandle_t	eventHadle;//事件组handle
int sum=0;

void Task4(void *parme)
{
	int i;
	while(1)
	{
		xSemaphoreTakeRecursive(semHadle, portMAX_DELAY);
		for(i=0;i<100000;i++)
		{sum++;}
		xSemaphoreGiveRecursive(semHadle);
        xEventGroupSetBits(eventHadle, 0);	//设置事件已完成
		taskYIELD();
	}
}

void Task5(void *parme)
{
	while(1)
	{	
		xSemaphoreTakeRecursive(semHadle, portMAX_DELAY);
		printf("%d\r\n",sum);
		xSemaphoreGiveRecursive(semHadle);
        xEventGroupSetBits(eventHadle, 1);	//设置事件已完成
		taskYIELD();
	}
}

void Task6(void *parme)
{
	while(1)
	{
		xEventGroupWaitBits(eventHadle,(1|0),pdTRUE,pdTRUE,portMAX_DELAY);//等待事件完成
		printf("task 4 and task5 end toghter\r\n");
	}
}

void main()
{
    semHadle=xSemaphoreCreateRecursiveMutex();//互斥量
	eventHadle = xEventGroupCreate();//事件组
	if(semHadle==NULL)
	{
		printf("create semaphore fail!\r\n");
	}	
	xTaskCreate(Task4,"Task3",100,NULL,1,NULL);
	xTaskCreate(Task5,"Task3",100,NULL,1,NULL);
	xTaskCreate(Task6,"Task6",100,NULL,1,NULL);
	vTaskStartScheduler();
}

3、相关函数    

xEventGroupSetBits(eventHadle, 0);    //设置某一个事件已完成

xEventGroupWaitBits(eventHadle,(1|0),pdTRUE,pdTRUE,portMAX_DELAY);//等待事件完成

        /*前一个pdTRUE的位置表示事件返回时是否把所有事件置零,后一个表示是否等待所有事件,还是其中一个事件。*/

        同步点:可以同步多个任务

xEventGroupSync(EventGroupHandle_t xEventGroup, const EventBits_t uxBitsToSet, const EventBits_t uxBitsToWaitFor,portMAX_DELAY)

/*
    const EventBits_t uxBitsToSet   设置完成的事件是哪一位
    const EventBits_t uxBitsToWaitFor 等待哪些位
    portMAX_DELAY                    等待事件。这里这个表示不成功就阻塞

*/

7、任务通知

        1:特点

                多对一的关系,通知的任务不会阻塞(要么成功,要么失败,但是都不等待),被通知的任务没数据可以等待,有数据就返回

                传递数据,发送事件时更快

                更节省内存,因为不需要创建额外的结构体。

                不能在中断中使用,因为中断不属于任务,内部没有TCB结构体

                接收方只有一个任务

                 不能缓冲多个数据,只能保存一个,因为TCB结构体里只有一个value值和statu状态。

        2:相关函数

        3:实现轻量级信号量

xTaskHandle taskHandle1; 
xTaskHandle taskHandle2;

void Task1(void *parme)
{
	int i;
	int count=0;
	while(1)
	{		
		for(i=0;i<10;i++)
		{
            xTaskNotifyGive(taskHandle2);//向哪个任务发送通知,让其val++
        }
		printf("task1=%d\r\n",count++);
		vTaskDelay(1);
		taskYIELD();//主动退出调度
	}
}


void Task2(void *parme)
{
	int i=0;
	int val;
	while(1)
	{	
		i++;
	//接收通知,参数一表示函数退出时是否将自身value清零;参数二表示未收到通知,等待多久
    //pdFALSE:如果通知值大于0,则把通知值减1
		val= ulTaskNotifyTake(pdTRUE, portMAX_DELAY);

		printf("val= %d,i=%d\r\n",val,i);
		vTaskDelay(1);
	}
}

void main()
{
    xTaskCreate(Task1,"Task1",100,NULL,1,&taskHandle1);
	xTaskCreate(Task2,"Task2",100,NULL,1,&taskHandle2);			
	vTaskStartScheduler();
}

        4:实现轻量级队列

xTaskHandle taskHandle1; 
xTaskHandle taskHandle2;

void Task1(void *parme)
{
	int i;
	while(1)
	{		
		for(i=0;i<10;i++)
		{
			xTaskNotify(taskHandle2,i, eSetValueWithOverwrite);//如果有数据未读,不覆盖
		}
		taskYIELD();
		//vTaskDelete(NULL);
	}
}

void Task2(void *parme)
{
	int i=0;
	int val;
	while(1)
	{	
		i++;	
		xTaskNotifyWait(0, 0,&val, portMAX_DELAY);//接收
		printf("val= %d,i=%d\r\n",val,i);
	}
}

int main( void )
{
	prvSetupHardware();//硬件设置

	xTaskCreate(Task1,"Task1",100,NULL,1,&taskHandle1);
	xTaskCreate(Task2,"Task2",100,NULL,1,&taskHandle2);		

	vTaskStartScheduler();
	return 0;
}

        5:实现轻量级事件组

                注意,和事件组是有区别的:事件组可以选择等待某一个事件或者所有事件,而任务通知只要有bit位被设置,都会被唤醒

xTaskHandle taskHandle1; 
xTaskHandle taskHandle2;
xTaskHandle taskHandle3;

void Task1(void *parme)
{
	int i;
	while(1)
	{		
		vTaskDelay(20);
		xTaskNotify(taskHandle3,(1<<0), eSetBits);//设置bit位		
		//taskYIELD();
		vTaskDelete(NULL);
	}
}

void Task2(void *parme)
{
	while(1)
	{	
		vTaskDelay(30);
		xTaskNotify(taskHandle3,(1<<1), eSetBits);//设置bit位		
		//taskYIELD();
		vTaskDelete(NULL);
	}
}

void Task3(void *parme)
{
	int bit;
	while(1)
	{	
		xTaskNotifyWait(0, 0,&bit, portMAX_DELAY);//接收
		if((bit&0x03)==0x03)//如果只设置了一次
		{
			printf("get toghter bit=%d\r\n",bit);
		}
		else
		{
			printf("get only one bit=%d\r\n",bit);
		}		
	}
}

void main()
{
    xTaskCreate(Task1,"Task1",100,NULL,1,&taskHandle1);
	xTaskCreate(Task2,"Task2",100,NULL,1,&taskHandle2);		
	xTaskCreate(Task3,"Task3",100,NULL,1,&taskHandle3);
	
	vTaskStartScheduler();

}

8、定时器

        1:概念

                每隔一定的tick时间就触发。分为周期性定时器和一次性定时器,两者都只有两种状态,冬眠态或者running态。当timercreate时,进入dormant态,当到达指定tick,或有任务要修改定时器相关设置,会进入running态。(注:周期性定时器,进入running后,一直保持此状态,直到调用xTimerstop()才变为dormant)。

                如果使用,需要配置等待时间、队列长度和守护任务的栈大小等参数

                要实现回调函数,会创建一个守护任务来执行定时器触发时的任务。

                其结构体中队列的作用是:启动或修改定时器时,会把命令放入定时器队列中,由守护任务读取这个队列,然后执行相关操作,因为队列可能满,所以会有等待时间。

            2:基础使用

xTaskHandle taskHandle1; 
xTimerHandle xMyTimerHadle;//定时器的hadle
static int cut;
//定时器执行的回调函数
void vMyTimerCallbackFunction( TimerHandle_t xTimer )
{
	printf("callback function running....%d times\r\n",cut++);
}

void Task1(void *parme)
{
	xTimerStart(xMyTimerHadle, portMAX_DELAY);//启动定时器
	while(1)
	{		
		printf("task1 running...\r\n");
	}
}
void main(void)
{
    prvSetupHardware();//硬件设置

	xMyTimerHadle=xTimerCreate("myTimer",100,pdFALSE,NULL,vMyTimerCallbackFunction);//创建定时器
		
	xTaskCreate(Task1,"Task1",100,NULL,1,&taskHandle1);
	
	vTaskStartScheduler();
}

9、中断

        1:概念

                RTOS里,需应对各种事件,比如用户按下按键,这些事件大多通过硬件中断产生。

                中断处理流程:CPU跳到固定地址执行代码,这个固定地址叫中断向量,以下是执行代码做的事:                        

                1、保护现场,比如任务1被打断,保护它的运行环境,比如寄存器的值。

                 2、调用中断处理函数(ISR)

                 3、恢复运行任务1,或优先级更高的任务

        2:注意点

                RTOS中,任务优先级与硬件无关,由程序员决定,而中断看作硬件特性的一部分,它的执行由硬件决定

                ISR在内核空间调用,调用时用户任务无法执行,因为即使优先级最低的ISR都比任务的优先级高

                中断函数不能被阻塞,它的执行要尽量快,如果非常耗时的话,可以把它放到任务中去执行,所以ISR涉及程序间的通信.

        3:函数区别

                中断处理不能用普通的函数,需要用后缀带FromISR的版本。

                他们功能差不多,为什么要用呢?有什么好处呢?他们有什么区别吗?

                因为ISR不能阻塞,所以他们参数不一样。如果在一般函数,需判断此程序是是否在进行中断处理,就需要在程序中添加大量分支,这会造成大量的代码冗余,有些架构很难分清任务中还是ISR中,就需要添加更多的代码。

                其次,移植FreeRTOS时,还需要提供监测上下文的函数,比如is_in_isr()。

                也因此,任务中或ISR中都可以用FromISR函数,而反过来不行。毕竟任务也可以不等待阻塞。

10、资源管理

        我们在使用临界资源的时候,要让程序互斥执行,就需要进行资源管理,有两种情况

        1、两个任务要用到临界资源,任务在运行时,停止任务调度

        2、任务和中断用到临界资源,任务在运行时,屏蔽中断(中断不会被任务打断,所以不需要屏蔽中断)

           

        一般架构中,会有个4位寄存器0~255,保存中断优先级,最低位的中断不可被屏蔽;一般191~255(根据硬件情况会有不同)的中断会用到sysctl,我们在屏蔽中断的时候,就可以按照这一类来屏蔽,就避免了需要屏蔽很多个中断的情况。

        在任务中,是屏蔽后重新使能中断,而中断中是恢复中断,为什么是恢复呢?因为屏蔽中断之前可能有两种情况,中断是禁止的或者开启的,调用后恢复之前的状态。

11、总结

        1、任务创建时,先创建个TCB结构体,然后把提供的函数和栈大小传给它。

        

        

                

                

        

        

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值