freertos

Freertos

参考韦东山freertos

linux与rtos有什么区别

线程安全

线程安全是多线程编程时的计算机程序代码中的一个概念。在拥有共享数据的多条线程并行执行的程序中,线程安全的代码会通过同步机制保证各个线程都可以正常且正确的执行,不会出现数据污染等意外情况。

原子操作

原子操作(atomic operation)是不需要synchronized”,这是多线程编程的老生常谈了。所谓原子操作是指不会被线程调度机制打断的操作;这种操作一旦开始,就一直运行到结束,中间不会有任何 context switch [1] (切换到另一个线程)。

线程调度

计算机通常只有一个CPU,在任意时刻只能执行一条机器指令,每个线程只有获得CPU的使用权才能执行指令。所谓多线程的并发运行,其实是指从宏观上看,各个线程轮流获得CPU的使用权,分别执行各自的任务。在运行池中,会有多个处于就绪状态的线程在等待CPU,JAVA虚拟机的一项任务就是负责线程的调度,线程调度是指按照特定机制为多个线程分配CPU的使用权。

FreeRtos内存管理

Heap_1

只实现了pvPortMalloc,没有实现vPortFree。实现原理就是开辟了一个大数组,调用pvPortMalloc就从这个大数组里面分配空间

freertos_第1张图片

Heap_2

Heap_2也是在数组上分配内存,跟Heap_1不一样的地方在于:

  • Heap_2使用最佳匹配算法(best fit)来分配内存
  • 它支持vPortFree

最佳匹配算法:

  • 假设heap有3块空闲内存:5字节、25字节、100字节
  • pvPortMalloc想申请20字节
  • 找出最小的、能满足pvPortMalloc的内存:25字节
  • 把它划分为20字节、5字节
    • 返回这20字节的地址
    • 剩下的5字节仍然是空闲状态,留给后续的pvPortMalloc使用

与Heap_4相比,Heap_2不会合并相邻的空闲内存,所以Heap_2会导致严重的"碎片化"问题。

但是,如果申请、分配内存时大小总是相同的,这类场景下Heap_2没有碎片化的问题。所以它适合这种场景:频繁地创建、删除任务,但是任务的栈大小都是相同的(创建任务时,需要分配TCB和栈,TCB总是一样的)。

虽然不再推荐使用heap_2,但是它的效率还是远高于malloc、free。

freertos_第2张图片

若删除了b任务,创建的d任务小于b任务则会产生内存碎片

Heap_3

Heap_3使用标准C库里的malloc、free函数,所以堆大小由链接器的配置决定,配置项configTOTAL_HEAP_SIZE(FreeRtos使用的堆大小)不再起作用。

C库里的malloc、free函数并非线程安全的,Heap_3中先暂停FreeRTOS的调度器,再去调用这些函数,使用这种方法实现了线程安全。

Heap_4(较为常用)

Heap_4使用首次适应算法(first fit)来分配内存。它还会把相邻的空闲内存合并为一个更大的空闲内存,这有助于较少内存的碎片问题。

首次适应算法:

  • 假设堆中有3块空闲内存:5字节、200字节、100字节
  • pvPortMalloc想申请20字节
  • 找出第1个能满足pvPortMalloc的内存:200字节
  • 把它划分为20字节、180字节
    • 返回这20字节的地址
    • 剩下的180字节仍然是空闲状态,留给后续的pvPortMalloc使用

Heap_4会把相邻空闲内存合并为一个大的空闲内存,可以较少内存的碎片化问题。适用于这种场景:频繁地分配、释放不同大小的内存。

freertos_第3张图片

Heap_5

Heap_5分配内存、释放内存的算法跟Heap_4是一样的。

相比于Heap_4,Heap_5并不局限于管理一个大数组:它可以管理多块、分隔开的内存。

在嵌入式系统中,内存的地址可能并不连续,这种场景下可以使用Heap_5。

既然内存是分隔开的,那么就需要进行初始化:确定这些内存块在哪、多大:

  • 在使用pvPortMalloc之前,必须先指定内存块的信息
  • 使用vPortDefineHeapRegions来指定这些信息

怎么指定一块内存?使用如下结构体:

typedef struct HeapRegion
{
    uint8_t * pucStartAddress; // 起始地址
    size_t xSizeInBytes;       // 大小
} HeapRegion_t;

怎么指定多块内存?使用一个HeapRegion_t数组,在这个数组中,低地址在前、高地址在后。

比如:

HeapRegion_t xHeapRegions[] =
{
  { ( uint8_t * ) 0x80000000UL, 0x10000 }, // 起始地址0x80000000,大小0x10000
  { ( uint8_t * ) 0x90000000UL, 0xa0000 }, // 起始地址0x90000000,大小0xa0000
  { NULL, 0 } // 表示数组结束
 };

vPortDefineHeapRegions函数原型如下:

void vPortDefineHeapRegions( const HeapRegion_t * const pxHeapRegions );

把xHeapRegions数组传给vPortDefineHeapRegions函数,即可初始化Heap_5。

Heap相关的函数

void * pvPortMalloc( size_t xWantedSize );	// 分配内存,如果分配内存不成功,则返回值为NULL。这个里面有个钩子函数,申请失败会输出log信息,并进入断言
void vPortFree( void * pv );	// 释放内存
/*
    作用:分配内存、释放内存。

    如果分配内存不成功,则返回值为NULL。
*/
size_t xPortGetFreeHeapSize( void );//输出当前还剩余多少内存
/*
当前还有多少空闲内存,这函数可以用来优化内存的使用情况。比如当所有内核对象都分配好后,执行此函数返回2000,那么configTOTAL_HEAP_SIZE就可减小2000。
注意:在heap_3中无法使用。
*/

size_t xPortGetMinimumEverFreeHeapSize( void );

//返回:程序运行过程中,空闲内存容量的最小值。程序运行时剩余的最小的堆内存

//注意:只有heap_4、heap_5支持此函数。

malloc失败的钩子函数

void * pvPortMalloc( size_t xWantedSize )
{
    ......
    #if ( configUSE_MALLOC_FAILED_HOOK == 1 )
        {
            if( pvReturn == NULL )
            {
                extern void vApplicationMallocFailedHook( void );
                vApplicationMallocFailedHook();
            }
        }
    #endif
    
    return pvReturn;        
}

#define configCHECK_FOR_STACK_OVERFLOW                1
#define configUSE_MALLOC_FAILED_HOOK                  1
 
 
void vApplicationStackOverflowHook( TaskHandle_t xTask, char *pcTaskName )
{
    LOG_ERR(GTK_DBG_APP_SYS, "stack overflow:%s", pcTaskName);
    ASSERT(0);
}
 
 
void vApplicationMallocFailedHook( void )
{
    LOG_ERR(GTK_DBG_APP_SYS, "malloc error");
    ASSERT(0);
}

任务管理

freertos_第4张图片

freertos_第5张图片

创建任务

freertos_第6张图片

BaseType_t xTaskCreate( TaskFunction_t pxTaskCode, // 函数指针, 任务函数
                        const char * const pcName, // 任务的名字
                        const configSTACK_DEPTH_TYPE usStackDepth, // 栈大小,单位为word,10表示40字节
                        void * const pvParameters, // 调用任务函数时传入的参数
                        UBaseType_t uxPriority,    // 优先级
                        TaskHandle_t * const pxCreatedTask ); // 任务句柄, 以后使用它来操作这个任务

freertos_第7张图片

#define mainDELAY_LOOP_COUNT (0xffffff)

void vTask1( void *pvParameters )
{
	const char *pcTaskName = "T1 run\r\n";
	volatile uint32_t ul; /* volatile用来避免被优化掉 */
	
	/* 任务函数的主体一般都是无限循环 */
	for( ;; )
	{
		/* 打印任务1的信息 */
		printf( "%s\r\n",pcTaskName );
		
		/* 延迟一会(比较简单粗暴) */
		for( ul = 0; ul < mainDELAY_LOOP_COUNT; ul++ )
		{
		}
	}
}

void vTask2( void *pvParameters )
{
	const char *pcTaskName = "T2 run\r\n";
	volatile uint32_t ul; /* volatile用来避免被优化掉 */
	
	/* 任务函数的主体一般都是无限循环 */
	for( ;; )
	{
		/* 打印任务1的信息 */
		printf( "%s\r\n",pcTaskName );
		
		/* 延迟一会(比较简单粗暴) */
		for( ul = 0; ul < mainDELAY_LOOP_COUNT; ul++ )
		{
		}
	}
}

int main(void)
{
    app_periph_init();             /*
	printf("aaaaaaaaaaaaaaaaaaaaaaaaaaa\r\n");
    xTaskCreate(vTask1, "Task 1", 1000, NULL, 1, NULL);
	xTaskCreate(vTask2, "Task 2", 1000, NULL, 1, NULL);

    
    vTaskStartScheduler(); /*< freertos run all tasks*/
    for(;;)
        ; /*< Never perform here */
}

freertos_第8张图片

任务的删除

void vTaskDelete( TaskHandle_t xTaskToDelete );
//pvTaskCode	任务句柄,使用xTaskCreate创建任务时可以得到一个句柄。也可传入NULL,这表示删除自己。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lYNriH3i-1662542959861)(C:\Users\John\AppData\Roaming\Typora\typora-user-images\image-20220906162145494.png)]

void vTask3( void *pvParameters )
{
	printf("Task3 is running and about to delete itself\r\n");

	// 可以直接传入参数NULL, 这里只是为了演示函数用法
	vTaskDelete(NULL);// == vTaskDelete(NULL);
}

void vTask2( void *pvParameters )
{
	printf("Task2 is running and about to delete itself\r\n");
	BaseType_t ret;
	const TickType_t xDelay100ms = pdMS_TO_TICKS( 100UL );		
	ret = xTaskCreate(vTask3, "Task 3", 1000, NULL, 3, NULL);
	if(ret != pdPASS ){
		printf("Create Task2 Failed\r\n");
	}
	vTaskDelay( xDelay100ms );
	// 可以直接传入参数NULL, 这里只是为了演示函数用法
	vTaskDelete(TASK_2_Handle);// == vTaskDelete(NULL);
}

void vTask1( void *pvParameters )
{
    static char *pcTaskName = "vTask1";
	const TickType_t xDelay100ms = pdMS_TO_TICKS( 100UL );		
	BaseType_t ret;
	
	/* 任务函数的主体一般都是无限循环 */
	for( ;; )
	{
		/* 打印任务1的信息 */
		printf( "%s\r\n",pcTaskName );
        ret = xTaskCreate(vTask2, "Task 2", 1000, NULL, 2, &TASK_2_Handle);
		if(ret != pdPASS ){
            printf("Create Task2 Failed\r\n");
        }

        vTaskDelay( xDelay100ms );

	}
}


int main(void)
{
    app_periph_init();             /*
	printf("aaaaaaaaaaaaaaaaaaaaaaaaaaa\r\n");
    xTaskCreate(vTask1, "Task 1", 1000, NULL, 1, &TASK_1_Handle);

//    hw_set_vibrator_enable(true);  // 开机立即震动,提示用户
    // ble_stack_debug_setup(0x7FFFFFFF, 0x7FFFFFFF, vprintf);//协议栈日志
//    ble_stack_init(&s_app_ble_callback, &heaps_table); /*< init ble stack*/
//    xTaskCreate(maintask, "MAIN", configMINIMAL_STACK_SIZE * 4, NULL, configMAX_PRIORITIES - 1, &MainHandle);
    vTaskStartScheduler(); /*< freertos run all tasks*/
    for(;;)
        ; /*< Never perform here */
}

freertos_第9张图片

任务优先级和Tick

freertos_第10张图片

freertos_第11张图片

freertos_第12张图片

vTaskDelay(2);  // 等待2个Tick,假设configTICK_RATE_HZ=100, Tick周期时10ms, 等待20ms,一个tick是10ms

// 还可以使用pdMS_TO_TICKS宏把ms转换为tick
vTaskDelay(pdMS_TO_TICKS(100));	 // 等待100ms

//pdMS_TO_TICKS() 这个好用要记住

优先级实验

void vTask3( void *pvParameters )
{
    const TickType_t xDelay300ms = pdMS_TO_TICKS( 300UL );
    for(;;) {
        printf("vTask3 -----\r\n");
        vTaskDelay( xDelay300ms );//这个延时一定得有的,不然放不来其他任务都进不来了,优先级最高
    }
}

void vTask2( void *pvParameters )
{
    for(;;) {
        printf("vTask2 +++++\r\n");
		vTaskDelay(pdMS_TO_TICKS(100));//这个延时一定得有的,不然放不来其他任务1都进不来了TAsK1和TASK2优先级相同的
    }
}

void vTask1( void *pvParameters )
{
    static char *pcTaskName = "vTask1";
			
	BaseType_t ret;
	
	/* 任务函数的主体一般都是无限循环 */
	for( ;; )
	{
        printf("vTask1 =======\r\n");
		vTaskDelay(pdMS_TO_TICKS(100));//这个延时一定得有的,不然放不来其他任务2都进不来了
	}
}



static const char *pcTextForTask1 = "T1 run--\r\n";
static const char *pcTextForTask2 = "T2 run++\r\n";

int main(void)
{
    app_periph_init();             /*
	printf("aaaaaaaaaaaaaaaaaaaaaaaaaaa\r\n");
    xTaskCreate(vTask1, "Task 1", 1000, NULL, 1, NULL);
    xTaskCreate(vTask2, "Task 2", 1000, NULL, 1, NULL);
    xTaskCreate(vTask3, "Task 3", 1000, NULL, 3, NULL);

//    hw_set_vibrator_enable(true);  // 开机立即震动,提示用户
    // ble_stack_debug_setup(0x7FFFFFFF, 0x7FFFFFFF, vprintf);//协议栈日志
//    ble_stack_init(&s_app_ble_callback, &heaps_table); /*< init ble stack*/
//    xTaskCreate(maintask, "MAIN", configMINIMAL_STACK_SIZE * 4, NULL, configMAX_PRIORITIES - 1, &MainHandle);
    vTaskStartScheduler(); /*< freertos run all tasks*/
    for(;;)
        ; /*< Never perform here */
}

freertos_第13张图片

修改优先级

UBaseType_t uxTaskPriorityGet( const TaskHandle_t xTask );
//传参为任务句柄 NULL为查看自己的优先级

void vTaskPrioritySet( TaskHandle_t xTask,UBaseType_t uxNewPriority );
/*
	参数1 任务句柄
	参数2 任务优先级
	使用参数xTask来指定任务,设置为NULL表示设置自己的优先级; 参数uxNewPriority表示新的优先级,取值范围是0~(configMAX_PRIORITIES – 1)。
*/

static TaskHandle_t TASK_1_Handle;
static TaskHandle_t TASK_2_Handle;

void vTask3( void *pvParameters )
{
    const TickType_t xDelay300ms = pdMS_TO_TICKS( 300UL );
    for(;;) {
        printf("vTask3 -----\r\n");
        vTaskDelay( xDelay300ms );
    }
}

void vTask2( void *pvParameters )
{
    UBaseType_t BaseType;

	BaseType =  uxTaskPriorityGet( NULL );//获取自己的优先级
    for(;;) {
        printf("vTask2 +++++\r\n");
        vTaskPrioritySet(NULL,BaseType-2);//传入NULL设置自身的优先级,降低自身优先级2,使得任务1得以运行
		
    }
}

void vTask1( void *pvParameters )
{
    static char *pcTaskName = "vTask1";
			
	BaseType_t ret;
    UBaseType_t BaseType;

	BaseType =  uxTaskPriorityGet( NULL );//获取自己的优先级
	/* 任务函数的主体一般都是无限循环 */
	for( ;; )
	{
        printf("vTask1 =======\r\n");
        vTaskPrioritySet(TASK_2_Handle,BaseType+1);//拉高任务2优先级,逼任务一高1
        
	}
}



static const char *pcTextForTask1 = "T1 run--\r\n";
static const char *pcTextForTask2 = "T2 run++\r\n";

int main(void)
{
    app_periph_init();             /*
	printf("aaaaaaaaaaaaaaaaaaaaaaaaaaa\r\n");
    xTaskCreate(vTask1, "Task 1", 1000, NULL, 2, NULL);
    xTaskCreate(vTask2, "Task 2", 1000, NULL, 1, &TASK_2_Handle);

//    hw_set_vibrator_enable(true);  // 开机立即震动,提示用户
    // ble_stack_debug_setup(0x7FFFFFFF, 0x7FFFFFFF, vprintf);//协议栈日志
//    ble_stack_init(&s_app_ble_callback, &heaps_table); /*< init ble stack*/
//    xTaskCreate(maintask, "MAIN", configMINIMAL_STACK_SIZE * 4, NULL, configMAX_PRIORITIES - 1, &MainHandle);
    vTaskStartScheduler(); /*< freertos run all tasks*/
    for(;;)
        ; /*< Never perform here */
}

执行太快了建议加个延迟

freertos_第14张图片

freertos_第15张图片

任务状态

以前我们很简单地把任务的状态分为2中:运行(Runing)、非运行(Not Running)。

对于非运行的状态,还可以继续细分,比如前面的**[优先级实验]**中:

  • Task3执行vTaskDelay后:处于非运行状态,要过3秒种才能再次运行
  • Task3运行期间,Task1、Task2也处于非运行状态,但是它们随时可以运行
  • 这两种"非运行"状态就不一样,可以细分为:
    • 阻塞状态(Blocked)
    • 暂停状态(Suspended)
    • 就绪状态(Ready)

阻塞状态(Blocked)

在日常生活的例子中,母亲在电脑前跟同事沟通时,如果同事一直没回复,那么母亲的工作就被卡住了、被堵住了、处于阻塞状态(Blocked)。重点在于:母亲在等待

在**[优先级实验]**实验中,如果把任务3中的vTaskDelay调用注释掉,那么任务1、任务2根本没有执行的机会,任务1、任务2被"饿死"了(starve)。

在实际产品中,我们不会让一个任务一直运行,而是使用"事件驱动"的方法让它运行:

  • 任务要等待某个事件,事件发生后它才能运行
  • 在等待事件过程中,它不消耗CPU资源
  • 在等待事件的过程中,这个任务就处于阻塞状态(Blocked)

在阻塞状态的任务,它可以等待两种类型的事件:

  • 时间相关的事件
    • 可以等待一段时间:我等2分钟
    • 也可以一直等待,直到某个绝对时间:我等到下午3点
  • 同步事件:这事件由别的任务,或者是中断程序产生
    • 例子1:任务A等待任务B给它发送数据
    • 例子2:任务A等待用户按下按键
    • 同步事件的来源有很多(这些概念在后面会细讲):
      • 队列(queue)
      • 二进制信号量(binary semaphores)
      • 计数信号量(counting semaphores)
      • 互斥量(mutexes)
      • 递归互斥量、递归锁(recursive mutexes)
      • 事件组(event groups)
      • 任务通知(task notifications)

在等待一个同步事件时,可以加上超时时间。比如等待队里数据,超时时间设为10ms:

  • 10ms之内有数据到来:成功返回
  • 10ms到了,还是没有数据:超时返回

暂停状态(Suspended)

在日常生活的例子中,母亲正在电脑前跟同事沟通,母亲可以暂停:

  • 好烦啊,我暂停一会
  • 领导说:你暂停一下

FreeRTOS中的任务也可以进入暂停状态,唯一的方法是通过vTaskSuspend函数。函数原型如下:

void vTaskSuspend( TaskHandle_t xTaskToSuspend );
/*
	参数xTaskToSuspend表示要暂停的任务,如果为NULL,表示暂停自己。

	要退出暂停状态,只能由别人来操作:

	别的任务调用:vTaskResume

	中断程序调用:xTaskResumeFromISR

	实际开发中,暂停状态用得不多。
*/

就绪状态(Ready)

这个任务完全准备好了,随时可以运行:只是还轮不到它。这时,它就处于就绪态(Ready)。

完整的状态转换图

freertos_第16张图片

Delay函数

void vTaskDelay( const TickType_t xTicksToDelay ); /* xTicksToDelay: 等待多少给Tick */


BaseType_t xTaskDelayUntil( TickType_t * const pxPreviousWakeTime,const TickType_t xTimeIncrement );

/* pxPreviousWakeTime: 上一次被唤醒的时间
 * pxPreviousWakeTime = xTaskGetTickCount();//用于获取系统当前运行的时钟节拍数
 * xTimeIncrement: 要阻塞到(pxPreviousWakeTime + xTimeIncrement)
 * 单位都是Tick Count
 */
static TaskHandle_t TASK_1_Handle;
static TaskHandle_t TASK_2_Handle;
int flag = 0;
void vTask3( void *pvParameters )
{
    const TickType_t xDelay300ms = pdMS_TO_TICKS( 300UL );
    for(;;) {


    }
}

void vTask2( void *pvParameters )
{
    for( ;; )
	{
		printf( "Task 2 is %d\r\n",(uint16_t)uxTaskPriorityGet( NULL ) );//输出打印的东西要注意一下别太相似了,不然串口输出会有问题
	}
}

void vTask1( void *pvParameters )
{


    const TickType_t xDelay50ms = pdMS_TO_TICKS( 50UL );
	TickType_t xLastWakeTime;
	int i;
	
	/* 获得当前的Tick Count */
	xLastWakeTime = xTaskGetTickCount();
			
	for( ;; )
	{

		flag = 1;
		/* 故意加入多个循环,让程序运行时间长一点 */
		for (i = 0; i <5; i++)
			printf( "Task 1 is running %d\r\n",(uint16_t)uxTaskPriorityGet( NULL ) );

#if 1	
		vTaskDelay(50);
		printf("aaaaaa\r\n");
		i = 0;
#else		
		vTaskDelayUntil(&xLastWakeTime, xDelay50ms);
#endif		
	}
}



static const char *pcTextForTask1 = "T1 run--\r\n";
static const char *pcTextForTask2 = "T2 run++\r\n";

int main(void)
{
    app_periph_init();             /*
	printf("aaaaaaaaaaaaaaaaaaaaaaaaaaa\r\n");
    xTaskCreate(vTask1, "Task 1", 1000, NULL, 2, NULL);
    xTaskCreate(vTask2, "Task 2", 1000, NULL, 1, NULL);

//    hw_set_vibrator_enable(true);  // 开机立即震动,提示用户
    // ble_stack_debug_setup(0x7FFFFFFF, 0x7FFFFFFF, vprintf);//协议栈日志
//    ble_stack_init(&s_app_ble_callback, &heaps_table); /*< init ble stack*/
//    xTaskCreate(maintask, "MAIN", configMINIMAL_STACK_SIZE * 4, NULL, configMAX_PRIORITIES - 1, &MainHandle);
    vTaskStartScheduler(); /*< freertos run all tasks*/
    for(;;)
        ; /*< Never perform here */
}

freertos_第17张图片

freertos_第18张图片

空闲任务及其钩子函数

空闲任务(Idle任务)的作用:释放被删除的任务的内存。

除了上述目的之外,为什么必须要有空闲任务?一个良好的程序,它的任务都是事件驱动的:平时大部分时间处于阻塞状态。有可能我们自己创建的所有任务都无法执行,但是调度器必须能找到一个可以运行的任务:所以,我们要提供空闲任务。在使用vTaskStartScheduler() 函数来创建、启动调度器时,这个函数内部会创建空闲任务:

  • 空闲任务优先级为0:它不能阻碍用户任务运行
  • 空闲任务要么处于就绪态,要么处于运行态,永远不会阻塞

空闲任务的优先级为0,这意味着一旦某个用户的任务变为就绪态,那么空闲任务马上被切换出去,让这个用户任务运行。在这种情况下,我们说用户任务"抢占"(pre-empt)了空闲任务,这是由调度器实现的。

要注意的是:如果使用vTaskDelete() 来删除任务,那么你就要确保空闲任务有机会执行,否则就无法释放被删除任务的内存。

我们可以添加一个空闲任务的钩子函数(Idle Task Hook Functions),空闲任务的循环每执行一次,就会调用一次钩子函数。钩子函数的作用有这些:

  • 执行一些低优先级的、后台的、需要连续执行的函数
  • 测量系统的空闲时间:空闲任务能被执行就意味着所有的高优先级任务都停止了,所以测量空闲任务占据的时间,就可以算出处理器占用率。
  • 让系统进入省电模式:空闲任务能被执行就意味着没有重要的事情要做,当然可以进入省电模式了。

空闲任务的钩子函数的限制:

  • 不能导致空闲任务进入阻塞状态、暂停状态
  • 如果你会使用vTaskDelete() 来删除任务,那么钩子函数要非常高效地执行。如果空闲任务移植卡在钩子函数里的话,它就无法释放内存。

使用钩子函数的前提

  • 把这个宏定义为1:configUSE_IDLE_HOOK
  • 实现vApplicationIdleHook函数

freertos_第19张图片

调度算法

正在运行的任务,被称为"正在使用处理器",它处于运行状态。在单处理系统中,任何时间里只能有一个任务处于运行状态。

非运行状态的任务,它处于这3中状态之一:阻塞(Blocked)、暂停(Suspended)、就绪(Ready)。就绪态的任务,可以被调度器挑选出来切换为运行状态,调度器永远都是挑选最高优先级的就绪态任务并让它进入运行状态。

阻塞状态的任务,它在等待"事件",当事件发生时任务就会进入就绪状态。事件分为两类:时间相关的事件、同步事件。所谓时间相关的事件,就是设置超时时间:在指定时间内阻塞,时间到了就进入就绪状态。使用时间相关的事件,可以实现周期性的功能、可以实现超时功能。同步事件就是:某个任务在等待某些信息,别的任务或者中断服务程序会给它发送信息。怎么"发送信息"?方法很多,有:任务通知(task notification)、队列(queue)、事件组(event group)、信号量(semaphoe)、互斥量(mutex)等。这些方法用来发送同步信息,比如表示某个外设得到了数据。

通过配置文件FreeRTOSConfig.h的两个配置项来配置调度算法:

configUSE_PREEMPTION//抢占式

configUSE_TIME_SLICING//时间片

同步互斥与通信

可以把多任务系统当做一个团队,里面的每一个任务就相当于团队里的一个人。团队成员之间要协调工作进度(同步)、争用会议室(互斥)沟通(通信)。多任务系统中所涉及的概念,都可以在现实生活中找到例子。

各类RTOS都会涉及这些概念:任务通知(task notification)队列(queue)事件组(event group)信号量(semaphoe)、**互斥量(mutex)**等

同步与互斥的概念

一句话理解同步与互斥:我等你用完厕所,我再用厕所。 什么叫同步?就是:哎哎哎,我正在用厕所,你等会。 什么叫互斥?就是:哎哎哎,我正在用厕所,你不能进来。 同步与互斥经常放在一起讲,是因为它们之的关系很大,“互斥”操作可以使用“同步”来实现。我“等”你用完厕所,我再用厕所。这不就是用“同步”来实现“互斥”吗?

再举一个例子。在团队活动里,同事A先写完报表,经理B才能拿去向领导汇报。经理B必须等同事A完成报表,AB之间有依赖,B必须放慢脚步,被称为同步。在团队活动中,同事A已经使用会议室了,经理B也想使用,即使经理B是领导,他也得等着,这就叫互斥。经理B跟同事A说:你用完会议室就提醒我。这就是使用"同步"来实现"互斥"。

有时候看代码更容易理解,伪代码如下:

void  抢厕所(void)
{
   if (有人在用) 我眯一会;
   用厕所;
   喂,醒醒,有人要用厕所吗;
}

假设有A、B两人早起抢厕所,A先行一步占用了;B慢了一步,于是就眯一会;当A用完后叫醒B,B也就愉快地上厕所了。 在这个过程中,A、B是互斥地访问“厕所”,“厕所”被称之为临界资源。我们使用了“休眠-唤醒”的同步机制实现了“临界资源”的“互斥访问”。

同一时间只能有一个人使用的资源,被称为临界资源。比如任务A、B都要使用串口来打印,串口就是临界资源。如果A、B同时使用串口,那么打印出来的信息就是A、B混杂,无法分辨。所以使用串口时,应该是这样:A用完,B再用;B用完,A再用。

同步与互斥并不简单及各类方法的对比

队列(queue)

先进先出(FIFO)

freertos_第20张图片

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KWu0DfXG-1662542959866)(C:\Users\John\AppData\Roaming\Typora\typora-user-images\image-20220907164618190.png)]

freertos_第21张图片
freertos_第22张图片

队列函数

1.创建
动态分配内存:xQueueCreate,队列的内存在函数内部动态分配

QueueHandle_t xQueueCreate( UBaseType_t uxQueueLength, 
							UBaseType_t uxItemSize );
// uxQueueLength 队列长度,最多能存放多少个数据(item)
// uxItemSize   每个数据(item)的大小:以字节为单位

静态分配内存:xQueueCreateStatic,队列的内存要事先分配好
返回值 非0:成功,返回句柄,以后使用句柄来操作队列
NULL:失败,因为pxQueueBuffer为NULL

QueueHandle_t xQueueCreateStatic(
                           UBaseType_t uxQueueLength,
                           UBaseType_t uxItemSize,
                           uint8_t *pucQueueStorageBuffer,
                           StaticQueue_t *pxQueueBuffer
                       );
/*
uxQueueLength	队列长度,最多能存放多少个数据(item)
uxItemSize	每个数据(item)的大小:以字节为单位
pucQueueStorageBuffer	如果uxItemSize非0,pucQueueStorageBuffer必须指向一个uint8_t数组,
此数组大小至少为"uxQueueLength * uxItemSize"
pxQueueBuffer	必须执行一个StaticQueue_t结构体,用来保存队列的数据结构
*/
#define QUEUE_LENGTH 10 //队列长度
 #define ITEM_SIZE sizeof( uint32_t )//元素大小
 
 // xQueueBuffer用来保存队列结构体
 StaticQueue_t xQueueBuffer;//
 
 // ucQueueStorage 用来保存队列的数据
 // 大小为:队列长度 * 数据大小
 uint8_t ucQueueStorage[ QUEUE_LENGTH * ITEM_SIZE ];
 
 void vATask( void *pvParameters )
 {
	QueueHandle_t xQueue1;
 
	// 创建队列: 可以容纳QUEUE_LENGTH个数据,每个数据大小是ITEM_SIZE
	xQueue1 = xQueueCreateStatic( QUEUE_LENGTH,
						  ITEM_SIZE,
						  ucQueueStorage,
						  &xQueueBuffer ); 
 }

2.复位

void vQueueDelete( QueueHandle_t xQueue );
//把队列恢复为初始状态 队列刚被创建时,里面没有数据;

3.删除

void vQueueDelete( QueueHandle_t xQueue );
//只能删除使用动态方法创建的队列

4.写队列
可以把数据写到队列头部,也可以写到尾部,这些函数有两个版本:在任务中使用、在ISR中使用。
返回值 pdPASS:数据成功写入了队列
errQUEUE_FULL:写入失败,因为队列满了。

/* 等同于xQueueSendToBack
 * 往队列尾部写入数据,如果没有空间,阻塞时间为xTicksToWait
 */
BaseType_t xQueueSend(
                                QueueHandle_t    xQueue,
                                const void       *pvItemToQueue,
                                TickType_t       xTicksToWait
                            );

/* 
 * 往队列尾部写入数据,如果没有空间,阻塞时间为xTicksToWait
 */
BaseType_t xQueueSendToBack(
                                QueueHandle_t    xQueue,
                                const void       *pvItemToQueue,
                                TickType_t       xTicksToWait
                            );


/* 
 * 往队列尾部写入数据,此函数可以在中断函数中使用,不可阻塞
 */
BaseType_t xQueueSendToBackFromISR(
                                      QueueHandle_t xQueue,
                                      const void *pvItemToQueue,
                                      BaseType_t *pxHigherPriorityTaskWoken
                                   );

/* 
 * 往队列头部写入数据,如果没有空间,阻塞时间为xTicksToWait
 */
BaseType_t xQueueSendToFront(
                                QueueHandle_t    xQueue,
                                const void       *pvItemToQueue,
                                TickType_t       xTicksToWait
                            );

/* 
 * 往队列头部写入数据,此函数可以在中断函数中使用,不可阻塞
 */
BaseType_t xQueueSendToFrontFromISR(
                                      QueueHandle_t xQueue,
                                      const void *pvItemToQueue,
                                      BaseType_t *pxHigherPriorityTaskWoken
                                   );

/*
xQueue	队列句柄,要写哪个队列
pvItemToQueue	数据指针,这个数据的值会被复制进队列,复制多大的数据?在创建队列时已经指定了数据大小
xTicksToWait	如果队列满则无法写入新数据,可以让任务进入阻塞状态,xTicksToWait表示阻塞的最大时间(Tick Count)。
如果被设为0,无法写入数据时函数会立刻返回;如果被设为portMAX_DELAY,则会一直阻塞直到有空间可写
*/

个人理解xQueueSendToFront() 将数据插入到buf的N-1的位置,pcReadFrom指针指向这个位置,当这个数据被读出pcReadFrom又会指移动itemsize的位置回到pcHead位置,
freertos_第23张图片

5.查询
可以查询队列中有多少个数据、有多少空余空间

/*
 * 返回队列中可用数据的个数
 */
UBaseType_t uxQueueMessagesWaiting( const QueueHandle_t xQueue );

/*
 * 返回队列中可用空间的个数
 */
UBaseType_t uxQueueSpacesAvailable( const QueueHandle_t xQueue );

6.覆盖/偷看
当队列长度为1时,可以使用xQueueOverwrite()或xQueueOverwriteFromISR()来覆盖数据。 注意,队列长度必须为1。当队列满时,这些函数会覆盖里面的数据,这也以为着这些函数不会被阻塞

/* 覆盖队列
 * xQueue: 写哪个队列
 * pvItemToQueue: 数据地址
 * 返回值: pdTRUE表示成功, pdFALSE表示失败
 */
BaseType_t xQueueOverwrite(
                           QueueHandle_t xQueue,
                           const void * pvItemToQueue
                      );

BaseType_t xQueueOverwriteFromISR(
                           QueueHandle_t xQueue,
                           const void * pvItemToQueue,
                           BaseType_t *pxHigherPriorityTaskWoken
                      );

中断中使用的变量介绍
freertos_第24张图片
如果想让队列中的数据供多方读取,也就是说读取时不要移除数据,要留给后来人。那么可以使用"窥视",也就是xQueuePeek()或xQueuePeekFromISR()。这些函数会从队列中复制出数据,但是不移除数据。这也意味着,如果队列中没有数据,那么"偷看"时会导致阻塞;一旦队列中有数据,以后每次"偷看"都会成功

/* 偷看队列
 * xQueue: 偷看哪个队列
 * pvItemToQueue: 数据地址, 用来保存复制出来的数据
 * xTicksToWait: 没有数据的话阻塞一会
 * 返回值: pdTRUE表示成功, pdFALSE表示失败
 */
BaseType_t xQueuePeek(
                          QueueHandle_t xQueue,
                          void * const pvBuffer,
                          TickType_t xTicksToWait
                      );

BaseType_t xQueuePeekFromISR(
                                 QueueHandle_t xQueue,
                                 void *pvBuffer,
                             );

队列初始用法

QueueHandle_t xQueue;

static void vSenderTask( void *pvParameters)
{
    int32_t lValueToSend;
	BaseType_t xStatus;//用来判读是否发送成功的 

	lValueToSend = ( int32_t ) pvParameters;

    	for( ;; )
	{
		/* 写队列
		 * xQueue: 写哪个队列
		 * &lValueToSend: 写什么数据? 传入数据的地址, 会从这个地址把数据复制进队列
		 * 0: 不阻塞, 如果队列满的话, 写入失败, 立刻返回
		 */
		xStatus = xQueueSendToBack( xQueue, &lValueToSend, 0 );

		if( xStatus != pdPASS )
		{
			printf( "Could not send to the queue.\r\n" );
		}
	}
}

static void vReceiverTask( void *pvParameters)
{
    int32_t lReceivedValue;
	BaseType_t xStatus;
	const TickType_t xTicksToWait = 100 ;//队列为空阻塞100ms
    for(;;)
    {
        xStatus = xQueueReceive( xQueue, &lReceivedValue, xTicksToWait );

		if( xStatus == pdPASS )
		{
			printf( "Received = %d\r\n", lReceivedValue );
		}
		else
		{
			printf( "Could not receive from the queue.\r\n" );
		}
    }
}

int main(void)
{	
	//LCD 初始化
	ILI9341_Init();  
	
	//触摸屏初始化
	XPT2046_Init();
	SysTick_Init(72);
//	Calibrate_or_Get_TouchParaWithFlash(3,0);

	/* USART config */
    USART_Config();  
    
    UBaseType_t uxQueueLength = 10;
    UBaseType_t  uxItemSize = sizeof(int32_t);

    xQueue = xQueueCreate(uxQueueLength,uxItemSize);

    if(xQueue)
    {
        xTaskCreate( vSenderTask, "Sender1", 1000, ( void * ) 100, 1, NULL );//任务1 发送两个不同的参数上去一个也是一样的,没必要一定得俩
		xTaskCreate( vSenderTask, "Sender2", 1000, ( void * ) 200, 1, NULL );
        xTaskCreate( vReceiverTask, "Receiver", 1000, NULL, 2, NULL );//任务2 用来接收队列的消息
    }
    else
        printf("xQueueCreate failed\r\n");
    vTaskStartScheduler();  
	for (;;)
	{
	}
		
}

freertos_第25张图片
freertos_第26张图片
2.往队列里面写入结构体

static void vSenderTask( void *pvParameters)
{
	BaseType_t xStatus;//用来判读是否发送成功的 
    const TickType_t xTicksToWait = ( 100 );
    for( ;; )
	{
		xStatus = xQueueSendToBack( xQueue, pvParameters, xTicksToWait );

		if( xStatus != pdPASS )
		{
			printf( "Could not send to the queue.\r\n" );
		}
	}
}

static void vReceiverTask( void *pvParameters)
{
   Data_t xReceivedStructure;
	BaseType_t xStatus;
	const TickType_t xTicksToWait = 100 ;//队列为空阻塞100ms
    for(;;)
    {
        xStatus = xQueueReceive( xQueue, &xReceivedStructure, xTicksToWait );

		if( xStatus == pdPASS )
		{
			if(xReceivedStructure.eDataID == eMotorSpeed)
                printf( "From CAN, MotorSpeed = %d\r\n", xReceivedStructure.lDataValue );
            else if(xReceivedStructure.eDataID == eSpeedSetPoint)
                printf( "From HMI, SpeedSetPoint = %d\r\n", xReceivedStructure.lDataValue );
		}
		else
		{
			printf( "Could not receive from the queue.\r\n" );
		}
    }
}
#endif
int main(void)
{	
	//LCD 初始化
	ILI9341_Init();  
	
	//触摸屏初始化
	XPT2046_Init();
	SysTick_Init(72);
//	Calibrate_or_Get_TouchParaWithFlash(3,0);

	/* USART config */
    USART_Config();  
    
    UBaseType_t uxQueueLength = 10;
    UBaseType_t  uxItemSize = sizeof(int32_t);

    UBaseType_t  uxItemSize1 = sizeof(Data_t);
    xQueue = xQueueCreate(uxQueueLength,uxItemSize1);

    if(xQueue)
    {
        #if test_1 == 1 
        xTaskCreate( vSenderTask, "Sender1", 1000, ( void * ) 100, 1, NULL );
		xTaskCreate( vSenderTask, "Sender2", 1000, ( void * ) 200, 1, NULL );
        xTaskCreate( vReceiverTask, "Receiver", 1000, NULL, 2, NULL );
        #elif test_2 == 1
        xTaskCreate( vSenderTask, "CAN_task", 1000,   (void *)&(xStructsToSend[0]), 2, NULL );
		xTaskCreate( vSenderTask, "HDMI_task", 1000,  (void *)&(xStructsToSend[1]), 2, NULL );
        xTaskCreate( vReceiverTask, "Receiver", 1000, NULL, 1, NULL );//读任务有限级低所以进入该任务的时候队列一定有数据
        #endif
    }
    else
        printf("xQueueCreate failed\r\n");
    vTaskStartScheduler();  
	for (;;)
	{
	}
		
}

freertos_第27张图片

freertos_第28张图片
3.队列传输大数据

#elif test_3 == 1
static void vSenderTask( void *pvParameters )
{
	BaseType_t xStatus;
	static int cnt = 0;
	
	char *buffer;

	/* 无限循环 */
	for( ;; )
	{
		sprintf(pcBuffer, "chen xxxxxx %d\r\n", cnt++);
		buffer = pcBuffer; // buffer变量等于数组的地址, 下面要把这个地址写入队列
		
		/* 写队列
		 * xQueue: 写哪个队列
		 * pvParameters: 写什么数据? 传入数据的地址, 会从这个地址把数据复制进队列
		 * 0: 如果队列满的话, 即刻返回
		 */
		xStatus = xQueueSendToBack( xQueue, &buffer, 0 ); /* 只需要写入4字节, 无需写入整个buffer */

		if( xStatus != pdPASS )
		{
			printf( "Could not send to the queue.\r\n" );
		}
	}
}
static void vReceiverTask( void *pvParameters )
{
	/* 读取队列时, 用这个变量来存放数据 */
	char *buffer;
	const TickType_t xTicksToWait = pdMS_TO_TICKS( 100UL );	
	BaseType_t xStatus;

	/* 无限循环 */
	for( ;; )
	{
		/* 读队列
		 * xQueue: 读哪个队列
		 * &xReceivedStructure: 读到的数据复制到这个地址
		 * xTicksToWait: 没有数据就阻塞一会
		 */
		xStatus = xQueueReceive( xQueue, &buffer, xTicksToWait); /* 得到buffer地址,只是4字节 */

		if( xStatus == pdPASS )
		{
			/* 读到了数据 */
			printf("Get: %s", buffer);
		}
		else
		{
			/* 没读到数据 */
			printf( "Could not receive from the queue.\r\n" );
		}
	}
}
#endif
int main(void)
{	
	//LCD 初始化
	ILI9341_Init();  
	
	//触摸屏初始化
	XPT2046_Init();
	SysTick_Init(72);
//	Calibrate_or_Get_TouchParaWithFlash(3,0);

	/* USART config */
    USART_Config();  
    
    UBaseType_t uxQueueLength = 10;
    UBaseType_t  uxItemSize = sizeof(int32_t);

    UBaseType_t  uxItemSize1 = sizeof(Data_t);
    UBaseType_t  uxItemSize2 = sizeof(char *);
    xQueue = xQueueCreate(uxQueueLength,uxItemSize2);

    if(xQueue)
    {
        #if test_1 == 1 
        xTaskCreate( vSenderTask, "Sender1", 1000, ( void * ) 100, 1, NULL );
		xTaskCreate( vSenderTask, "Sender2", 1000, ( void * ) 200, 1, NULL );
        xTaskCreate( vReceiverTask, "Receiver", 1000, NULL, 2, NULL );
        #elif test_2 == 1
        xTaskCreate( vSenderTask, "CAN_task", 1000,   (void *)&(xStructsToSend[0]), 2, NULL );
		xTaskCreate( vSenderTask, "HDMI_task", 1000,  (void *)&(xStructsToSend[1]), 2, NULL );
        xTaskCreate( vReceiverTask, "Receiver", 1000, NULL, 1, NULL );//读任务有限级低所以进入该任务的时候队列一定有数据
        #elif test_3 == 1
        xTaskCreate( vSenderTask, "Sender", 1000, NULL, 1, NULL );

		xTaskCreate( vReceiverTask, "Receiver", 1000, NULL, 2, NULL );
        #endif
    }
    else
        printf("xQueueCreate failed\r\n");
    vTaskStartScheduler();  
	for (;;)
	{
	}
		
}

freertos_第29张图片
4.消息队列模拟锁的用法

#elif test_4 == 1
void Init_Usart_Lock(void)
{
	UBaseType_t uxQueueLength = 10;
	UBaseType_t  uxItemSize = sizeof(uint32_t);
	uint32_t val = 1;
	xQueue = xQueueCreate(uxQueueLength,uxItemSize);
	if(xQueue)
	{
		xQueueSendToBack( xQueue, &val, 0 ); 
	}
	else
	{
		while (1);
	}

}
void Get_Usart_Lock(void)
{
	uint32_t val;
	xQueueReceive( xQueue, &val, 100); /* 得到buffer地址,只是4字节 */
}

void PUt_Usart_Lock(void)
{
	uint32_t val = 10;
	xQueueSendToBack( xQueue, &val, 100); /* 得到buffer地址,只是4字节 */
}

static void vTask1(void *pvParameters)
{
	for(;;)
	{
		Get_Usart_Lock();
		printf("%s runing\r\n",__func__);
		PUt_Usart_Lock();
        vTaskDelay(1);//延时一下保证任务正常切换,也保证串口输出没问题
	}
}

static void vTask2(void *pvParameters)
{
	for(;;)
	{
		Get_Usart_Lock();
		printf("%s runing\r\n",__func__);
		PUt_Usart_Lock();
        vTaskDelay(1);
	}
}

#endif
int main(void)
{	
	//LCD 初始化
	ILI9341_Init();  
	
	//触摸屏初始化
	XPT2046_Init();
	SysTick_Init(72);
//	Calibrate_or_Get_TouchParaWithFlash(3,0);

	/* USART config */
    USART_Config();  
    Init_Usart_Lock();
    UBaseType_t uxQueueLength = 10;
    UBaseType_t  uxItemSize = sizeof(int32_t);

    UBaseType_t  uxItemSize1 = sizeof(Data_t);
    UBaseType_t  uxItemSize2 = sizeof(char *);
    // xQueue = xQueueCreate(uxQueueLength,uxItemSize2);

    if(xQueue)
    {
        #if test_1 == 1 
        xTaskCreate( vSenderTask, "Sender1", 1000, ( void * ) 100, 1, NULL );
		xTaskCreate( vSenderTask, "Sender2", 1000, ( void * ) 200, 1, NULL );
        xTaskCreate( vReceiverTask, "Receiver", 1000, NULL, 2, NULL );
        #elif test_2 == 1
        xTaskCreate( vSenderTask, "CAN_task", 1000,   (void *)&(xStructsToSend[0]), 2, NULL );
		xTaskCreate( vSenderTask, "HDMI_task", 1000,  (void *)&(xStructsToSend[1]), 2, NULL );
        xTaskCreate( vReceiverTask, "Receiver", 1000, NULL, 1, NULL );//读任务有限级低所以进入该任务的时候队列一定有数据
        #elif test_3 == 1
        xTaskCreate( vSenderTask, "Sender", 1000, NULL, 1, NULL );

		xTaskCreate( vReceiverTask, "Receiver", 1000, NULL, 2, NULL );
		#elif test_4 == 1
		xTaskCreate( vTask1, "vTask1", 1000, NULL, 1, NULL );

		xTaskCreate( vTask2, "vTask2", 1000, NULL, 1, NULL );
        #endif
    }
    else
        printf("xQueueCreate failed\r\n");
    vTaskStartScheduler();  
	for (;;)
	{
	}
		
}

freertos_第30张图片
6.队列集

#elif test_5 == 1
static void vTask1(void *pvParameters)
{
	int i = 0;
	for(;;)
	{
		i++;
		xQueueSendToBack( xQueue1, &i, 100);
        vTaskDelay(10);
	}
}

static void vTask2(void *pvParameters)
{
	int i = 0;
	for(;;)
	{
		i--;
		xQueueSendToBack( xQueue2, &i, 100);
		vTaskDelay(10);
	}
}
static void vTask3(void *pvParameters)
{
	QueueSetMemberHandle_t handle;
	int i = 0;
	for(;;)
	{
		handle = xQueueSelectFromSet(xQueueSet,100);//判断哪个队列有元素,并且返回有数据的队列的

		xQueueReceive(handle,&i,0);
		if(i>0)
		{
			printf("xqueue1 send message i = %d\r\n",i);
		}
		else
			printf("xqueue2 send message i = %d\r\n",i);

	}
}

#endif
int main(void)
{	
	//LCD 初始化
	ILI9341_Init();  
	
	//触摸屏初始化
	XPT2046_Init();
	SysTick_Init(72);
//	Calibrate_or_Get_TouchParaWithFlash(3,0);

	/* USART config */
    USART_Config();  
//    Init_Usart_Lock();
    UBaseType_t uxQueueLength = 10;
    UBaseType_t  uxItemSize = sizeof(int32_t);

    UBaseType_t  uxItemSize1 = sizeof(Data_t);
    UBaseType_t  uxItemSize2 = sizeof(char *);
    // xQueue = xQueueCreate(uxQueueLength,uxItemSize2);
	xQueue1 = xQueueCreate(2,sizeof(int));
	xQueue2 = xQueueCreate(2,sizeof(int));
	xQueueSet = xQueueCreateSet(4);//创建队列集,参数为队列1长度+队列2长度


	//队列集里加入队列
	xQueueAddToSet(xQueue1,xQueueSet);
	xQueueAddToSet(xQueue2,xQueueSet);



    if(1)
    {
        #if test_1 == 1 
        xTaskCreate( vSenderTask, "Sender1", 1000, ( void * ) 100, 1, NULL );
		xTaskCreate( vSenderTask, "Sender2", 1000, ( void * ) 200, 1, NULL );
        xTaskCreate( vReceiverTask, "Receiver", 1000, NULL, 2, NULL );
        #elif test_2 == 1
        xTaskCreate( vSenderTask, "CAN_task", 1000,   (void *)&(xStructsToSend[0]), 2, NULL );
		xTaskCreate( vSenderTask, "HDMI_task", 1000,  (void *)&(xStructsToSend[1]), 2, NULL );
        xTaskCreate( vReceiverTask, "Receiver", 1000, NULL, 1, NULL );//读任务有限级低所以进入该任务的时候队列一定有数据
        #elif test_3 == 1
        xTaskCreate( vSenderTask, "Sender", 1000, NULL, 1, NULL );

		xTaskCreate( vReceiverTask, "Receiver", 1000, NULL, 2, NULL );
		#elif test_4 == 1
		xTaskCreate( vTask1, "vTask1", 1000, NULL, 1, NULL );

		xTaskCreate( vTask2, "vTask2", 1000, NULL, 1, NULL );
		#elif test_5 == 1
		xTaskCreate( vTask1, "vTask1", 1000, NULL, 1, NULL );
		xTaskCreate( vTask2, "vTask2", 1000, NULL, 1, NULL );
		xTaskCreate( vTask3, "vTask3", 1000, NULL, 2, NULL );
        #endif
    }
    else
        printf("xQueueCreate failed\r\n");
    vTaskStartScheduler();  
	for (;;)
	{
	}
		
}

freertos_第31张图片

信号量(semaphore)

freertos_第32张图片

信号量跟队列的对比

freertos_第33张图片
freertos_第34张图片

两种信号量的对比

信号量的计数值都有限制:限定了最大值。如果最大值被限定为1,那么它就是二进制信号量;如果最大值不是1,它就是计数型信号量。
freertos_第35张图片

信号量函数

1.创建

二值信号量:

/* 创建一个二进制信号量,返回它的句柄。
 * 此函数内部会分配信号量结构体 
 * 返回值: 返回句柄,非NULL表示成功
 */
SemaphoreHandle_t xSemaphoreCreateBinary( void );

/* 创建一个二进制信号量,返回它的句柄。
 * 此函数无需动态分配内存,所以需要先有一个StaticSemaphore_t结构体,并传入它的指针
 * 返回值: 返回句柄,非NULL表示成功
 */
SemaphoreHandle_t xSemaphoreCreateBinaryStatic( StaticSemaphore_t *pxSemaphoreBuffer );

计数型信号量

/* 创建一个计数型信号量,返回它的句柄。
 * 此函数内部会分配信号量结构体 
 * uxMaxCount: 最大计数值
 * uxInitialCount: 初始计数值
 * 返回值: 返回句柄,非NULL表示成功
 */
SemaphoreHandle_t xSemaphoreCreateCounting(UBaseType_t uxMaxCount, UBaseType_t uxInitialCount);

/* 创建一个计数型信号量,返回它的句柄。
 * 此函数无需动态分配内存,所以需要先有一个StaticSemaphore_t结构体,并传入它的指针
 * uxMaxCount: 最大计数值
 * uxInitialCount: 初始计数值
 * pxSemaphoreBuffer: StaticSemaphore_t结构体指针
 * 返回值: 返回句柄,非NULL表示成功
 */
SemaphoreHandle_t xSemaphoreCreateCountingStatic( UBaseType_t uxMaxCount, 
                                                 UBaseType_t uxInitialCount, 
                                                 StaticSemaphore_t *pxSemaphoreBuffer );

删除
对于动态创建的信号量,不再需要它们时,可以删除它们以回收内存。

/*
 * xSemaphore: 信号量句柄,你要删除哪个信号量
 */
void vSemaphoreDelete( SemaphoreHandle_t xSemaphore );

give/take

BaseType_t xSemaphoreGive( SemaphoreHandle_t xSemaphore );//在任务中使用
/*
xSemaphore	信号量句柄,释放哪个信号量
pdTRUE表示成功,
如果二进制信号量的计数值已经是1,再次调用此函数则返回失败;
如果计数型信号量的计数值已经是最大值,再次调用此函数则返回失败
*/
 BaseType_t xSemaphoreGiveFromISR(
                        SemaphoreHandle_t xSemaphore,
                        BaseType_t *pxHigherPriorityTaskWoken
                    );//中断使用
/*
xSemaphore	信号量句柄,释放哪个信号量
pxHigherPriorityTaskWoken	如果释放信号量导致更高优先级的任务变为了就绪态,
则*pxHigherPriorityTaskWoken = pdTRUE
返回值	pdTRUE表示成功,
如果二进制信号量的计数值已经是1,再次调用此函数则返回失败;
如果计数型信号量的计数值已经是最大值,再次调用此函数则返回失败
*/                
BaseType_t xSemaphoreTake(
                   SemaphoreHandle_t xSemaphore,
                   TickType_t xTicksToWait
               );
/*
xSemaphore		信号量句柄,获取哪个信号量
xTicksToWait	如果无法马上获得信号量,阻塞一会:0:不阻塞,马上返回
												portMAX_DELAY: 一直阻塞直到成功
												其他值: 阻塞的Tick个数,可以使用pdMS_TO_TICKS()来指定阻塞时间为若干ms
返回值	pdTRUE表示成功
*/       
BaseType_t xSemaphoreTakeFromISR(
                        SemaphoreHandle_t xSemaphore,
                        BaseType_t *pxHigherPriorityTaskWoken
                    );
 /*xSemaphore	信号量句柄,获取哪个信号量
pxHigherPriorityTaskWoken	如果获取信号量导致更高优先级的任务变为了就绪态,
则*pxHigherPriorityTaskWoken = pdTRUE
返回值	pdTRUE表示成功
*/

信号量使用

SemaphoreHandle_t xBinarySemaphore;

#if test_1 == 1 
static void vTask1(void *pvParameters)
{
	int ok_status =	0;
	int fail_status =	0;
    int i = 0;
	for(;;)
	{
        for(i = 0; i < 3; i++)
		{
			if(xSemaphoreGive(xBinarySemaphore) == pdTRUE)
			{
				printf("Give OK %d\r\n", ok_status++);
			}

			else
			{
				printf("Give Err  %d\r\n", fail_status++);
			}
		}
		vTaskDelay(20);
	}

}
static void vTask2(void *pvParameters)
{
	int ok_status =	0;
	int fail_status = 0;
	for(;;)
	{
		if(xSemaphoreTake(xBinarySemaphore,0) == pdTRUE)
		{
			printf("Take OK %d\r\n", ok_status++);
		}
		else{ 
			printf("Take Err %d\r\n", fail_status++);
		}

	}

}
#elif test_2 == 1


#elif test_3 == 1

#elif test_4 == 1

#elif test_5 == 1

#endif
int main(void)
{	
	
	SysTick_Init(72);
    USART_Config();  

	xBinarySemaphore = xSemaphoreCreateBinary( );//创建二值信号量


    if(xBinarySemaphore != NULL)
    {
        #if test_1 == 1 
			xTaskCreate( vTask1, "vTask1", 1000, NULL, 2, NULL );
			xTaskCreate( vTask2, "vTask2", 1000, NULL, 1, NULL );
      	#elif test_2 == 1
        #elif test_3 == 1
        #elif test_4 == 1
		#elif test_5 == 1
		
        #endif
    }
    else
        printf("xQueueCreate failed\r\n");
    vTaskStartScheduler();  
	for (;;)
	{
	}
		
}

从打印结果上可以看出只有当give出了信号量才能Take成功,并且二值信号量give和take的数目都只有1
freertos_第36张图片
信号量的数据存储(ring_buffer)

#define BUF_LEN  32
#define NEXT_PLACE(i) ((i+1) % 32)
static char ring_buffer[BUF_LEN] = {0};
uint32_t ring_buffer_r = 0;
uint32_t ring_buffer_w = 0;
static int is_ring_buffer_empty(void)
{
	return ring_buffer_r == ring_buffer_w;
}

static int is_ring_buffer_full(void)
{
	return NEXT_PLACE(ring_buffer_w) == ring_buffer_r;//画个图就懂了
}

static int ring_buffer_put(unsigned char val)
{
	if (is_ring_buffer_full())
		return -1;
	ring_buffer[ring_buffer_w] = val;
	ring_buffer_w = NEXT_PLACE(ring_buffer_w);
	return 0;
}

static int ring_buffer_get(unsigned char *pval)
{
	if (is_ring_buffer_empty())
		return -1;
	*pval = ring_buffer[ring_buffer_r];
	ring_buffer_r = NEXT_PLACE(ring_buffer_r);
	return 0;
}


static void vTask1(void *pvParameters)
{
	int ok_status =	0;
	int fail_status =	0;
	int cnt_tx = 0;
	for(;;)
	{
		for(int i = 0; i<3;i++)
		{
			ring_buffer_put('a'+cnt_tx);
			cnt_tx++;
			if(xSemaphoreGive(xBinarySemaphore) == pdTRUE)
			{
				printf("Give OK %d\r\n", ok_status++);
			}

			else
			{
				printf("Give Err  %d\r\n", fail_status++);
			}
		}
        vTaskDelay(10);

	}
}
static void vTask2(void *pvParameters)
{
	int ok_status =	0;
	int fail_status = 0;
    uint8_t c;
	for(;;)
	{
        if(xSemaphoreTake(xBinarySemaphore,0) == pdTRUE)
		{
			printf("Take OK %d\r\n", ok_status++);
            while (ring_buffer_get(&c) == 0)
			{
				printf("%c", c);
			}
			printf("\r\n");
		}
		else{ 
			printf("Take Err %d\r\n", fail_status++);
		}
	}

}

#elif test_3 == 1

#elif test_4 == 1

#elif test_5 == 1

#endif
int main(void)
{	
	
	SysTick_Init(72);
    USART_Config();  

	xBinarySemaphore = xSemaphoreCreateBinary( );//创建二值信号量


    if(xBinarySemaphore != NULL)
    {
        #if test_1 == 1 
			xTaskCreate( vTask1, "vTask1", 1000, NULL, 2, NULL );
			xTaskCreate( vTask2, "vTask2", 1000, NULL, 1, NULL );
      	#elif test_2 == 1
			xTaskCreate( vTask1, "vTask1", 1000, NULL, 2, NULL );
			xTaskCreate( vTask2, "vTask2", 1000, NULL, 1, NULL );
        #elif test_3 == 1
        #elif test_4 == 1
		#elif test_5 == 1
		
        #endif
    }
    else
        printf("xQueueCreate failed\r\n");
    vTaskStartScheduler();  
	for (;;)
	{
	}
		
}

在这个实验中使用了一个简单的ring_buffer来进行数据的缓存,可以看出数据存入ring_buffer中并没有丢失,并且可以一次性完全的读出,ring_buffer的实现原理比这个实验来得更为重要一点
freertos_第37张图片使用计数型信号量
这个配置要打开
在这里插入图片描述

#elif test_3 == 1
static void vTask1(void *pvParameters)
{
	int i;
	int cnt_ok = 0;
	int cnt_err = 0;
	
	/* 无限循环 */
	for( ;; )
	{		
		for (i = 0; i < 4; i++)
		{			
			/* 提醒对方 */
			if (xSemaphoreGive(xCountingSemaphore) == pdTRUE)
				printf("Give CountingSemaphore %d time: OK\r\n", cnt_ok++);
			else
				printf("Give CountingSemaphore %d time: ERR\r\n", cnt_err++);
		}
				
		vTaskDelay(20);
	}
}
static void vTask2(void *pvParameters)
{
	int cnt_ok = 0;
	int cnt_err = 0;

	/* 无限循环 */
	for( ;; )
	{
		if( xSemaphoreTake(xCountingSemaphore, portMAX_DELAY) == pdTRUE )
		{
			/* 得到了信号量 */
			printf("Get CountingSemaphore OK: %d\r\n", cnt_ok++);
		}
		else
		{
			/* 没有得到了信号量 */
			printf("Get BinarySemaphore ERR: %d\r\n", cnt_err++);
		}
	}
}
#elif test_4 == 1

#elif test_5 == 1

#endif
int main(void)
{	
	
	SysTick_Init(72);
    USART_Config();  

	xBinarySemaphore = xSemaphoreCreateBinary( );//创建二值信号量
	xCountingSemaphore = xSemaphoreCreateCounting(3, 0);//创建计数信号量
	//QueueHandle_t xQueueCreateCountingSemaphore( const UBaseType_t uxMaxCount,const UBaseType_t uxInitialCount )
	// 参数1信号量最大值,2信号量初始值

    if(xBinarySemaphore != NULL && xCountingSemaphore != NULL)
    {
        #if test_1 == 1 
			xTaskCreate( vTask1, "vTask1", 1000, NULL, 2, NULL );
			xTaskCreate( vTask2, "vTask2", 1000, NULL, 1, NULL );
      	#elif test_2 == 1
			xTaskCreate( vTask1, "vTask1", 1000, NULL, 2, NULL );
			xTaskCreate( vTask2, "vTask2", 1000, NULL, 1, NULL );
        #elif test_3 == 1
			xTaskCreate( vTask1, "vTask1", 1000, NULL, 2, NULL );
			xTaskCreate( vTask2, "vTask2", 1000, NULL, 1, NULL );
        #elif test_4 == 1
		#elif test_5 == 1
		
        #endif
    }
    else
        printf("xQueueCreate failed\r\n");
    vTaskStartScheduler();  
	for (;;)
	{
	}
		
}


freertos_第38张图片

互斥量(mutex)

freertos_第39张图片

互斥量的使用场合

在多任务系统中,任务A正在使用某个资源,还没用完的情况下任务B也来使用的话,就可能导致问题。
比如对于串口,任务A正使用它来打印,在打印过程中任务B也来打印,客户看到的结果就是A、B的信息混杂在一起。

下面就是这个情况的发生
freertos_第40张图片
freertos_第41张图片
freertos_第42张图片

互斥量函数

互斥量是一种特殊的二进制信号量。

使用互斥量时,先创建、然后去获得释放它。使用句柄来表示一个互斥量。
创建互斥量的函数有2种:动态分配内存,静态分配内存,函数原型如下:

/* 创建一个互斥量,返回它的句柄。
 * 此函数内部会分配互斥量结构体 
 * 返回值: 返回句柄,非NULL表示成功
 */
SemaphoreHandle_t xSemaphoreCreateMutex( void );

/* 创建一个互斥量,返回它的句柄。
 * 此函数无需动态分配内存,所以需要先有一个StaticSemaphore_t结构体,并传入它的指针
 * 返回值: 返回句柄,非NULL表示成功
 */
SemaphoreHandle_t xSemaphoreCreateMutexStatic( StaticSemaphore_t *pxMutexBuffer );

configUSE_MUTEXES
在这里插入图片描述
其他函数
要注意的是,互斥量不能在ISR(中断)中使用。
各类操作函数,比如删除、give/take,跟一般是信号量是一样的。

/*
 * xSemaphore: 信号量句柄,你要删除哪个信号量, 互斥量也是一种信号量
 */
void vSemaphoreDelete( SemaphoreHandle_t xSemaphore );

/* 释放 */
BaseType_t xSemaphoreGive( SemaphoreHandle_t xSemaphore );
/* 获得 */
BaseType_t xSemaphoreTake(
                   SemaphoreHandle_t xSemaphore,
                   TickType_t xTicksToWait
               );

互斥量基本使用

使用互斥量时有如下特点:
刚创建的互斥量可以被成功"take"
“take"互斥量成功的任务,被称为"holder”,只能由它"give"互斥量;别的任务"give"不成功
在ISR中不能使用互斥量

#if test_1 == 1 
static void vSenderTask(void *pvParameters)
{
	int cnt = 0;
	int task_name = (int )pvParameters;
	int i;
	char c;
	for(;;)
	{
		/* 获得互斥量: 上锁 */
		xSemaphoreTake(xMutex, portMAX_DELAY);
		
		printf("Task %d use UART count: %d, ", task_name, cnt++);
		c = (task_name == 1 ) ? 'a' : 'A';
		for (i = 0; i < 26; i++)
			printf("%c", c + i);
		printf("\r\n");
		
		/* 释放互斥量: 开锁 */
		xSemaphoreGive(xMutex);
		
		vTaskDelay(10);
	}
}
#elif test_2 == 1


#elif test_3 == 1

#elif test_4 == 1

#elif test_5 == 1

#endif
int main(void)
{	
	
	SysTick_Init(72);
    USART_Config();  

	xMutex = xSemaphoreCreateMutex( );//创建互斥锁
	

    if(xMutex != NULL )
    {
        #if test_1 == 1 
			xTaskCreate( vSenderTask, "Sender1", 1000, (void *)1, 1, NULL );
			xTaskCreate( vSenderTask, "Sender2", 1000, (void *)2, 1, NULL );
      	#elif test_2 == 1
			
        #elif test_3 == 1
			
        #elif test_4 == 1
		#elif test_5 == 1
		
        #endif
    }
    else
        printf("xMutex failed\r\n");
    vTaskStartScheduler();  
	for (;;)
	{
	}
		
}

加入互斥锁显示(完美显示)
freertos_第43张图片
不加互斥锁(打印乱七八糟的了)
freertos_第44张图片
谁上锁就由谁解锁?
在FreeRTOSv10.4.1版本中是不允许这样使用的

#elif test_2 == 1
static void vTakeTask(void *pvParameters)
{
	BaseType_t Xmutex_status;
	Xmutex_status = xSemaphoreTake(xMutex, 0);
	printf("%s mutex status == %s\r\n",__func__,(Xmutex_status == pdTRUE) ? "SUCCESSS":"FAILER");
//	xSemaphoreGive(xMutex);
	for(;;)
	{
		vTaskDelay(10);
	}
}

static void vGiveorTakeTask(void *pvParameters)
{
	BaseType_t Xmutex_status;
	Xmutex_status = xSemaphoreTake(xMutex, 0);
	printf("%s mutex status == %s\r\n",__func__,(Xmutex_status == pdTRUE) ? "SUCCESSS":"FAILER");
	if(Xmutex_status != pdTRUE)
	{
		Xmutex_status = xSemaphoreGive(xMutex);
		printf("%s give mutex_status == %s\r\n",__func__,(Xmutex_status == pdTRUE) ? "SUCCESSS":"FAILER");
	}
	Xmutex_status = xSemaphoreTake(xMutex, 0);
	printf("%s Take again mutex_status == %s\r\n",__func__,(Xmutex_status == pdTRUE) ? "SUCCESSS":"FAILER");
	
	for(;;)
	{
		vTaskDelay(10);
	}
}

#elif test_3 == 1

#elif test_4 == 1

#elif test_5 == 1

#endif
int main(void)
{	
	
	SysTick_Init(72);
    USART_Config();  

	xMutex = xSemaphoreCreateMutex( );//创建互斥锁
	

    if(xMutex != NULL )
    {
        #if test_1 == 1 
			xTaskCreate( vSenderTask, "Sender1", 1000, (void *)1, 1, NULL );
			xTaskCreate( vSenderTask, "Sender2", 1000, (void *)2, 1, NULL );
      	#elif test_2 == 1
			xTaskCreate( vTakeTask, "Task1", 1000, NULL, 2, NULL );
			xTaskCreate( vGiveorTakeTask, "Task2", 1000, NULL, 1, NULL );
        #elif test_3  == 1
			
        #elif test_4 == 1
		#elif test_5 == 1
		
        #endif
    }
    else
        printf("xMutex failed\r\n");
    vTaskStartScheduler();  
	for (;;)
	{
	}
		
}

这样的写法可以实现但是不合法
freertos_第45张图片
正常的开锁解锁就不会出现断言(把任务1 的注释打开 )
freertos_第46张图片

优先级反转
freertos_第47张图片

#elif test_3 == 1
static int high 	= 3;
static int medim 	= 2;
static int low		= 1;
static void vHPTask(void *pvParameters)
{
	high  	= 10;
	medim 	= 9;
	low		= 7;	
	printf("%s  [%d %d %d ]\r\n",__func__,high,medim,low);
	vTaskDelay(10);//延迟10ms 空出CPU时间
	for(;;)
	{
		// 拿到锁之后再去修改变量值打印
		xSemaphoreTake(xMutex, portMAX_DELAY);
		high  	= 20;
		medim 	= 20;
		low		= 20;
		printf("%s  [%d %d %d ]\r\n",__func__,high,medim,low);

	}
}

static void vMPTask(void *pvParameters)
{
	high  	= 20;
	medim 	= 29;
	low		= 27;	
	printf("%s  [%d %d %d ]\r\n",__func__,high,medim,low);
	vTaskDelay(10);//延迟10ms 空出CPU时间
	for(;;)
	{
		high  	= 30;
		medim 	= 30;
		low		= 30;
		printf("%s  [%d %d %d ]\r\n",__func__,high,medim,low);
	}
}


static void vLPTask(void *pvParameters)
{
	high  	= 30;
	medim 	= 39;
	low		= 37;	
	printf("%s  [%d %d %d ]\r\n",__func__,high,medim,low);
	for(;;)
	{
		// 拿锁
		xSemaphoreTake(xMutex, 0);
		high  	= 40;
		medim 	= 40;
		low		= 40;
		// 打印
        for(int i = 0; i<10;i++)
            printf("%s  [%d %d %d ]\r\n",__func__,high++,medim,low);
		// 释放锁
		xSemaphoreGive(xMutex);
	}
}
#elif test_4 == 1

#elif test_5 == 1

#endif
int main(void)
{	
	
	SysTick_Init(72);
    USART_Config();  

	xMutex = xSemaphoreCreateMutex( );//创建互斥锁
	

    if(xMutex != NULL )
    {
        #if test_1 == 1 
			xTaskCreate( vSenderTask, "Sender1", 1000, (void *)1, 1, NULL );
			xTaskCreate( vSenderTask, "Sender2", 1000, (void *)2, 1, NULL );
      	#elif test_2 == 1
			xTaskCreate( vTakeTask, "Task1", 1000, NULL, 2, NULL );
			xTaskCreate( vGiveorTakeTask, "Task2", 1000, NULL, 1, NULL );
        #elif test_3  == 1
			xTaskCreate( vLPTask, "LPTask", 1000, NULL, 1, NULL );
			xTaskCreate( vMPTask, "MPTask", 1000, NULL, 2, NULL );
			xTaskCreate( vHPTask, "HPTask", 1000, NULL, 3, NULL );
        #elif test_4 == 1
		#elif test_5 == 1
		
        #endif
    }
    else
        printf("xMutex failed\r\n");
    vTaskStartScheduler();  
	for (;;)
	{
	}
		
}

这边要强调的是我们将拿不到锁进入阻塞态的时间拉到最长了,若改短时间,任务1还是会抢占执行的
freertos_第48张图片
优先级继承

#elif test_4 == 1
static int high 	= 3;
static int medim 	= 2;
static int low		= 1;
static void vHPTask(void *pvParameters)
{
	high  	= 10;
	medim 	= 9;
	low		= 7;	
	printf("%s  [%d %d %d ]\r\n",__func__,high,medim,low);
	vTaskDelay(10);//延迟10ms 空出CPU时间
	for(;;)
	{
		printf("vHPTask wait for Lock\r\n");
		// 拿到锁之后再去修改变量值打印
		xSemaphoreTake(xMutex, portMAX_DELAY);/*在这个位置获取互斥锁,这个时候锁在任务3手上,那不到阻塞
												,优先级相当于给了任务3,任务3执行完释放锁,然后任务1继续执行*/
		high  	= 20;
		medim 	= 20;
		low		= 20;
		printf("%s  [%d %d %d ]\r\n",__func__,high,medim,low);
		xSemaphoreGive(xMutex);
	}
}

static void vMPTask(void *pvParameters)
{
	high  	= 20;
	medim 	= 29;
	low		= 27;	
	printf("%s  [%d %d %d ]\r\n",__func__,high,medim,low);
	vTaskDelay(10);//延迟10ms 空出CPU时间
	for(;;)
	{
		high  	= 30;
		medim 	= 30;
		low		= 30;
		printf("%s  [%d %d %d ]\r\n",__func__,high,medim,low);
	}
}


static void vLPTask(void *pvParameters)
{
	high  	= 30;
	medim 	= 39;
	low		= 37;	
	printf("%s  [%d %d %d ]\r\n",__func__,high,medim,low);
	for(;;)
	{
		// 拿锁
		xSemaphoreTake(xMutex, 0);
		high  	= 40;
		medim 	= 40;
		low		= 40;
		// 打印
        for(int i = 0; i<10;i++)
            printf("%s  [%d %d %d ]\r\n",__func__,high++,medim,low);
		// 释放锁
		xSemaphoreGive(xMutex);
	}
}
#elif test_5 == 1

#endif
int main(void)
{	
	
	SysTick_Init(72);
    USART_Config();  

	xMutex = xSemaphoreCreateMutex( );//创建互斥锁
	

    if(xMutex != NULL )
    {
        #if test_1 == 1 
			xTaskCreate( vSenderTask, "Sender1", 1000, (void *)1, 1, NULL );
			xTaskCreate( vSenderTask, "Sender2", 1000, (void *)2, 1, NULL );
      	#elif test_2 == 1
			xTaskCreate( vTakeTask, "Task1", 1000, NULL, 2, NULL );
			xTaskCreate( vGiveorTakeTask, "Task2", 1000, NULL, 1, NULL );
        #elif test_3  == 1
			xTaskCreate( vLPTask, "LPTask", 1000, NULL, 1, NULL );
			xTaskCreate( vMPTask, "MPTask", 1000, NULL, 2, NULL );
			xTaskCreate( vHPTask, "HPTask", 1000, NULL, 3, NULL );
        #elif test_4 == 1
			xTaskCreate( vLPTask, "LPTask", 1000, NULL, 1, NULL );
			xTaskCreate( vMPTask, "MPTask", 1000, NULL, 2, NULL );
			xTaskCreate( vHPTask, "HPTask", 1000, NULL, 3, NULL );
		#elif test_5 == 1
		
        #endif
    }
    else
        printf("xMutex failed\r\n");
    vTaskStartScheduler();  
	for (;;)
	{
	}
		
}

freertos_第49张图片

递归锁

在这里插入图片描述

freertos_第50张图片

API

/* 创建一个递归锁,返回它的句柄。
 * 此函数内部会分配互斥量结构体 
 * 返回值: 返回句柄,非NULL表示成功
 */
SemaphoreHandle_t xSemaphoreCreateRecursiveMutex( void );


/* 释放 */
BaseType_t xSemaphoreGiveRecursive( SemaphoreHandle_t xSemaphore );

/* 获得 */
BaseType_t xSemaphoreTakeRecursive(
                   SemaphoreHandle_t xSemaphore,
                   TickType_t xTicksToWait
               );
#elif test_5 == 1
static void vTakeTask( void *pvParameters )
{
	const TickType_t xTicksToWait = pdMS_TO_TICKS( 100UL );	
	BaseType_t xStatus;
	int i;

	
	/* 无限循环 */
	for( ;; )
	{	
		/* 获得递归锁: 上锁 */
		xStatus = xSemaphoreTakeRecursive(xMutex, portMAX_DELAY);
		printf("Task1 take the Mutex in main loop %s\r\n", \
			(xStatus == pdTRUE)? "Success" : "Failed");

		/* 阻塞很长时间, 让另一个任务执行, 
		 * 看看它有无办法再次获得递归锁 
		 */
		vTaskDelay(xTicksToWait);

		for (i = 0; i < 10; i++)
		{
			/* 获得递归锁: 上锁 */
			xStatus = xSemaphoreTakeRecursive(xMutex, portMAX_DELAY);
			
			printf("Task1 take the Mutex in sub loop %s, for time %d\r\n", \
				(xStatus == pdTRUE)? "Success" : "Failed", i);

			/* 释放递归锁 */
			xSemaphoreGiveRecursive(xMutex);
		}
		
		/* 释放递归锁 */
		xSemaphoreGiveRecursive(xMutex);
	}
}

static void vGiveAndTakeTask( void *pvParameters )
{
	const TickType_t xTicksToWait = pdMS_TO_TICKS( 10UL );	
	BaseType_t xStatus;

	/* 尝试获得递归锁: 上锁 */
	xStatus = xSemaphoreTakeRecursive(xMutex, 0);
	printf("Task2: at first, take the Mutex %s\r\n", \
		(xStatus == pdTRUE)? "Success" : "Failed");
	
	/* 如果失败则监守自盗: 开锁 */
	if (xStatus != pdTRUE)
	{
		/* 无法释放别人持有的锁 */
		xStatus = xSemaphoreGiveRecursive(xMutex);
		printf("Task2: give Mutex %s\r\n", \
			(xStatus == pdTRUE)? "Success" : "Failed");
	}

	/* 如果无法获得, 一直等待 */
	xStatus = xSemaphoreTakeRecursive(xMutex, portMAX_DELAY);
	printf("Task2: and then, take the Mutex %s\r\n", \
		(xStatus == pdTRUE)? "Success" : "Failed");
	
	/* 无限循环 */
	for( ;; )
	{	
		/* 什么都不做 */
		vTaskDelay(xTicksToWait);
	}
}
#endif
int main(void)
{	
	
	SysTick_Init(72);
    USART_Config();  

	// xMutex = xSemaphoreCreateMutex( );//创建互斥锁
	xMutex = xSemaphoreCreateRecursiveMutex( );//创建递归锁

    if(xMutex != NULL )
    {
        #if test_1 == 1 
			xTaskCreate( vSenderTask, "Sender1", 1000, (void *)1, 1, NULL );
			xTaskCreate( vSenderTask, "Sender2", 1000, (void *)2, 1, NULL );
      	#elif test_2 == 1
			xTaskCreate( vTakeTask, "Task1", 1000, NULL, 2, NULL );
			xTaskCreate( vGiveorTakeTask, "Task2", 1000, NULL, 1, NULL );
        #elif test_3  == 1
			xTaskCreate( vLPTask, "LPTask", 1000, NULL, 1, NULL );
			xTaskCreate( vMPTask, "MPTask", 1000, NULL, 2, NULL );
			xTaskCreate( vHPTask, "HPTask", 1000, NULL, 3, NULL );
        #elif test_4 == 1
			xTaskCreate( vLPTask, "LPTask", 1000, NULL, 1, NULL );
			xTaskCreate( vMPTask, "MPTask", 1000, NULL, 2, NULL );
			xTaskCreate( vHPTask, "HPTask", 1000, NULL, 3, NULL );
		#elif test_5 == 1
			xTaskCreate( vTakeTask, "Task1", 1000, NULL, 2, NULL );
			xTaskCreate( vGiveAndTakeTask, "Task2", 1000, NULL, 1, NULL );
        #endif
    }
    else
        printf("xMutex failed\r\n");
    vTaskStartScheduler();  
	for (;;)
	{
	}
		
}

互斥递归锁谁上锁只能谁解锁,其他任务无法解锁
freertos_第51张图片

freertos_第52张图片

事件组(event group)

freertos_第53张图片
freertos_第54张图片
freertos_第55张图片
freertos_第56张图片

事件组函数

创建

EventGroupHandle_t xEventGroup;//句柄

/* 创建一个事件组,返回它的句柄。
 * 此函数内部会分配事件组结构体 
 * 返回值: 返回句柄,非NULL表示成功
 */
EventGroupHandle_t xEventGroupCreate( void );

/* 创建一个事件组,返回它的句柄。
 * 此函数无需动态分配内存,所以需要先有一个StaticEventGroup_t结构体,并传入它的指针
 * 返回值: 返回句柄,非NULL表示成功
 */
EventGroupHandle_t xEventGroupCreateStatic( StaticEventGroup_t * pxEventGroupBuffer );

删除

/*
 * xEventGroup: 事件组句柄,你要删除哪个事件组
 */
void vEventGroupDelete( EventGroupHandle_t xEventGroup )

设置事件
可以设置事件组的某个位、某些位,使用的函数有2个:
在任务中使用xEventGroupSetBits()
在ISR中使用xEventGroupSetBitsFromISR()
有一个或多个任务在等待事件,如果这些事件符合这些任务的期望,那么任务还会被唤醒。

/* 设置事件组中的位
 * xEventGroup: 哪个事件组
 * uxBitsToSet: 设置哪些位? 
 *              如果uxBitsToSet的bitX, bitY为1, 那么事件组中的bitX, bitY被设置为1
 *               可以用来设置多个位,比如 0x15 就表示设置bit4, bit2, bit0
 * 返回值: 返回原来的事件值(没什么意义, 因为很可能已经被其他任务修改了)
 */
EventBits_t xEventGroupSetBits( EventGroupHandle_t xEventGroup,
                                    const EventBits_t uxBitsToSet );


/* 设置事件组中的位
 * xEventGroup: 哪个事件组
 * uxBitsToSet: 设置哪些位? 
 *              如果uxBitsToSet的bitX, bitY为1, 那么事件组中的bitX, bitY被设置为1
 *               可以用来设置多个位,比如 0x15 就表示设置bit4, bit2, bit0
 * pxHigherPriorityTaskWoken: 有没有导致更高优先级的任务进入就绪态? pdTRUE-有, pdFALSE-没有
 * 返回值: pdPASS-成功, pdFALSE-失败
 */
BaseType_t xEventGroupSetBitsFromISR( EventGroupHandle_t xEventGroup,
									  const EventBits_t uxBitsToSet,
									  BaseType_t * pxHigherPriorityTaskWoken );

值得注意的是,ISR中的函数,比如队列函数xQueueSendToBackFromISR、信号量函数xSemaphoreGiveFromISR,它们会唤醒某个任务,最多只会唤醒1个任务。
但是设置事件组时,有可能导致多个任务被唤醒,这会带来很大的不确定性。所以xEventGroupSetBitsFromISR函数不是直接去设置事件组,而是给一个FreeRTOS后台任务(daemon task)发送队列数据,由这个任务来设置事件组。
如果后台任务的优先级比当前被中断的任务优先级高,xEventGroupSetBitsFromISR会设置*pxHigherPriorityTaskWoken为pdTRUE。
如果daemon task成功地把队列数据发送给了后台任务,那么xEventGroupSetBitsFromISR的返回值就是pdPASS。

等待事件
使用xEventGroupWaitBits来等待事件,可以等待某一位、某些位中的任意一个,也可以等待多位;等到期望的事件后,还可以清除某些位。

EventBits_t xEventGroupWaitBits( EventGroupHandle_t xEventGroup,
                                 const EventBits_t uxBitsToWaitFor,
                                 const BaseType_t xClearOnExit,
                                 const BaseType_t xWaitForAllBits,
                                 TickType_t xTicksToWait );

xEventGroup						等待哪个事件组?

uxBitsToWaitFor					等待哪些位?哪些位要被测试?

xClearOnExit					函数提出前是否要清除事件?
								pdTRUE: 清除uxBitsToWaitFor指定的位
								pdFALSE: 不清除
xWaitForAllBits					怎么测试?是"AND"还是"OR"(pdTRUE,等待的位全为1才成功)		pdTRUE: 等待的位,全部为1;
(pdFALSE,等待的位有为1就成功)	pdFALSE: 等待的位,某一个为1即可

xTicksToWait					如果期待的事件未发生,阻塞多久。
								可以设置为0:判断后即刻返回;
								可设置为portMAX_DELAY:一定等到成功才返回;
								可以设置为期望的Tick Count,一般用pdMS_TO_TICKS()把ms转换为Tick Count

返回值							返回的是事件值,
								如果期待的事件发生了,返回的是"非阻塞条件成立"时的事件值;
								如果是超时退出,返回的是超时时刻的事件值。
                                                        

freertos_第57张图片
同步点
freertos_第58张图片

EventBits_t xEventGroupSync(    EventGroupHandle_t xEventGroup,
                                const EventBits_t uxBitsToSet,
                                const EventBits_t uxBitsToWaitFor,
                                TickType_t xTicksToWait );

xEventGroup						哪个事件组?

uxBitsToSet						要设置哪些事件?我完成了哪些事件?
								比如0x05(二进制为0101)会导致事件组的bit0,bit2被设置为1

uxBitsToWaitFor					等待那个位、哪些位?
								比如0x15(二级制10101),表示要等待bit0,bit2,bit4都为1

xTicksToWait					如果期待的事件未发生,阻塞多久。
								可以设置为0:判断后即刻返回;
								可设置为portMAX_DELAY:一定等到成功才返回;
								可以设置为期望的Tick Count,一般用pdMS_TO_TICKS()把ms转换为Tick Count

返回值							返回的是事件值,
								如果期待的事件发生了,返回的是"非阻塞条件成立"时的事件值;
								如果是超时退出,返回的是超时时刻的事件值。

等待多个事件

#if test_1 == 1
/* bit0: 吃饭
 * bit1: 洗澡
 * bit2: 上床
 */
#define EATING   	(1<<0)
#define SHOWER   	(1<<1)
#define UPBED  		(1<<2) 

static void vEating( void *pvParameters )
{
	int i = 0;
	printf("%s running \r\n",__func__);
	for(;;)
	{
		printf("wait eating %d time\r\n",i++); 
		xEventGroupSetBits(xEventGroup,EATING);//发通知。吃完饭了
		xEventGroupWaitBits(xEventGroup,UPBED,pdTRUE,pdTRUE,portMAX_DELAY);//上床完事了就可以继续吃饭了,如果没有就继续等待

	}
}

static void vShowering( void *pvParameters )
{
	int i = 0;
	printf("%s running \r\n",__func__);
	for(;;)
	{
		xEventGroupWaitBits(xEventGroup,EATING,pdFALSE,pdTRUE,portMAX_DELAY);//等待是否吃完饭
		printf("wait Showering %d time\r\n",i++); 
		xEventGroupSetBits(xEventGroup,SHOWER);
	}
}

static void vUp_bed( void *pvParameters )
{
	int i = 0;
	printf("%s running \r\n",__func__);
	for(;;)
	{
		xEventGroupWaitBits(xEventGroup,EATING|SHOWER,pdTRUE,pdTRUE,portMAX_DELAY);//等待是否吃完饭和洗完澡
		printf("wait vUp_bed %d time\r\n",i++); 
		xEventGroupSetBits(xEventGroup,UPBED);
	}
}

#elif test_2 == 1


#elif test_3 == 1

#elif test_4 == 1

#elif test_5 == 1

#endif
int main(void)
{	
	
	SysTick_Init(72);
    USART_Config();  

	xEventGroup = xEventGroupCreate();

    if(xEventGroup != NULL )
    {
        #if test_1 == 1 
			//创建 3个需要很按顺序执行任务 1 吃饭 2 洗澡 3 上床
			xTaskCreate(vEating,"task1",1000,0,1,NULL);
			xTaskCreate(vShowering,"task1",1000,0,2,NULL);
			xTaskCreate(vUp_bed,"task1",1000,0,3,NULL);
      	#elif test_2 == 1
			
        #elif test_3  == 1

        #elif test_4 == 1
			
		#elif test_5 == 1
			
        #endif
    }
    else
        printf("xEventGroup failed\r\n");
    vTaskStartScheduler();  
	for (;;)
	{
	}
		
}

freertos_第59张图片
任务同步
freertos_第60张图片

#elif test_2 == 1
/* bit0: 吃饭
 * bit1: 洗澡
 * bit2: 上床
 */
#define EATING   	(1<<0)
#define SHOWER   	(1<<1)
#define UPBED  		(1<<2) 
#define ALL  		(EATING | SHOWER | UPBED) 

static void vEating( void *pvParameters )
{
	int i = 0;
	printf("%s running \r\n",__func__);
	for(;;)
	{
		printf("%s is Eating %d time....\r\n", (char *)pvParameters, i);
		
		xEventGroupSync(xEventGroup, EATING, ALL, portMAX_DELAY);

		printf("%s is sync Success %d time....\r\n", (char *)pvParameters, i++);
		vTaskDelay(100);
	}
}

static void vShowering( void *pvParameters )
{
	int i = 0;
	printf("%s running \r\n",__func__);
	for(;;)
	{
		printf("%s is Showering %d time....\r\n", (char *)pvParameters, i);
		
		xEventGroupSync(xEventGroup, SHOWER, ALL, portMAX_DELAY);

		printf("%s is sync Success %d time....\r\n", (char *)pvParameters, i++);
		vTaskDelay(100);
	}
}

static void vUp_bed( void *pvParameters )
{
	int i = 0;
	printf("%s running \r\n",__func__);
	for(;;)
	{
		printf("%s is Up_bed %d time....\r\n", (char *)pvParameters, i);
		
		xEventGroupSync(xEventGroup, UPBED, ALL, portMAX_DELAY);

		printf("%s is sync Success %d time....\r\n", (char *)pvParameters, i++);
		vTaskDelay(100);
	}
}


#elif test_3 == 1

#elif test_4 == 1

#elif test_5 == 1

#endif
int main(void)
{	
	
	SysTick_Init(72);
    USART_Config();  

	xEventGroup = xEventGroupCreate();

    if(xEventGroup != NULL )
    {
        #if test_1 == 1 
			//创建 3个需要很按顺序执行任务 1 吃饭 2 洗澡 3 上床
			xTaskCreate(vEating,"task1",1000,0,1,NULL);
			xTaskCreate(vShowering,"task2",1000,0,2,NULL);
			xTaskCreate(vUp_bed,"task3",1000,0,3,NULL);
      	#elif test_2 == 1
			xTaskCreate(vEating,"task1",1000,"A",1,NULL);
			xTaskCreate(vShowering,"task2",1000,"B",2,NULL);
			xTaskCreate(vUp_bed,"task3",1000,"C",3,NULL);
        #elif test_3  == 1

        #elif test_4 == 1
			
		#elif test_5 == 1
			
        #endif
    }
    else
        printf("xEventGroup failed\r\n");
    vTaskStartScheduler();  
	for (;;)
	{
	}
		
}

freertos_第61张图片

任务通知(Task Notifications)

freertos_第62张图片

任务通知(Task Notifications)

freertos_第63张图片

任务通知的特性

freertos_第64张图片

通知状态和通知值

每个任务都有一个结构体:TCB(Task Control Block),里面有2个成员:
一个是uint8_t类型,用来表示通知状态
一个是uint32_t类型,用来表示通知值

typedef struct tskTaskControlBlock
{
    ......
    /* configTASK_NOTIFICATION_ARRAY_ENTRIES = 1 */
    volatile uint32_t ulNotifiedValue[ configTASK_NOTIFICATION_ARRAY_ENTRIES ];
    volatile uint8_t ucNotifyState[ configTASK_NOTIFICATION_ARRAY_ENTRIES ];
    ......
} tskTCB;

通知状态有3种取值:
taskNOT_WAITING_NOTIFICATION:任务没有在等待通知
taskWAITING_NOTIFICATION:任务在等待通知
taskNOTIFICATION_RECEIVED:任务接收到了通知,也被称为pending(有数据了,待处理)

#define taskNOT_WAITING_NOTIFICATION              ( ( uint8_t ) 0 )  /* 也是初始状态 */
#define taskWAITING_NOTIFICATION                  ( ( uint8_t ) 1 )
#define taskNOTIFICATION_RECEIVED                 ( ( uint8_t ) 2 )

通知值可以有很多种类型:
计数值
位(类似事件组)
任意数值

任务通知的使用

使用任务通知,可以实现轻量级的队列(长度为1)、邮箱(覆盖的队列)、计数型信号量、二进制信号量、事件组。

任务通知的两类函数

简化版
在任务中使用xTaskNotifyGive函数,在ISR中使用vTaskNotifyGiveFromISR函数,都是直接给其他任务发送通知:
使得通知值加一
并使得通知状态变为"pending",也就是taskNOTIFICATION_RECEIVED,表示有数据了、待处理
可以使用ulTaskNotifyTake函数来取出通知值:
如果通知值等于0,则阻塞(可以指定超时时间)
当通知值大于0时,任务从阻塞态进入就绪态
在ulTaskNotifyTake返回之前,还可以做些清理工作:把通知值减一,或者把通知值清零
使用ulTaskNotifyTake函数可以实现轻量级的、高效的二进制信号量、计数型信号量。


BaseType_t xTaskNotifyGive( TaskHandle_t xTaskToNotify );

xTaskToNotify		任务句柄(创建任务时得到),给哪个任务发通知

返回值				必定返回pdPASS

void vTaskNotifyGiveFromISR( TaskHandle_t xTaskHandle, 
							BaseType_t *pxHigherPriorityTaskWoken );

xTaskHandle						任务句柄(创建任务时得到),给哪个任务发通知

pxHigherPriorityTaskWoken		被通知的任务,可能正处于阻塞状态。
								此函数发出通知后,会把它从阻塞状态切换为就绪态。
								如果被唤醒的任务的优先级,高于当前任务的优先级,
								则"*pxHigherPriorityTaskWoken"被设置为pdTRUE,
								这表示在中断返回之前要进行任务切换。
								(这一位可用于判断是否需要任务切换)

uint32_t ulTaskNotifyTake( BaseType_t xClearCountOnExit, 
							TickType_t xTicksToWait );

xClearCountOnExit				函数返回前是否清零:
								pdTRUE:把通知值清零
								pdFALSE:如果通知值大于0,则把通知值减一

xTicksToWait					任务进入阻塞态的超时时间,它在等待通知值大于00:不等待,即刻返回;
								portMAX_DELAY:一直等待,直到通知值大于0;
								其他值:Tick Count,可以用pdMS_TO_TICKS()把ms转换为Tick Count
返回值							函数返回之前,在清零或减一之前的通知值。
								如果xTicksToWait非0,则返回值有2种情况:
								1. 大于0:在超时前,通知值被增加了
								2. 等于0:一直没有其他任务增加通知值,最后超时返回0

专业版
freertos_第65张图片


BaseType_t xTaskNotify( TaskHandle_t xTaskToNotify, 
						uint32_t ulValue,
						eNotifyAction eAction );
xTaskToNotify			任务句柄(创建任务时得到),给哪个任务发通知

ulValue					怎么使用ulValue,由eAction参数决定


eNotifyAction取值:
eNoAction						仅仅是更新通知状态为"pending",未使用ulValue。
								这个选项相当于轻量级的、更高效的二进制信号量。

eSetBits						通知值 = 原来的通知值 | ulValue,按位或。
								相当于轻量级的、更高效的事件组。
								eIncrement	通知值 = 原来的通知值 + 1,未使用ulValue。
								相当于轻量级的、更高效的二进制信号量、计数型信号量。
								相当于xTaskNotifyGive()函数。

eSetValueWithoutOverwrite		不覆盖。
								如果通知状态为"pending"(表示有数据未读),
								则此次调用xTaskNotify不做任何事,返回pdFAIL。
								如果通知状态不是"pending"(表示没有新数据),
								则:通知值 = ulValue。

eSetValueWithOverwrite			覆盖。
								无论如何,不管通知状态是否为"pendng",
								通知值 = ulValue。

返回值							pdPASS:成功,大部分调用都会成功
								pdFAIL:只有一种情况会失败,当eAction为eSetValueWithoutOverwrite,
								并且通知状态为"pending"(表示有新数据未读),这时就会失败。

BaseType_t xTaskNotifyFromISR( TaskHandle_t xTaskToNotify,
                               uint32_t ulValue, 
                               eNotifyAction eAction, 
                               BaseType_t *pxHigherPriorityTaskWoken );

xTaskNotifyFromISR函数跟xTaskNotify很类似,就多了最后一个参数pxHigherPriorityTaskWoken。在很多ISR函数中,这个参数的作用都是类似的,使用场景如下:

  • 被通知的任务,可能正处于阻塞状态
  • xTaskNotifyFromISR函数发出通知后,会把接收任务从阻塞状态切换为就绪态
  • 如果被唤醒的任务的优先级,高于当前任务的优先级,则 (*)pxHigherPriorityTaskWoken被设置为pdTRUE,这表示在中断返回之前要进行任务切换。 (加括号是为不让加粗被破坏)

BaseType_t xTaskNotifyWait( uint32_t ulBitsToClearOnEntry, 
                            uint32_t ulBitsToClearOnExit, 
                            uint32_t *pulNotificationValue, 
                            TickType_t xTicksToWait );


ulBitsToClearOnEntry					在xTaskNotifyWait入口处,要清除通知值的哪些位?
										通知状态不是"pending"的情况下,才会清除。
										它的本意是:我想等待某些事件发生,所以先把"旧数据"的某些位清零。
										能清零的话:通知值 = 通知值 & ~(ulBitsToClearOnEntry)。
										比如传入0x01,表示清除通知值的bit0;
										传入0xffffffff即ULONG_MAX,表示清除所有位,即把值设置为0

ulBitsToClearOnExit						在xTaskNotifyWait出口处,如果不是因为超时推出,而是因为得到了数据而退出时:
										通知值 = 通知值 & ~(ulBitsToClearOnExit)。
										在清除某些位之前,通知值先被赋给"*pulNotificationValue"。
										比如入0x03,表示清除通知值的bit0、bit1;
										传入0xffffffff即ULONG_MAX,表示清除所有位,即把值设置为0

pulNotificationValue					用来取出通知值。
										在函数退出时,使用ulBitsToClearOnExit清除之前,把通知值赋给"*pulNotificationValue"。
										如果不需要取出通知值,可以设为NULL。

xTicksToWait							任务进入阻塞态的超时时间,它在等待通知状态变为"pending"0:不等待,即刻返回;
										portMAX_DELAY:一直等待,直到通知状态变为"pending";
										其他值:Tick Count,可以用pdMS_TO_TICKS()把ms转换为Tick Count

返回值									1. pdPASS:成功
										这表示xTaskNotifyWait成功获得了通知:
										可能是调用函数之前,通知状态就是"pending";
										也可能是在阻塞期间,通知状态变为了"pending"2. pdFAIL:没有得到通知。

传输计数值

static TaskHandle_t xRecvTask;

#define BUF_LEN  32
#define NEXT_PLACE(i) ((i+1) % 32)
static char ring_buffer[BUF_LEN] = {0};
uint32_t ring_buffer_r = 0;
uint32_t ring_buffer_w = 0;
static int is_ring_buffer_empty(void)
{
	return ring_buffer_r == ring_buffer_w;
}

static int is_ring_buffer_full(void)
{
	return NEXT_PLACE(ring_buffer_w) == ring_buffer_r;//画个图就懂了
}

static int ring_buffer_put(unsigned char val)
{
	if (is_ring_buffer_full())
		return -1;
	ring_buffer[ring_buffer_w] = val;
	ring_buffer_w = NEXT_PLACE(ring_buffer_w);
	return 0;
}

static int ring_buffer_get(unsigned char *pval)
{
	if (is_ring_buffer_empty())
		return -1;
	*pval = ring_buffer[ring_buffer_r];
	ring_buffer_r = NEXT_PLACE(ring_buffer_r);
	return 0;
}

#if test_1 == 1
static void vSenderTask(void *pvParameters)
{
	int i;
	int cnt_tx = 0;
	int cnt_ok = 0;
	int cnt_err = 0;
	char c;
	for(;;)
	{
		for(i = 0; i < 3;i++)
		{
			c = 'a'+ cnt_tx++;
			ring_buffer_put(c);

			if(xTaskNotifyGive(xRecvTask) == pdTRUE)
				printf("xTaskNotifyGive success %d time \r\n",cnt_ok++);
			else
				printf("xTaskNotifyGive fakl %d time \r\n",cnt_err++);
		}
		vTaskDelay(20);

	}
}
static void vReceiverTask(void *pvParameters)
{
	int cnt_ok = 0;
	uint8_t c;
	int notify_val;
	
	for( ;; )
	{
		notify_val = ulTaskNotifyTake(pdTRUE, portMAX_DELAY);

		printf("ulTaskNotifyTake success %d time notify_val = %d\r\n ",cnt_ok,notify_val);
		
		while(notify_val--)
		{
			ring_buffer_get(&c);
			printf("%c ",c);
		}
		printf("\r\n");
	}
}

#elif test_2 == 1


#elif test_3 == 1

#elif test_4 == 1

#elif test_5 == 1

#endif
int main(void)
{	
	
	SysTick_Init(72);
    USART_Config();  


    if(1)
    {
        #if test_1 == 1 
			/* 创建1个任务用于发送任务通知
	 		* 优先级为2
	 		*/
			xTaskCreate( vSenderTask, "Sender", 1000, NULL, 2, NULL );

			/* 创建1个任务用于接收任务通知
			* 优先级为1
			*/
			xTaskCreate( vReceiverTask, "Receiver", 1000, NULL, 1, &xRecvTask );
      	#elif test_2 == 1

        #elif test_3  == 1

        #elif test_4 == 1
			
		#elif test_5 == 1
			
        #endif
    }
    else
        printf("xEventGroup failed\r\n");
    vTaskStartScheduler();  
	for (;;)
	{
	}
		
}

freertos_第66张图片
freertos_第67张图片


传输任意值
freertos_第68张图片

/**
  ******************************************************************************
  * @file    main.c
  * @author  fire
  * @version V1.0
  * @date    2013-xx-xx
  * @brief   ��������ʵ��
  ******************************************************************************
  * @attention
  *
  * ʵ��ƽ̨:Ұ�� F103-ָ���� STM32 ������ 
  * ��̳    :http://www.firebbs.cn
  * �Ա�    :https://fire-stm32.taobao.com
  *
  ************************************** ****************************************
  */ 
 
#include "stm32f10x.h"
#include "./usart/bsp_usart.h"	
#include "./lcd/bsp_ili9341_lcd.h"
#include "./lcd/bsp_xpt2046_lcd.h"
#include "./flash/bsp_spi_flash.h"
#include "./led/bsp_led.h" 
#include "bsp_SysTick.h"
#include 
#include "../../lvgl.h"
#include "lv_port_disp.h"
#include "lv_port_indev.h"
#include "lv_examples.h" 
#include "lv_clock.h"
#include "FreeRTOS.h"
#include "task.h"
#include "FreeRTOSConfig.h"
#include "queue.h"
#include "main_define.h"
#include "semphr.h"
#include "event_groups.h"

static TaskHandle_t xRecvTask;

#define BUF_LEN  32
#define NEXT_PLACE(i) ((i+1) % 32)
static char ring_buffer[BUF_LEN] = {0};
uint32_t ring_buffer_r = 0;
uint32_t ring_buffer_w = 0;
static int is_ring_buffer_empty(void)
{
	return ring_buffer_r == ring_buffer_w;
}

static int is_ring_buffer_full(void)
{
	return NEXT_PLACE(ring_buffer_w) == ring_buffer_r;//画个图就懂了
}

static int ring_buffer_put(unsigned char val)
{
	if (is_ring_buffer_full())
		return -1;
	ring_buffer[ring_buffer_w] = val;
	ring_buffer_w = NEXT_PLACE(ring_buffer_w);
	return 0;
}

static int ring_buffer_get(unsigned char *pval)
{
	if (is_ring_buffer_empty())
		return -1;
	*pval = ring_buffer[ring_buffer_r];
	ring_buffer_r = NEXT_PLACE(ring_buffer_r);
	return 0;
}

#if test_1 == 1
static void vSenderTask(void *pvParameters)
{
	int i;
	int cnt_tx = 0;
	int cnt_ok = 0;
	int cnt_err = 0;
	char c;
	for(;;)
	{
		for(i = 0; i < 3;i++)
		{
			c = 'a'+ cnt_tx++;
			ring_buffer_put(c);

			if(xTaskNotifyGive(xRecvTask) == pdTRUE)
				printf("xTaskNotifyGive success %d time \r\n",cnt_ok++);
			else
				printf("xTaskNotifyGive fakl %d time \r\n",cnt_err++);
		}
		vTaskDelay(20);

	}
}
static void vReceiverTask(void *pvParameters)
{
	int cnt_ok = 0;
	uint8_t c;
	int notify_val;
	
	for( ;; )
	{
		notify_val = ulTaskNotifyTake(pdTRUE, portMAX_DELAY);

		printf("ulTaskNotifyTake success %d time notify_val = %d\r\n ",cnt_ok,notify_val);
		
		while(notify_val--)
		{
			ring_buffer_get(&c);
			printf("%c ",c);
		}
		printf("\r\n");
	}
}

#elif test_2 == 1
static void vSenderTask(void *pvParameters)
{
	int i;
	int cnt_tx = 0;
	int cnt_ok = 0;
	int cnt_err = 0;
	char c;
	for(;;)
	{
        for(i = 0; i <3; i++)
        {
            c = 'a' + cnt_tx;
            cnt_tx++;
            if( xTaskNotify(xRecvTask,(uint32_t)c,eSetValueWithoutOverwrite) == pdTRUE )
                printf("xTaskNotify success %d time send data %c \r\n",cnt_ok++,c);
            else
                printf("xTaskNotify fail %d time fail data %c\r\n",cnt_err++,c);

        }
		vTaskDelay(20);

	}
}
static void vReceiverTask(void *pvParameters)
{
	int cnt_ok = 0;
	uint32_t pulNotificationValue;
	BaseType_t xResult;

	for( ;; )
	{
		xResult = xTaskNotifyWait(0,0,&pulNotificationValue,portMAX_DELAY);
        
        if(xResult == pdTRUE)
            printf("xTaskNotifyWait success %d data %c \r\n",cnt_ok++,(uint8_t)pulNotificationValue);
	}
}

#elif test_3 == 1

#elif test_4 == 1

#elif test_5 == 1

#endif
int main(void)
{	
	
	SysTick_Init(72);
    USART_Config();  


    if(1)
    {
        #if test_1 == 1 
			/* 创建1个任务用于发送任务通知
	 		* 优先级为2
	 		*/
			xTaskCreate( vSenderTask, "Sender", 1000, NULL, 2, NULL );

			/* 创建1个任务用于接收任务通知
			* 优先级为1
			*/
			xTaskCreate( vReceiverTask, "Receiver", 1000, NULL, 1, &xRecvTask );
      	#elif test_2 == 1
            xTaskCreate( vSenderTask, "Sender", 1000, NULL, 2, NULL );
	        xTaskCreate( vReceiverTask, "Receiver", 1000, NULL, 1, &xRecvTask );
        #elif test_3  == 1

        #elif test_4 == 1
			
		#elif test_5 == 1
			
        #endif
    }
    else
        printf("xEventGroup failed\r\n");
    vTaskStartScheduler();  
	for (;;)
	{
	}
		
}





/* ------------------------------------------end of file---------------------------------------- */

freertos_第69张图片

软件定时器(software timer)

#define configUSE_TIMERS				            1  

freertos_第70张图片

freertos_第71张图片

软件定时器的特性

freertos_第72张图片
freertos_第73张图片

软件定时器的上下文


守护任务
要理解软件定时器API函数的参数,特别是里面的xTicksToWait,需要知道定时器执行的过程
FreeRTOS中有一个Tick中断,软件定时器基于Tick来运行。在哪里执行定时器函数?第一印象就是在Tick中断里执行:

  • 在Tick中断中判断定时器是否超时

  • 如果超时了,调用它的回调函数

FreeRTOS是RTOS,它不允许内核、在中断中执行不确定的代码:如果定时器函数很耗时,会影响整个系统。
所以,FreeRTOS中,不在Tick中断中执行定时器函数。

在任务中执行定时器,这个任务就是:RTOS Damemon Task,RTOS守护任务。以前被称为"Timer server",但是这个任务要做并不仅仅是定时器相关,所以改名为:RTOS Damemon Task。

当FreeRTOS的配置项configUSE_TIMERS被设置为1时,在启动调度器时,会自动创建RTOS Damemon Task。
我们自己编写的任务函数要使用定时器时,是通过"定时器命令队列"(timer command queue)和守护任务交互
freertos_第74张图片
守护任务的优先级为:configTIMER_TASK_PRIORITY;定时器命令队列的长度为configTIMER_QUEUE_LENGTH
freertos_第75张图片


守护任务的调度
守护任务的调度,跟普通的任务并无差别。当守护任务是当前优先级最高的就绪态任务时,它就可以运行。它的工作有两类:

  • 处理命令:从命令队列里取出命令、处理
  • 执行定时器的回调函数
    能否及时处理定时器的命令、能否及时执行定时器的回调函数,严重依赖于守护任务的优先级
    freertos_第76张图片
    freertos_第77张图片

回调函数

定时器的回调函数的原型

void ATimerCallback( TimerHandle_t xTimer );

定时器的回调函数是在守护任务中被调用的,守护任务不是专为某个定时器服务的,它还要处理其他定时器。

所以,定时器的回调函数不要影响其他人:

  • 回调函数要尽快实行,定时器的回调函数是在守护任务中被调用的,守护任务不是专为某个定时器服务的,它还要处理其他定时器。

所以,定时器的回调函数不要影响其他人:

回调函数要尽快实行,不能进入阻塞状态

不要调用会导致阻塞的API函数,比如vTaskDelay()

可以调用xQueueReceive()之类的函数,但是超时时间要设为0:即刻返回,不可阻塞

  • **不要定时器的回调函数是在守护任务中被调用的,守护任务不是专为某个定时器服务的,它还要处理其他定时器。

所以,定时器的回调函数不要影响其他人:

回调函数要尽快实行,不能进入阻塞状态

不要调用会导致阻塞的API函数,比如vTaskDelay()

可以调用xQueueReceive()之类的函数,但是超时时间要设为0:即刻返回,不可阻塞调用会导致阻塞的定时器的回调函数是在守护任务中被调用的,守护任务不是专为某个定时器服务的,它还要处理其他定时器。

所以,定时器的回调函数不要影响其他人:

回调函数要尽快实行,不能进入阻塞状态

不要调用会导致阻塞的API函数,比如vTaskDelay()

可以调用xQueueReceive()之类的函数,但是超时时间要设为0:即刻返回,不可阻塞API函数**,定时器的回调函数是在守护任务中被调用的,守护任务不是专为某个定时器服务的,它还要处理其他定时器。

所以,定时器的回调函数不要影响其他人:

  • 回调函数要尽快实行,不能进入阻塞状态

  • 不要调用会导致阻塞的API函数,比如vTaskDelay()

  • 可以调用xQueueReceive()之类的函数,但是超时时间要设为0:即刻返回,不可阻塞

软件定时器的函数

freertos_第78张图片


创建定时器

TimerHandle_t xTimerCreate( const char * const pcTimerName, 
							const TickType_t xTimerPeriodInTicks,
							const UBaseType_t uxAutoReload,
							void * const pvTimerID,
							TimerCallbackFunction_t pxCallbackFunction );
 
 使用动态分配内存的方法创建定时器
 pcTimerName:					定时器名字, 用处不大, 只在调试时用到
 xTimerPeriodInTicks: 			周期, 以Tick为单位
 uxAutoReload: 					类型, pdTRUE表示自动加载, pdFALSE表示一次性
 pvTimerID: 					回调函数可以使用此参数, 比如分辨是哪个定时器
 pxCallbackFunction: 			回调函数
 返回值: 成功则返回TimerHandle_t, 否则返回NULL
TimerHandle_t xTimerCreateStatic(const char * const pcTimerName,
                                 TickType_t xTimerPeriodInTicks,
                                 UBaseType_t uxAutoReload,
                                 void * pvTimerID,
                                 TimerCallbackFunction_t pxCallbackFunction,
                                 StaticTimer_t *pxTimerBuffer );
使用静态分配内存的方法创建定时器
  pcTimerName:					定时器名字, 用处不大, 只在调试时用到
  xTimerPeriodInTicks: 			周期, 以Tick为单位
  uxAutoReload: 				类型, pdTRUE表示自动加载, pdFALSE表示一次性
  pvTimerID: 					回调函数可以使用此参数, 比如分辨是哪个定时器
  pxCallbackFunction: 			回调函数
  pxTimerBuffer: 				传入一个StaticTimer_t结构体, 将在上面构造定时器
  返回值: 						成功则返回TimerHandle_t, 否则返回NULL

回调函数的类型是:

void ATimerCallback( TimerHandle_t xTimer );

typedef void (* TimerCallbackFunction_t)( TimerHandle_t xTimer );

删除
定时器的很多API函数,都是通过发送"命令"到命令队列,由守护任务来实现。

如果队列满了,"命令"就无法即刻写入队列。我们可以指定一个超时时间xTicksToWait,等待一会。

BaseType_t xTimerDelete( TimerHandle_t xTimer, TickType_t xTicksToWait );

删除定时器
xTimer: 			要删除哪个定时器
xTicksToWait: 		超时时间
返回值: 			pdFAIL表示"删除命令"在xTicksToWait个Tick内无法写入队列
       				pdPASS表示成功


启动/停止
启动定时器就是设置它的状态为运行态(Running、Active)。
停止定时器就是设置它的状态为冬眠(Dormant),让它不能运行。

BaseType_t xTimerStart( TimerHandle_t xTimer, TickType_t xTicksToWait );

启动定时器
xTimer: 				哪个定时器

xTicksToWait: 			超时时间

返回值: 				pdFAIL表示"启动命令"在xTicksToWait个Tick内无法写入队列
       					pdPASS表示成功
BaseType_t xTimerStartFromISR(   TimerHandle_t xTimer,
                                 BaseType_t *pxHigherPriorityTaskWoken );

启动定时器(ISR版本)

xTimer: 							哪个定时器

pxHigherPriorityTaskWoken: 			向队列发出命令使得守护任务被唤醒,
		                           如果守护任务的优先级比当前任务的高,"*pxHigherPriorityTaskWoken = pdTRUE",
		                           表示需要进行任务调度

返回值:								pdFAIL表示"启动命令"无法写入队列
       								pdPASS表示成功


BaseType_t xTimerStop( TimerHandle_t xTimer, TickType_t xTicksToWait );

停止定时器

xTimer: 					哪个定时器

xTicksToWait: 				超时时间

返回值: 					pdFAIL表示"停止命令"在xTicksToWait个Tick内无法写入队列
       						pdPASS表示成功

BaseType_t xTimerStopFromISR(    TimerHandle_t xTimer,
                                 BaseType_t *pxHigherPriorityTaskWoken );
停止定时器(ISR版本)

xTimer: 						哪个定时器

pxHigherPriorityTaskWoken: 		向队列发出命令使得守护任务被唤醒,
								如果守护任务的优先级比当前任务的高,"*pxHigherPriorityTaskWoken = pdTRUE",
								表示需要进行任务调度

返回值: 						pdFAIL表示"停止命令"无法写入队列
								pdPASS表示成功
								 

注意:这些函数的xTicksToWait表示的是,把命令写入命令队列的超时时间。命令队列可能已经满了,无法马上把命令写入队列里,可以等待一会。

xTicksToWait是定时器本身的超时时间,不是定时器本身的"周期。

创建定时器时,设置了它的周期(period)。xTimerStart()函数是用来启动定时器。假设调用xTimerStart()的时刻是tX,定时器的周期是n,那么在tX+n时刻定时器的回调函数被调用。

如果定时器已经被启动,但是它的函数尚未被执行,再次执行xTimerStart()函数相当于执行xTimerReset(),重新设定它的启动时间。


复位
从定时器的状态转换图可以知道,使用xTimerReset()函数可以让定时器的状态从冬眠态转换为运行态,相当于使用xTimerStart()函数。

如果定时器已经处于运行态,使用xTimerReset()函数就相当于重新确定超时时间。假设调用xTimerReset()的时刻是tX,定时器的周期是n,那么tX+n就是重新确定的超时时间。

BaseType_t xTimerReset( TimerHandle_t xTimer, TickType_t xTicksToWait );
复位定时器
xTimer: 		哪个定时器
xTicksToWait: 	超时时间
返回值: 		pdFAIL表示"复位命令"在xTicksToWait个Tick内无法写入队列
       			pdPASS表示成功
      
BaseType_t xTimerResetFromISR(   TimerHandle_t xTimer,
                                 BaseType_t *pxHigherPriorityTaskWoken );
复位定时器(ISR版本)

xTimer: 						哪个定时器

pxHigherPriorityTaskWoken: 		向队列发出命令使得守护任务被唤醒,
							   如果守护任务的优先级比当前任务的高,"*pxHigherPriorityTaskWoken = pdTRUE",
								表示需要进行任务调度

返回值: 						pdFAIL表示"停止命令"无法写入队列
								pdPASS表示成功
							


修改周期
从定时器的状态转换图可以知道,使用xTimerChangePeriod()函数,处理能修改它的周期外,还可以让定时器的状态从冬眠态转换为运行态。
修改定时器的周期时,会使用新的周期重新计算它的超时时间。假设调用xTimerChangePeriod()函数的时间tX,新的周期是n,则tX+n就是新的超时时间。

BaseType_t xTimerChangePeriod(   TimerHandle_t xTimer,
                                 TickType_t xNewPeriod,
                                 TickType_t xTicksToWait );
修改定时器的周期
xTimer: 						哪个定时器

xNewPeriod: 					新周期

xTicksToWait: 					超时时间, 命令写入队列的超时时间 

返回值: 						pdFAIL表示"修改周期命令"在xTicksToWait个Tick内无法写入队列
								pdPASS表示成功

BaseType_t xTimerChangePeriodFromISR( TimerHandle_t xTimer,
                                      TickType_t xNewPeriod,
                                      BaseType_t *pxHigherPriorityTaskWoken );
修改定时器的周期

xTimer: 							哪个定时器

xNewPeriod: 						新周期

pxHigherPriorityTaskWoken: 			向队列发出命令使得守护任务被唤醒,
									如果守护任务的优先级比当前任务的高,"*pxHigherPriorityTaskWoken = pdTRUE",
									表示需要进行任务调度

返回值: 							pdFAIL表示"修改周期命令"在xTicksToWait个Tick内无法写入队列
									pdPASS表示成功

定时器ID
freertos_第79张图片
怎么使用定时器ID,完全由程序来决定:

  • 可以用来标记定时器,表示自己是什么定时器

  • 可以用来保存参数,给回调函数使用

它的初始值在创建定时器时由xTimerCreate()这类函数传入,后续可以使用这些函数来操作:

  • 更新ID:使用vTimerSetTimerID()函数

  • 查询ID:查询pvTimerGetTimerID()函数

这两个函数不涉及命令队列,它们是直接操作定时器结构体。

/* 获得定时器的ID
 * xTimer: 哪个定时器
 * 返回值: 定时器的ID
 */
void *pvTimerGetTimerID( TimerHandle_t xTimer );

/* 设置定时器的ID
 * xTimer: 哪个定时器
 * pvNewID: 新ID
 * 返回值: 无
 */
void vTimerSetTimerID( TimerHandle_t xTimer, void *pvNewID );

定时器使用

#if test_1 == 1
static void vONEShotTimerFunc( TimerHandle_t xTimer )
{
	static int cnt = 0;
	printf("run vONEShotTimerFunc %d\r\n", cnt++);
}

static void vAutoLoadTimerFunc( TimerHandle_t xTimer )
{
	static int cnt = 0;
	printf("run vAutoLoadTimerFunc %d\r\n", cnt++);
}
#elif test_2 == 1
static void vSenderTask(void *pvParameters)
{
	
	for(;;)
	{
       

	}
}
static void vReceiverTask(void *pvParameters)
{
	
	for( ;; )
	{
	}
}

#elif test_3 == 1

#elif test_4 == 1

#elif test_5 == 1

#endif

#define mainONE_SHOT_TIMER_PERIOD 		( 50 )
#define mainAUTO_RELOAD_TIMER_PERIOD 	( 60 )

int main(void)
{	
	
	SysTick_Init(72);
    USART_Config();  
	
	TimerHandle_t xOneShotTimer;
	TimerHandle_t xAutoReloadTimer;

    if(1)
    {
        #if test_1 == 1 
 			xOneShotTimer  = xTimerCreate("OneShot",mainONE_SHOT_TIMER_PERIOD,pdFALSE,0,vONEShotTimerFunc);
			xAutoReloadTimer  = xTimerCreate("OneShot",mainAUTO_RELOAD_TIMER_PERIOD,pdTRUE,0,vAutoLoadTimerFunc);
            if (xOneShotTimer && xAutoReloadTimer)
            {
                /* 启动定时器 */
                xTimerStart(xOneShotTimer, 0);
                xTimerStart(xAutoReloadTimer, 0);
            }
        #elif test_2 == 1
           
        #elif test_3  == 1

        #elif test_4 == 1
			
		#elif test_5 == 1
			
        #endif
    }
    else
        printf("xEventGroup failed\r\n");
    vTaskStartScheduler();  
	for (;;)
	{
	}
		
}

freertos_第80张图片

中断管理(Interrupt Management)

freertos_第81张图片

两套API函数

为什么需要两套API?
freertos_第82张图片

  • 很多API函数会导致任务计入阻塞状态:

    • 运行这个函数的任务进入阻塞状态

    • 比如写队列时,如果队列已满,可以进入阻塞状态等待一会

  • ISR调用API函数时,ISR不是"任务",ISR不能进入阻塞状态

  • 所以,在任务中、在ISR中,这些函数的功能是有差别的
    freertos_第83张图片
    FreeRTOS使用两套函数,而不是使用一套函数,是因为有如下好处:

  • 使用同一套函数的话,需要增加额外的判断代码、增加额外的分支,是的函数更长、更复杂、难以测试

  • 在任务、ISR中调用时,需要的参数不一样,比如:

    • 在任务中调用:需要指定超时时间,表示如果不成功就阻塞一会

    • 在ISR中调用:不需要指定超时时间,无论是否成功都要即刻返回

    • 如果强行把两套函数揉在一起,会导致参数臃肿、无效

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

  • 有些处理器架构没有办法轻易分辨当前是处于任务中,还是处于ISR中,就需要额外添加更多、更复杂的代码


使用两套函数可以让程序更高效,但是也有一些缺点,比如你要使用第三方库函数时,即会在任务中调用它,也会在ISR总调用它。这个第三方库函数用到了FreeRTOS的API函数,你无法修改库函数。这个问题可以解决:

  • 把中断的处理推迟到任务中进行(Defer interrupt processing),在任务中调用库函数

  • 尝试在库函数中使用"FromISR"函数:

    • 在任务中、在ISR中都可以调用"FromISR"函数

    • 反过来就不行,非FromISR函数无法在ISR中使用

  • 第三方库函数也许会提供OS抽象层,自行判断当前环境是在任务还是在ISR中,分别调用不同的函数

两套API函数列表

类型 在任务中 在ISR中
队列(queue) xQueueSendToBack xQueueSendToBackFromISR
xQueueSendToBack xQueueSendToBackFromISR
xQueueSendToFront xQueueSendToFrontFromISR
xQueueReceive xQueueReceiveFromISR
xQueueOverwrite xQueueOverwriteFromISR
xQueuePeek xQueuePeekFromISR
信号量(semaphore) xSemaphoreGive xSemaphoreGiveFromISR
xSemaphoreTake xSemaphoreTakeFromISR
事件组(event group) xEventGroupSetBits xEventGroupSetBitsFromISR
xEventGroupGetBits xEventGroupGetBitsFromISR
任务通知(task notification) xTaskNotifyGive xTaskNotifyGiveFromISR
xTaskNotify xTaskNotifyFromISR
软件定时器(software timer) xTimerStart xTimerStartFromISR
xTimerStop xTimerStopFromISR
xTimerReset xTimerResetFromISR
xTimerChangePeriod xTimerChangePeriodFromISR

xHigherPriorityTaskWoken参数

xHigherPriorityTaskWoken的含义是:是否有更高优先级的任务被唤醒了。如果为pdTRUE,则意味着后面要进行任务切换。
以写队列为例:
任务A调用xQueueSendToBack()写队列,有几种情况发生:

  • 队列满了,任务A阻塞等待,另一个任务B运行

  • 队列没满,任务A成功写入队列,但是它导致另一个任务B被唤醒,任务B的优先级更高:任务B先运行

  • 队列没满,任务A成功写入队列,即刻返回

可以看到,在任务中调用API函数可能导致任务阻塞、任务切换,这叫做"context switch",上下文(什么是上下文)切换。这个函数可能很长时间才返回,在函数的内部实现了任务切换。


xQueueSendToBackFromISR()函数也可能导致任务切换,但是不会在函数内部进行切换,而是返回一个参数:表示是否需要切换,函数原型与用法如下:

/* 
 * 往队列尾部写入数据,此函数可以在中断函数中使用,不可阻塞
 */
BaseType_t xQueueSendToBackFromISR(
                                      QueueHandle_t xQueue,
                                      const void *pvItemToQueue,
                                      BaseType_t *pxHigherPriorityTaskWoken
                                   );

/* 用法示例 */

BaseType_t xHigherPriorityTaskWoken = pdFALSE;
xQueueSendToBackFromISR(xQueue, pvItemToQueue, &xHigherPriorityTaskWoken);

if (xHigherPriorityTaskWoken == pdTRUE)
{
    /* 任务切换 */    
}

pxHigherPriorityTaskWoken参数,就是用来保存函数的结果:是否需要切换

*pxHigherPriorityTaskWoken等于pdTRUE:函数的操作导致更高优先级的任务就绪了,ISR应该进行任务切换

*pxHigherPriorityTaskWoken等于pdFALSE:没有进行任务切换的必要

freertos_第84张图片
ISR中有可能多次调用"FromISR"函数,如果在"FromISR"内部进行任务切换,会浪费时间。解决方法是:

  • 在"FromISR"中标记是否需要切换

  • 在ISR返回之前再进行任务切换

  • 示例代码如下

void XXX_ISR()
{
    int i;
    BaseType_t xHigherPriorityTaskWoken = pdFALSE;
    
    for (i = 0; i < N; i++)
    {
        xQueueSendToBackFromISR(..., &xHigherPriorityTaskWoken); /* 被多次调用 */
    }
	
    /* 最后再决定是否进行任务切换 */
    if (xHigherPriorityTaskWoken == pdTRUE)
	{
    	/* 任务切换 */    
	}
}

上述的例子很常见,比如UART中断:在UART的ISR中读取多个字符,发现收到回车符时才进行任务切换。

在ISR中调用API时不进行任务切换,而只是在"xHigherPriorityTaskWoken"中标记一下,除了效率,还有多种好处:

  • 效率高:避免不必要的任务切换

  • 让ISR更可控:中断随机产生,在API中进行任务切换的话,可能导致问题更复杂

  • 可移植性

  • 在Tick中断中,调用vApplicationTickHook():它运行与ISR,只能使用"FromISR"的函数


使用"FromISR"函数时,如果不想使用xHigherPriorityTaskWoken参数,可以设置为NULL。


怎么切换任务
FreeRTOS的ISR函数中,使用两个宏进行任务切换:

portEND_SWITCHING_ISR( xHigherPriorityTaskWoken );//使用汇编实现

portYIELD_FROM_ISR( xHigherPriorityTaskWoken );//使用C语言实现
新版本都统一使用portYIELD_FROM_ISR

使用示例如下:

void XXX_ISR()
{
    int i;
    BaseType_t xHigherPriorityTaskWoken = pdFALSE;
    
    for (i = 0; i < N; i++)
    {
        xQueueSendToBackFromISR(..., &xHigherPriorityTaskWoken); /* 被多次调用 */
    }
	
    /* 最后再决定是否进行任务切换 
     * xHigherPriorityTaskWoken为pdTRUE时才切换
     */
    portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
}

freertos_第85张图片

中断的延迟处理

freertos_第86张图片

freertos_第87张图片

中断与任务间的通信

前面讲解过的队列、信号量、互斥量、事件组、任务通知等等方法,都可使用。

要注意的是,在ISR中使用的函数要有"FromISR"后缀。

资源管理(Resource Management)

引入两个功能:屏蔽/使能中断、暂停/恢复调度器。

要独占式地访问临界资源,有3种方法:

  • 公平竞争:比如使用互斥量,谁先获得互斥量谁就访问临界资源,这部分内容前面讲过。

  • 谁要跟我抢,我就灭掉谁:

    • 中断要跟我抢?我屏蔽中断

    • 其他任务要跟我抢?我禁止调度器,不运行任务切换

屏蔽中断

屏蔽中断有两套宏:任务中使用、ISR中使用:

任务中使用:taskENTER_CRITICA()/taskEXIT_CRITICAL()

ISR中使用:taskENTER_CRITICAL_FROM_ISR()/taskEXIT_CRITICAL_FROM_ISR()

在任务中屏蔽中断

 {
	 /* 在任务中,当前时刻中断是使能的
	 * 执行这句代码后,屏蔽中断
	 */
 	taskENTER_CRITICAL();

	{
		/* 访问临界资源 */
	}

	/* 重新使能中断 */
	taskEXIT_CRITICAL();
 }

在taskENTER_CRITICA()/taskEXIT_CRITICAL()之间:

  • 低优先级的中断被屏蔽了:优先级低于、等于configMAX_SYSCALL_INTERRUPT_PRIORITY

  • 高优先级的中断可以产生:优先级高于configMAX_SYSCALL_INTERRUPT_PRIORITY

    • 但是,这些中断ISR里,不允许使用FreeRTOS的API函数
  • 任务调度依赖于中断、依赖于API函数,所以:这两段代码之间,不会有任务调度产生

这套taskENTER_CRITICA()/taskEXIT_CRITICAL()宏,是可以递归使用的,它的内部会记录嵌套的深度,只有嵌套深度变为0时,调用taskEXIT_CRITICAL()才会重新使能中断。(递归调用一定得递归释放)

使用taskENTER_CRITICA()/taskEXIT_CRITICAL()来访问临界资源是很粗鲁的方法:

  • 中断无法正常运行

  • 任务调度无法进行

  • 所以,之间的代码要尽可能快速地执行


在ISR中屏蔽中断

void vAnInterruptServiceRoutine( void )
{
    /* 用来记录当前中断是否使能 */
    UBaseType_t uxSavedInterruptStatus;
    
    /* 在ISR中,当前时刻中断可能是使能的,也可能是禁止的
     * 所以要记录当前状态, 后面要恢复为原先的状态
     * 执行这句代码后,屏蔽中断
     */
    uxSavedInterruptStatus = taskENTER_CRITICAL_FROM_ISR();
    
    /* 访问临界资源 */

    /* 恢复中断状态 */
    taskEXIT_CRITICAL_FROM_ISR( uxSavedInterruptStatus );
    /* 现在,当前ISR可以被更高优先级的中断打断了 */
}

在taskENTER_CRITICA_FROM_ISR()/taskEXIT_CRITICAL_FROM_ISR()之间:

  • 低优先级的中断被屏蔽了:优先级低于、等于configMAX_SYSCALL_INTERRUPT_PRIORITY

  • 高优先级的中断可以产生:优先级高于configMAX_SYSCALL_INTERRUPT_PRIORITY

    • 但是,这些中断ISR里,不允许使用FreeRTOS的API函数
  • 任务调度依赖于中断、依赖于API函数,所以:这两段代码之间,不会有任务调度产生

暂停调度器

如果有别的任务来跟你竞争临界资源,你可以把中断关掉:这当然可以禁止别的任务运行,但是这代价太大了。它会影响到中断的处理。

如果只是禁止别的任务来跟你竞争,不需要关中断,暂停调度器就可以了:在这期间,中断还是可以发生、处理。

/* 暂停调度器 */
void vTaskSuspendAll( void );

/* 恢复调度器
 * 返回值: pdTRUE表示在暂定期间有更高优先级的任务就绪了
 *        可以不理会这个返回值
 */
BaseType_t xTaskResumeAll( void );

你可能感兴趣的:(rtos,rtos)