零、FreeRTOS笔记

1.xTaskCreate 

FreeRTOS.h
#ifndef configSUPPORT_DYNAMIC_ALLOCATION
    /* Defaults to 1 for backward compatibility. */
    #define configSUPPORT_DYNAMIC_ALLOCATION    1
#endif

tasks.c
#if ( configSUPPORT_DYNAMIC_ALLOCATION == 1 )

    BaseType_t xTaskCreate( TaskFunction_t pxTaskCode,
                            const char * const pcName, /*lint !e971 Unqualified char types are allowed for strings and single characters only. */
                            const configSTACK_DEPTH_TYPE usStackDepth,
                            void * const pvParameters,
                            UBaseType_t uxPriority,
                            TaskHandle_t * const pxCreatedTask )
    {
        TCB_t * pxNewTCB;
        BaseType_t xReturn;

        /* If the stack grows down then allocate the stack then the TCB so the stack
         * does not grow into the TCB.  Likewise if the stack grows up then allocate
         * the TCB then the stack. */
        #if ( portSTACK_GROWTH > 0 )
            {
                /* Allocate space for the TCB.  Where the memory comes from depends on
                 * the implementation of the port malloc function and whether or not static
                 * allocation is being used. */
                pxNewTCB = ( TCB_t * ) pvPortMalloc( sizeof( TCB_t ) );

                if( pxNewTCB != NULL )
                {
                    /* Allocate space for the stack used by the task being created.
                     * The base of the stack memory stored in the TCB so the task can
                     * be deleted later if required. */
                    pxNewTCB->pxStack = ( StackType_t * ) pvPortMallocStack( ( ( ( size_t ) usStackDepth ) * sizeof( StackType_t ) ) ); /*lint !e961 MISRA exception as the casts are only redundant for some ports. */

                    if( pxNewTCB->pxStack == NULL )
                    {
                        /* Could not allocate the stack.  Delete the allocated TCB. */
                        vPortFree( pxNewTCB );
                        pxNewTCB = NULL;
                    }
                }
            }
        #else /* portSTACK_GROWTH */
            {
                StackType_t * pxStack;

                /* Allocate space for the stack used by the task being created. */
                pxStack = pvPortMallocStack( ( ( ( size_t ) usStackDepth ) * sizeof( StackType_t ) ) ); /*lint !e9079 All values returned by pvPortMalloc() have at least the alignment required by the MCU's stack and this allocation is the stack. */

                if( pxStack != NULL )
                {
                    /* Allocate space for the TCB. */
                    pxNewTCB = ( TCB_t * ) pvPortMalloc( sizeof( TCB_t ) ); /*lint !e9087 !e9079 All values returned by pvPortMalloc() have at least the alignment required by the MCU's stack, and the first member of TCB_t is always a pointer to the task's stack. */

                    if( pxNewTCB != NULL )
                    {
                        /* Store the stack location in the TCB. */
                        pxNewTCB->pxStack = pxStack;
                    }
                    else
                    {
                        /* The stack cannot be used as the TCB was not created.  Free
                         * it again. */
                        vPortFreeStack( pxStack );
                    }
                }
                else
                {
                    pxNewTCB = NULL;
                }
            }
        #endif /* portSTACK_GROWTH */

        if( pxNewTCB != NULL )
        {
            #if ( tskSTATIC_AND_DYNAMIC_ALLOCATION_POSSIBLE != 0 ) /*lint !e9029 !e731 Macro has been consolidated for readability reasons. */
                {
                    /* Tasks can be created statically or dynamically, so note this
                     * task was created dynamically in case it is later deleted. */
                    pxNewTCB->ucStaticallyAllocated = tskDYNAMICALLY_ALLOCATED_STACK_AND_TCB;
                }
            #endif /* tskSTATIC_AND_DYNAMIC_ALLOCATION_POSSIBLE */

            prvInitialiseNewTask( pxTaskCode, pcName, ( uint32_t ) usStackDepth, pvParameters, uxPriority, pxCreatedTask, pxNewTCB, NULL );
            prvAddNewTaskToReadyList( pxNewTCB );
            xReturn = pdPASS;
        }
        else
        {
            xReturn = errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY;
        }

        return xReturn;
    }

#endif /* configSUPPORT_DYNAMIC_ALLOCATION */

2.调度机制:在freertos中,高优先级的任务先运行,同等任务交替执行。

3.FreeRTOS在xTaskCreate中分配TCB和栈,相对的就是在vTaskDelete中释放TCB和栈。

回答:X,不是在vTaskDelete中释放TCB和栈,而是在空闲任务中进行这些清理工作。

可以做个实验,连续不断的调用xTaskCreate、vTaskDelete,最终导致内存耗光。

4.xTaskCreake创建任务时,分配了100*4字节的。但是在Task1中,在栈区开了一个500字节缓冲区,这样做会冲掉Task1分配的栈区,导致运行结果异常。

4.使用Keil模拟器中的逻辑分析仪时,时间不准确,怎么解决?

A、确认代码中设置时钟的频率

B、确认Options中Xtal的频率

5.为什么后面创建的任务反而先运行?

A、后面的任务插入链表时,pxCurrentTCB执行它

6.使用vTaskDelay时,如何延时若干毫秒?

A、自己把毫秒换算给Tick数

B、使用宏把毫秒换算为Tick数

7.使用Keil模拟器中的逻辑分析仪时,时间不准确,怎么解决?

A、确认代码中设置时钟的频率

B、确认Options中Xtal的频率

8.vTaskDelayUntil是老版本,它没有返回值,新版本怎么用?

A、vTaskDelayUntil是新版本

B、传入的参数是一样的

C、有返回值

9.在xTaskCreate中分配TCB和栈,也是在xTaskDelete中释放TCB和栈

A、不一定,对于自杀的任务,由空闲任务来清理内存。

10.在Keil中怎么重新使用模拟器运行代码?

A、Device中选择设备“STM32F103ZE”

B、Debug中选择“Use Simulator”

C、指定DLL:DARMSTM.DLL

D、指定参数:-pSTM32F103ZE

11.vTaskDelete函数是否清理被删除的任务,比如释放它的内存?

A、对于自杀的任务,由空闲任务来清理(自杀的任务,无法清理自己使用的内存:比如对于栈,释放栈的函数要用到栈,这时矛盾的)

B、对于被杀的任务,在vTaskDelete函数内部清理(凶手调用这个函数,由凶手清理)

12.任务调度算法——状态转化图

12.1 状态与事件

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

2. 非运行状态的任务,它处于这3中状态之一:

        阻塞(Blocked):等待某一个事件唤醒它。

        暂停(Suspended):单纯的在休息

        就绪(Ready)

3. 就绪态的任务,可以被调度器挑选出来切换为运行状态,调度器永远都是挑选最高优先级的就绪任务并让它进入运行状态。

4. 阻塞状态的任务,它在等待"事件",当事件发生时任务就会进入就绪状态。

4.1事件分为两类:

4.1.1 事件相关的事件

        a.所谓事件相关的事件,就是设置超时时间:在指定时间内阻塞,时间到了就进入就绪状态。

        b.使用时间相关的时间,可以实现周期性的功能,可以实现超时功能。

4.1.2 同步事件

        a.同步事件就是:某个任务在等待某些信息,别的任务或中断服务程序会给它发送信息。

        b.怎么发送“发送信息”?方法很多

                b.1 任务通知(task notification)

                b.2 队列

                b.3 事件组

                b.4 信号量

                b.5 互斥量

                b.6 这些方法用来发送同步信息,比如表示某个外设得到了数据。

12.2调度策略

  • 是否抢占?

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

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

13.不支持时间片轮转时,空闲任务什么时候才可以执行?

A、Task3调用vTaskDelay导致任务切换时

B、如果没有高优先级的Task3,空闲任务永远没有机会执行

不支持时间片轮转的话,当前任务何时放弃 CPU资源?

只有这两种情况:被高优先级任务抢占、自己主动放弃。

如果把Task3去掉,那么Task1会永远执行,所以空闲任务没有机会执行。

14.configIDLE_SHOULD_YIELD的实质是什么?

A、在空闲任务的循环中,主动触发调度器,让出CPU

B、空闲任务每循环一次,就调度taskYIELD()函数一次

C、不配置configIDLE_SHOULD_YIELD为0的话,空闲任务的while循环会执行多次,跟用户任务一样

15.什么叫异步?

A、你不知道它什么时候发生

B、中断是一种异步

C、FreeRTOS的任务没有实现异常

D、异步,类似于中断

以中断为例,发生中断时,CPU暂停当前程序去处理中断;异步属于任务层面的中断,发生异步事件时,任务暂停执行,处理事件后继续执行。FreeRTOS并未实现异步。

16.FreeRTOS思考

  • 任务切换的基础:tick中断

  • 怎么管理不同状态的任务:放在不同链表里

  • 一共4种状态,运行(running)、阻塞(Blocked)、暂停(Suspended)、就绪(Ready)

  • 就绪态的任务,可以被调度器挑选出来切换为运行状态,调度器永远都是挑选最高优先级的就绪态任务并让它进入运行状态。

  • 阻塞状态的任务,它在等待"事件",当事件发生时任务就会进入就绪状态。

  • 事件分为两类: 时间相关的事件、同步事件

    • 同步事件: 任务通知(task notification)、 队列(queue)、 事件组(event group)、 信号量(semaphoe)、 互斥量(mutex)等

  • 在FreeRTOS中,自己实现的代码,在使用同步、互斥、通讯的过程中,基本都会有缺陷,所以需要学习FreeRTOS的对上述问题的解决方案。

    • FreeRTOS的解决方案需要保证:

      • 正确性

      • 效率:等待者要进入阻塞状态

      • 多种解决方案:队列、事件组、信号量、任务通知、互斥量

  • 使用同步的缺陷

static int sum = 0;
static volatile int flagCalcEnd = 0;

void Task1Function(void * param)
{
	volatile int i = 0;
	while (1)
	{
		for (i = 0; i < 10000000; i++)
			sum++;
		flagCalcEnd = 1;
		vTaskDelete(NULL);
	}
}

void Task2Function(void * param)
{
	while (1)
	{
		if (flagCalcEnd)
			printf("sum = %d\r\n", sum);
	}
}

int main(void)
{
    xTaskCreate(Task1Function, "Task1", 100, NULL, 1, &xHandleTask1);
	xTaskCreate(Task2Function, "Task2", 100, NULL, 1, NULL);
}

/*
该程序的本意是想让Task1Functions计算sum,然后使用Task2Functions打印sum的值,在测试中,计算完印sum的值需要4s。
如果屏蔽掉//xTaskCreate(Task2Function, "Task2", 100, NULL, 1, NULL);在测试中,计算完印sum的值需要2s。

该程序的问题在于,task1在计算sum时,task2不停的和task1在竞争cpu资源,导致task1的计算花费太长的时间,自己通过设置flagCalcEnd标志位的方法,可以实现功能,但是太浪费cpu资源,所以有缺陷。
*/
  • 使用互斥的缺陷

static volatile int flagUARTused = 0;

void TaskGenericFunction(void * param)
{
	while (1)
	{
		if (!flagUARTused)
		{
			flagUARTused = 1;
			printf("%s\r\n", (char *)param);
			flagUARTused = 0;
			vTaskDelay(1);
		}
	}
}

int main( void )
{
	xTaskCreate(TaskGenericFunction, "Task3", 100, "Task 3 is running", 1, NULL);
	xTaskCreate(TaskGenericFunction, "Task4", 100, "Task 4 is running", 1, NULL);
}

/*
运行结果:
Task 3 is running
Task 4 is running
.......

但是,该程序在运行很长时间后,会出现bug,因为TaskGenericFunction该函数存在原子操作。
*/
  • 通讯过程的缺陷

使用全局变量进行通讯,导致局部变量满天飞......

17.把数据写到队列头部,会覆盖原来头部的数据吗?

不会覆盖原来的数据。队列中的数据使用环形缓冲区管理数据,把数据放到头部时,会先移动头部位置,并不会覆盖原来数据。

18.队列结构体中,除了buffer指针、两个链表,还有什么?

A、管理环形缓冲区的头、尾指针

B、队列中item的大小

C、其它辅助的成员

19.除了通过vTaskDelay让出CPU资源,还有没有更合理的函数?

A、使用taskYIELD(),主动发起一次任务切换

B、vTaskDelay会让任务阻塞、暂停若干tick,taskYIELD()更合理

C、可以设置不同的优先级来实现抢占

20.往队列A中写入N个数据,会导致写队列集多少次?

N次。

写队列A一次,就会写队列集一次;写队列A N次,就会写队列集N次。反过来,读队列集一次,得到了队列A,也只应该读队列A一次。

21.假设队列A、B、C使用队列集,有哪些要注意的地方?
A、写队列A N次,会导致写队列集N次,也就是队列集里有N个队列A的句柄。
B、读一次队列集返回一个队列后,只能读这个队列一次。
C、创建队列集时,它要管理队列ABC,那么队列集的长度=队列A长度+队列B长度+队列C长度

22.信号量的数值范围是?

A、创建信号量时就指定的最大值

B、最小值是0

23.有多个任务执行xSemaphoreTake,那么当其它任务执行xSemaphoreGive时,唤醒哪个任务?

A、优先级最高的任务

B、优先级相同时,等待最久的任务

RTOS实时性要求高,所以最高优先级的任务先被唤醒;大家优先级都一样的话,唤醒等待时间最长的任务。

24.使用队列也可以实现同步,为什么还要使用信号量?

A、使用队列可以传递数据,数据的保存需要空间

B、使用信号量时不需要传递数据,更节省空间

C、使用信号量时不需要复制数据,效率更高

25.configSUPPORT_DYNAMIC_ALLOCATION不需要手动定义,为什么?

A、如果你没有定义它,在配置文件李会把它配置为1

B、如果不需要动态分配内存的话,需要自己定义它为0

freeRTOS.h

#ifndef configSUPPORT_DYNAMIC_ALLOCATION
    /* Defaults to 1 for backward compatibility. */
    #define configSUPPORT_DYNAMIC_ALLOCATION    1
#endif

26.程序员怎么实现“谁上锁、就由谁解锁”?

A、上锁、解锁的代码成对出现

B、在临界代码中,不要解锁

C、只能依靠程序员的自觉

FreeRTOS的互斥量并没有实现“谁上锁、就由谁解锁”,只能开程序员的自觉了。

27.如果任务C的优先级比任务A的优先级还低,就不会发生优先级继承。

28.创建互斥量后,不需要Give一次。二进制信号量初始值是0,创建后需要Give一次;互斥量初始值是1,创建后不需要Give一次。

29.要使用互斥量,需要定义哪个配置项?

A、configUSE_MUTEXES

B、configSUPPORT_DYNAMIC_ALLOCATION

C、configSUPPORT_DYNAMIC_ALLOCATION不需要手动配置

FreeRTOS.h

#ifndef configSUPPORT_DYNAMIC_ALLOCATION
    /* Defaults to 1 for backward compatibility. */
    #define configSUPPORT_DYNAMIC_ALLOCATION    1
#endif

30.互斥量常规使用

static SemaphoreHandle_t xSemUART;

void TaskGenericFunction(void * param)
{
	while (1)
	{
		xSemaphoreTake(xSemUART, portMAX_DELAY);
		printf("%s\r\n", (char *)param);
		xSemaphoreGive(xSemUART);
		vTaskDelay(1);
	}
}

int main( void )
{	
	xSemUART = xSemaphoreCreateMutex();

	xTaskCreate(TaskGenericFunction, "Task3", 100, "Task 3 is running", 1, NULL);
	xTaskCreate(TaskGenericFunction, "Task4", 100, "Task 4 is running", 1, NULL);
}

/*
Task 4 is running
Task 3 is running
Task 4 is running
Task 3 is running
......
*/

30.二进制信号量,优先级反转,例子

static void vLPTask( void *pvParameters )
{
	const TickType_t xTicksToWait = pdMS_TO_TICKS( 10UL );	
	uint32_t i;
	char c = 'A';

	printf("LPTask start\r\n");
	
	/* 无限循环 */
	for( ;; )
	{	
		flagLPTaskRun = 1;
		flagMPTaskRun = 0;
		flagHPTaskRun = 0;

		/* 获得互斥量/二进制信号量 */
		xSemaphoreTake(xLock, portMAX_DELAY);
		
		/* 耗时很久 */
		
		printf("LPTask take the Lock for long time");
		for (i = 0; i < 500; i++) 
		{
			flagLPTaskRun = 1;
			flagMPTaskRun = 0;
			flagHPTaskRun = 0;
			printf("%c", c + i);
		}
		printf("\r\n");
		
		/* 释放互斥量/二进制信号量 */
		xSemaphoreGive(xLock);
		
		vTaskDelay(xTicksToWait);
	}
}

static void vMPTask( void *pvParameters )
{
	const TickType_t xTicksToWait = pdMS_TO_TICKS( 30UL );	

	flagLPTaskRun = 0;
	flagMPTaskRun = 1;
	flagHPTaskRun = 0;

	printf("MPTask start\r\n");
	
	/* 让LPTask、HPTask先运行 */	
	vTaskDelay(xTicksToWait);
	
	/* 无限循环 */
	for( ;; )
	{	
		flagLPTaskRun = 0;
		flagMPTaskRun = 1;
		flagHPTaskRun = 0;
	}
}

static void vHPTask( void *pvParameters )
{
	const TickType_t xTicksToWait = pdMS_TO_TICKS( 10UL );	

	flagLPTaskRun = 0;
	flagMPTaskRun = 0;
	flagHPTaskRun = 1;

	printf("HPTask start\r\n");
	
	/* 让LPTask先运行 */	
	vTaskDelay(xTicksToWait);
	
	/* 无限循环 */
	for( ;; )
	{	
		flagLPTaskRun = 0;
		flagMPTaskRun = 0;
		flagHPTaskRun = 1;
		printf("HPTask wait for Lock\r\n");
		
		/* 获得互斥量/二进制信号量 */
		xSemaphoreTake(xLock, portMAX_DELAY);
		
		flagLPTaskRun = 0;
		flagMPTaskRun = 0;
		flagHPTaskRun = 1;
		
		/* 释放互斥量/二进制信号量 */
		xSemaphoreGive(xLock);
	}
}


/* 互斥量/二进制信号量句柄 */
SemaphoreHandle_t xLock;

int main( void )
{	
    /* 创建互斥量/二进制信号量 */
    xLock = xSemaphoreCreateBinary( );
	xSemaphoreGive(xLock);

	if( xLock != NULL )
	{
		/* 创建3个任务: LP,MP,HP(低/中/高优先级任务)
		 */
		xTaskCreate( vLPTask, "LPTask", 1000, NULL, 1, NULL );
		xTaskCreate( vMPTask, "MPTask", 1000, NULL, 2, NULL );
		xTaskCreate( vHPTask, "HPTask", 1000, NULL, 3, NULL );
	}
}

    /* 二进制信号量 */
    xLock = xSemaphoreCreateBinary( );
    xSemaphoreGive(xLock);

31.互斥量,实现优先级继承,例子

static void vLPTask( void *pvParameters )
{
	const TickType_t xTicksToWait = pdMS_TO_TICKS( 10UL );	
	uint32_t i;
	char c = 'A';

	printf("LPTask start\r\n");
	
	/* 无限循环 */
	for( ;; )
	{	
		flagLPTaskRun = 1;
		flagMPTaskRun = 0;
		flagHPTaskRun = 0;

		/* 获得互斥量/二进制信号量 */
		xSemaphoreTake(xLock, portMAX_DELAY);
		
		/* 耗时很久 */
		
		printf("LPTask take the Lock for long time");
		for (i = 0; i < 500; i++) 
		{
			flagLPTaskRun = 1;
			flagMPTaskRun = 0;
			flagHPTaskRun = 0;
			printf("%c", c + i);
		}
		printf("\r\n");
		
		/* 释放互斥量/二进制信号量 */
		xSemaphoreGive(xLock);
		
		vTaskDelay(xTicksToWait);
	}
}

static void vMPTask( void *pvParameters )
{
	const TickType_t xTicksToWait = pdMS_TO_TICKS( 30UL );	

	flagLPTaskRun = 0;
	flagMPTaskRun = 1;
	flagHPTaskRun = 0;

	printf("MPTask start\r\n");
	
	/* 让LPTask、HPTask先运行 */	
	vTaskDelay(xTicksToWait);
	
	/* 无限循环 */
	for( ;; )
	{	
		flagLPTaskRun = 0;
		flagMPTaskRun = 1;
		flagHPTaskRun = 0;
	}
}

static void vHPTask( void *pvParameters )
{
	const TickType_t xTicksToWait = pdMS_TO_TICKS( 10UL );	

	flagLPTaskRun = 0;
	flagMPTaskRun = 0;
	flagHPTaskRun = 1;

	printf("HPTask start\r\n");
	
	/* 让LPTask先运行 */	
	vTaskDelay(xTicksToWait);
	
	/* 无限循环 */
	for( ;; )
	{	
		flagLPTaskRun = 0;
		flagMPTaskRun = 0;
		flagHPTaskRun = 1;
		printf("HPTask wait for Lock\r\n");
		
		/* 获得互斥量/二进制信号量 */
		xSemaphoreTake(xLock, portMAX_DELAY);
		
		flagLPTaskRun = 0;
		flagMPTaskRun = 0;
		flagHPTaskRun = 1;
		
		/* 释放互斥量/二进制信号量 */
		xSemaphoreGive(xLock);
	}
}


/* 互斥量/二进制信号量句柄 */
SemaphoreHandle_t xLock;

int main( void )
{	
    /* 创建互斥量/二进制信号量 */
    //xLock = xSemaphoreCreateBinary( );
	//xSemaphoreGive(xLock);
    xLock = xSemaphoreCreateMutex( );

	if( xLock != NULL )
	{
		/* 创建3个任务: LP,MP,HP(低/中/高优先级任务)
		 */
		xTaskCreate( vLPTask, "LPTask", 1000, NULL, 1, NULL );
		xTaskCreate( vMPTask, "MPTask", 1000, NULL, 2, NULL );
		xTaskCreate( vHPTask, "HPTask", 1000, NULL, 3, NULL );
	}
}

    /* 创建互斥量 */
    xLock = xSemaphoreCreateMutex( );

32.互斥量、二进制信号量的异同是什么?

A、互斥量有优先级继承功能

B、Give/Take函数完全一样

C、二进制信号量的初始值是0

D、互斥量的初始值是1

33.互斥量_递归锁

递归锁实现了“谁持有,就由谁释放”,递归锁会记录持有者,只能由持有者释放。

要使用递归锁,需要定义配置项FreeRTOSConfig.h::configUSE_RECURSIVE_MUTEXES

/*递归锁,谁持有,谁释放*/

static SemaphoreHandle_t xSemUART;

void TaskGenericFunction(void * param)
{
	int i;
	while (1)
	{
		xSemaphoreTakeRecursive(xSemUART, portMAX_DELAY);

		printf("%s\r\n", (char *)param);
		for (i = 0; i < 10; i++)
		{
			xSemaphoreTakeRecursive(xSemUART, portMAX_DELAY);
			printf("%s in loop %d\r\n", (char *)param, i);
			xSemaphoreGiveRecursive(xSemUART);
		}
		
		xSemaphoreGiveRecursive(xSemUART);
		vTaskDelay(1);
	}
}

void Task5Function(void * param)
{
	vTaskDelay(10);
	while (1)
	{
		while (1)
		{
			if (xSemaphoreTakeRecursive(xSemUART, 0) != pdTRUE)
			{
				xSemaphoreGiveRecursive(xSemUART);			
			}
			else
			{
				break;
			}
		}
		printf("%s\r\n", (char *)param);
		xSemaphoreGiveRecursive(xSemUART);
		vTaskDelay(1);
	}
}

/*-----------------------------------------------------------*/

int main( void )
{	
	//xSemUART = xSemaphoreCreateMutex();
	xSemUART = xSemaphoreCreateRecursiveMutex();

	xTaskCreate(TaskGenericFunction, "Task3", 100, "Task 3 is running", 1, NULL);
	xTaskCreate(TaskGenericFunction, "Task4", 100, "Task 4 is running", 1, NULL);
	xTaskCreate(Task5Function, "Task5", 100, "Task 5 is running", 1, NULL);
}

34.事件组

使用事件组,可以适用哪些场景?

A、某个事件

B、若干个事件中的某个事件

C、若干个事件中的所有事件

等待的事件中,它们要么是或的关系,要么是与的关系。也就是可以等待若干个事件中的任一个,可以等待若干个事件中的所有。不能在若干个事件中指定某个事件。

35.xEventGroupWaitBits返回前一定会清除事件吗?

AB同时成立才会清除

36.事件组起通知作用,事件组不能用来传递数据,数据的保存还需要另外想办法,比如使用队列。

37.为什么变量的定义,必须放在函数开头那里?不能放在代码之后?

A、Keil默认支持的C语言标准是C89,必须这样做

B、如果是C99的话,变量的定义可以放在任何地方

C、可以在Keil中指定使用C99标准

对于C语言,有几套标准:1980年的C89、1999年的C99、2011年的C11。C89中,变量的定义必须放在作用域的开头,C99中,变量的定义可以放在任何地方。可以设置Keil使用C99标准,如下如:

38.事件组的使用_等待事件

static int sum = 0;
static int dec = 0;
static QueueHandle_t xQueueCalcHandle;
static EventGroupHandle_t xEventGroupCalc;

void Task1Function(void * param)
{
	volatile int i = 0;
	while (1)
	{
		for (i = 0; i < 100000; i++)
			sum++;
		xQueueSend(xQueueCalcHandle, &sum, 0);
		/* 设置事件0 */
		xEventGroupSetBits(xEventGroupCalc, (1<<0));
	}
}

void Task2Function(void * param)
{
	volatile int i = 0;
	while (1)
	{
		for (i = 0; i < 100000; i++)
			dec--;
		xQueueSend(xQueueCalcHandle, &dec, 0);
		/* 设置事件1 */
		xEventGroupSetBits(xEventGroupCalc, (1<<1));
	}
}

void Task3Function(void * param)
{
	int val1, val2;
	while (1)
	{
		/*等待事件  */
		xEventGroupWaitBits(xEventGroupCalc, (1<<0)|(1<<1), pdTRUE, pdTRUE, portMAX_DELAY);
	
		xQueueReceive(xQueueCalcHandle, &val1, 0);
		xQueueReceive(xQueueCalcHandle, &val2, 0);
		
		printf("val1 = %d, val2 = %d\r\n", val1, val2);
	}
}

/*-----------------------------------------------------------*/

int main( void )
{
	TaskHandle_t xHandleTask1;

	/* 创建事件组 */
	xEventGroupCalc = xEventGroupCreate();

	xQueueCalcHandle = xQueueCreate(2, sizeof(int));
	if (xQueueCalcHandle == NULL)
	{
		printf("can not create queue\r\n");
	}

	xTaskCreate(Task1Function, "Task1", 100, NULL, 1, &xHandleTask1);
	xTaskCreate(Task2Function, "Task2", 100, NULL, 1, NULL);
	xTaskCreate(Task3Function, "Task3", 100, NULL, 1, NULL);
}

/*
val1 = 100000, val2 =-100000
val1 = 200000, val2 =-200000
val1 = 300000, val2 =-300000
val1 = 400000, val2 =-400000
val1 = 500000, val2 =-500000
val1 = 600000, val2 =-600000
val1 = 700000, val2 =-700000
*/

39.事件组的使用_同步点

xEventGroupSync推出后,会清除事件吗?

A、成功退出的话,会清除事件

B、成功退出的话,它等待哪些事件,就会清除哪些事件

使用xEventGroupSync函数时,在参数uxBitsToWaitFor中指定了等待哪些事件,如果函数成功返回,则会清除uxBitsToWaitFor表示的位。

31.任务通知

使用任务通知时,就是“通知任务”,目标明确。发送者、接收者是多对1的关系。

使用哪个任务通知时,只能通知指定的任务。

32.使用任务通知时,假设任务A通知任务B,任务A发出通知时不会阻塞;任务A等待通知时,可以等待。

TCB结构体里没有list让发送者阻塞在上面,所以发送者不会阻塞。

33.任务通知有哪些优点、缺点?

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

B、更节省内存,因为无需创建通信对象

C、不能使用任务通知给ISR发送数据、发送事件,ISR不属于任务

D、接收方只有一个任务

E、无法缓冲多个数据,任务通知只能保存1个数据

F、无法向多个任务进行广播

34.任务通知使用_轻量级信号量

创建信号量时,可以指定最大值、初始值;使用任务通知实现轻量级的信号量时?

A、不能设置最大值

B、初始值为0,不能指定初始值

C、最小值是0,跟信号量是一样的

xSemaphoreTake和ulTaskNotifyTake的返回值有什么不同?

A、xSemaphoreTake返回pdPASS表示成功,其它值表示失败

B、ulTaskNotifyTake成功的话返回通知值(大于0),如果是超时返回的话则是0

/*在任务通知里,实现轻量级的信号量,task1 Give了10次,在task2 也Task了10次*/

static TaskHandle_t xHandleTask2;

void Task1Function(void * param)
{
	volatile int i = 0;
	while (1)
	{
		for (i = 0; i < 10000; i++)
			sum++;
		for (i = 0; i < 10; i++)
		{
			xTaskNotifyGive(xHandleTask2); //发送给目标任务的句柄
		}
		vTaskDelete(NULL);
	}
}

void Task2Function(void * param)
{
	int i = 0;
	int val;
	while (1)
	{
		flagCalcEnd = 0;
		val = ulTaskNotifyTake(pdFALL, portMAX_DELAY); //把返回值val打印出来
        //val = ulTaskNotifyTake(pdTRUE, portMAX_DELAY); //设置为pdTRUE的话,只打印一次
		flagCalcEnd = 1;
		printf("sum = %d, NotifyVal = %d, i = %d\r\n", sum, val, i++);
	}
}


/*-----------------------------------------------------------*/

int main( void )
{
	TaskHandle_t xHandleTask1;

	xTaskCreate(Task1Function, "Task1", 100, NULL, 1, &xHandleTask1);
	xTaskCreate(Task2Function, "Task2", 100, NULL, 1, &xHandleTask2);
}

/*
sum = 10000, NotifyVal = 10, i = 0 
sum = 10000, NotifyVal = 9, i = 1
sum = 10000, NotifyVal = 8, i = 2
sum = 10000, NotifyVal = 7, i = 3
sum = 10000, NotifyVal = 6, i = 4
sum = 10000, NotifyVal = 5, i = 5
sum = 10000, NotifyVal = 4, i = 6
sum = 10000, NotifyVal = 3, i = 7
sum = 10000, NotifyVal = 2, i = 8
sum = 10000, NotifyVal = 1, i = 9 
*/

35.任务通知使用_轻量级队列

队列、使用任务通知实现的轻量级队列,有什么异同?

A、队列:可以容纳多个数据,数据大小可以指定

B、任务通知:只有1个数据,数据是32位的

C、队列:写队列时,可以阻塞

D、任务通知:写队列时,不可以阻塞

E、队列:如果队列长度是1,可以选择覆盖队列

F、任务通知:可以覆盖,也可以不覆盖

static TaskHandle_t xHandleTask2;

void Task1Function(void * param)
{
	volatile int i = 0;
	while (1)
	{
		for (i = 0; i < 10000; i++)
			sum++;
		for (i = 0; i < 10; i++)
		{
			xTaskNotify(xHandleTask2, sum, eSetValueWithoutOverwrite);
            //xTaskNotify(xHandleTask2, sum, eSetValueWithOverwrite);
			sum++;
		}
		vTaskDelete(NULL);
	}
}

void Task2Function(void * param)
{
	int val;
	int i = 0;
	
	while (1)
	{
		flagCalcEnd = 0;
		xTaskNotifyWait(0, 0, &val, portMAX_DELAY);
		flagCalcEnd = 1;
		printf("sum = %d, i = %d\r\n", val, i++);
	}
}

/*-----------------------------------------------------------*/

int main( void )
{
	TaskHandle_t xHandleTask1;

	xTaskCreate(Task1Function, "Task1", 100, NULL, 1, &xHandleTask1);
	xTaskCreate(Task2Function, "Task2", 100, NULL, 1, &xHandleTask2);

}

/*
sum = 10000, i = 0
*/

/*
//sum = 10009, i = 0
*/

使用任务通知实现的轻量级队列时,如果选择eSetValueWithOverwrite,它跟邮箱有什么差别?

A、邮箱: 一旦邮箱中有数据,可以多次读,都会成功

B、轻量级队列:有数据时,只能读一次

36.任务通知使用_轻量级事件组

任务通知并不能实现真正的事件组,为什么?

A、它不能等待指定的事件

B、它不能等待若干个事件中的任意一个

C、一旦有事件,总会唤醒任务

发送方可以设置事件,但是接收方不能挑选事件,即使不是它关心的事件,它也会被唤醒。

static TaskHandle_t xHandleTask3;

void Task1Function(void * param)
{
	volatile int i = 0;
	while (1)
	{
		for (i = 0; i < 100000; i++)
			sum++;
		xQueueSend(xQueueCalcHandle, &sum, 0);
		/* 设置事件0 */
		//xEventGroupSetBits(xEventGroupCalc, (1<<0));
		xTaskNotify(xHandleTask3, (1<<0), eSetBits);
		printf("Task 1 set bit 0\r\n");
		vTaskDelete(NULL);
	}
}

void Task2Function(void * param)
{
	volatile int i = 0;
	while (1)
	{
		for (i = 0; i < 1000000; i++)
			dec--;
		xQueueSend(xQueueCalcHandle, &dec, 0);
		/* 设置事件1 */
		//xEventGroupSetBits(xEventGroupCalc, (1<<1));
		xTaskNotify(xHandleTask3, (1<<1), eSetBits);
		printf("Task 2 set bit 1\r\n");
		vTaskDelete(NULL);
	}
}


void Task3Function(void * param)
{
	int val1, val2;
	int bits;
	while (1)
	{
		/*等待事件  */
		//xEventGroupWaitBits(xEventGroupCalc, (1<<0)|(1<<1), pdTRUE, pdTRUE, portMAX_DELAY);
		xTaskNotifyWait(0, 0, &bits, portMAX_DELAY);
		if ((bits & 0x3) == 0x3)
		{
			vTaskDelay(20);
			xQueueReceive(xQueueCalcHandle, &val1, 0);
			xQueueReceive(xQueueCalcHandle, &val2, 0);
			
			printf("val1 = %d, val2 = %d\r\n", val1, val2);
		}
		else
		{
			vTaskDelay(20);
			printf("have not get all bits, get only 0x%x\r\n", bits);
		}
	}
}

/*-----------------------------------------------------------*/

int main( void )
{
	TaskHandle_t xHandleTask1;

    xQueueCalcHandle = xQueueCreate(2, sizeof(int));
	if (xQueueCalcHandle == NULL)
	{
		printf("can not create queue\r\n");
	}

	xTaskCreate(Task1Function, "Task1", 100, NULL, 1, &xHandleTask1);
	xTaskCreate(Task2Function, "Task2", 100, NULL, 1, NULL);
	xTaskCreate(Task3Function, "Task3", 100, NULL, 1, &xHandleTask3);
}

/*
Task 1 set bit 0
have not get all bits,get only 0x1
Task 2 set bit 1
val1 = 100000, val2 = -1000000
*/

37.pvTimeID可以用来给pxCallbackFunction传递参数吗?

可以,pxCallbackFunction的参数时TimerHandle_t,可以从中取出pvTimerID。

38.既然定时器回调函数是在任务上下文中执行,那它可以调用导致阻塞的函数吗?

A、可以,但是不建议

B、定时器回调函数应该尽快执行完

如果定时器回调函数执行时间过长,会阻碍其它定时器函数的执行。

39.定时器守护任务的优先级应该设置得比较高?

是的,定时器任务优先级太低得话,定时器函数就不能及时执行。

40.定时器的超时函数是在守护任务里面执行,这个守护任务可以管理各种定时器,别的任务可以调用定时器函数(去启动/停止/复位/改变周期),这些函数的实质是把命令写入队列中,之后就会唤起守护任务,守护任务就会从队列中取那些命令,让我做什么事情。

如果队列满,不等待的话,这些队列都有可能失败。

static TimerHandle_t xMyTimerHandle;
static int flagTimer = 0;

void Task1Function(void * param)
{
	volatile int i = 0;

	xTimerStart(xMyTimerHandle, 0);
	
	while (1)
	{
		printf("Task1Function ...\r\n");
	}
}

void Task2Function(void * param)
{
	volatile int i = 0;
	while (1)
	{
	}
}


void MyTimerCallbackFunction( TimerHandle_t xTimer )
{
	static int cnt = 0;
	flagTimer = !flagTimer;
	printf("MyTimerCallbackFunction_t cnt = %d\r\n", cnt++);
}

/*-----------------------------------------------------------*/

int main( void )
{
	TaskHandle_t xHandleTask1;

	xMyTimerHandle = xTimerCreate("mytimer", 100, pdTRUE, NULL, MyTimerCallbackFunction);

	xTaskCreate(Task1Function, "Task1", 100, NULL, 1, &xHandleTask1);
	//xTaskCreate(Task2Function, "Task2", 100, NULL, 1, NULL);
}

/*
Task1Function ...
Task1Function ...
Task1Function ...
Task1Function ...
MyTimerCallbackFunction_t cnt = 1
Task1Function ...
Task1Function ...
Task1Function ...
Task1Function ...
MyTimerCallbackFunction_t cnt = 2
......
*/

41.定时器防抖

static TimerHandle_t xMyTimerHandle;
static int flagTimer = 0;


void MyTimerCallbackFunction( TimerHandle_t xTimer )
{
	static int cnt = 0;
	flagTimer = !flagTimer;
	printf("Get GPIO Key cnt = %d\r\n", cnt++);
}

/*-----------------------------------------------------------*/

void KeyInit(void)
{
	GPIO_InitTypeDef GPIO_InitStructure;//定义结构体
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);// 使能时钟
	GPIO_InitStructure.GPIO_Pin  = GPIO_Pin_0;             // 选择IO口   PA0
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;          // 设置成上拉输入
	GPIO_Init(GPIOA, &GPIO_InitStructure);                  // 使用结构体信息进行初始化IO口}
}

void KeyIntInit(void)
{
	EXTI_InitTypeDef EXTI_InitStructure;//定义初始化结构体
	NVIC_InitTypeDef NVIC_InitStructure;//定义结构体

	RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE); /* 使能AFIO复用时钟 */

	GPIO_EXTILineConfig(GPIO_PortSourceGPIOA, GPIO_PinSource0); /* 将GPIO口与中断线映射起来 */


	EXTI_InitStructure.EXTI_Line=EXTI_Line0; // 中断线
	EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;            // 中断模式
	EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising_Falling; // 双边沿触发

	EXTI_InitStructure.EXTI_LineCmd = ENABLE;

	EXTI_Init(&EXTI_InitStructure); // 初始化	


	
	NVIC_InitStructure.NVIC_IRQChannel = EXTI0_IRQChannel;     //使能外部中断所在的通道	
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;  // 抢占优先级
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;         // 子优先级
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;            // 使能外部中断通道 	
	NVIC_Init(&NVIC_InitStructure); // 初始化 

}

void EXTI0_IRQHandler(void)
{
	static int cnt = 0;
	if(EXTI_GetITStatus(EXTI_Line0) != RESET)
	{
		printf("EXTI0_IRQHandler cnt = %d\r\n", cnt++);
		/* 使用定时器消除抖动 */
		xTimerReset(xMyTimerHandle, 0); /* Tcur + 2000 */
		
		EXTI_ClearITPendingBit(EXTI_Line0);     //清除中断
	}     
}

int main( void )
{


	KeyInit();
	KeyIntInit();

	xMyTimerHandle = xTimerCreate("mytimer", 2000, pdFALSE, NULL, MyTimerCallbackFunction);

}

/*
创建定时器一次性定时器,回调函数得超时时间设置2s,超过2s后,执行回调函数MyTimerCallbackFunction
EXTI0_IRQHandler cnt = 0
EXTI0_IRQHandler cnt = 1
Get GPIO Key cnt = 0 //n次中断,导致1次按键的处理
EXTI0_IRQHandler cnt = 2
EXTI0_IRQHandler cnt = 3
EXTI0_IRQHandler cnt = 4
EXTI0_IRQHandler cnt = 5
Get GPIO Key cnt = 1
......
*/

队列:可以放入大量的数据

信号量:可以保护邻居资源,本质是队列,把队列的buf去掉了

互斥量:互斥量是特殊的信号量

        1.想要解决“谁上锁、就由谁解锁”的缺陷,但是FreeRTOS的互斥量并没有实现该功能

        2.可以解决优先级反转的问题

        3.还可以解决递归锁的问题

事件组:如果有单个事件的话,可以使用队列、信号量、互斥量。如果要等待多个事件的话,就需要新的机制,事件组。

任务通知:使用队列、信号量、事件组等方法时,并不知道对方是谁。使用任务通知时,可以明确指定:通知哪个任务。

使用任务通知时,任务结构体 TCB 中就包含了内部对象,可以直接接收别人发过来的“通知”。

相关推荐

  1. FreeRTOS学习笔记

    2024-04-05 00:44:01       33 阅读
  2. <span style='color:red;'>FreeRTOS</span>

    FreeRTOS

    2024-04-05 00:44:01      12 阅读

最近更新

  1. 算法·高精度

    2024-04-05 00:44:01       1 阅读
  2. 闲聊C++与面向对象思想

    2024-04-05 00:44:01       1 阅读
  3. 路由器中 RIB 与 FIB 的区别

    2024-04-05 00:44:01       1 阅读
  4. 生成日志系统和监控

    2024-04-05 00:44:01       1 阅读
  5. Apache Spark详解

    2024-04-05 00:44:01       1 阅读
  6. qt opencv 应用举例

    2024-04-05 00:44:01       1 阅读
  7. Pytorch中分类回归常用的损失和优化器

    2024-04-05 00:44:01       1 阅读
  8. 【Rust】Cargo介绍

    2024-04-05 00:44:01       1 阅读
  9. 搭建Spring Cloud项目思路

    2024-04-05 00:44:01       1 阅读
  10. C语言从头学32——字符串数组

    2024-04-05 00:44:01       1 阅读
  11. 7. 有奖猜谜

    2024-04-05 00:44:01       1 阅读

热门阅读

  1. 【DevOps工具篇】Keycloak中设置与OpenLDAP认证集成

    2024-04-05 00:44:01       17 阅读
  2. Linux 设备驱动管理之内核对象(Kernel Object)机制

    2024-04-05 00:44:01       15 阅读
  3. 69. x 的平方根

    2024-04-05 00:44:01       13 阅读
  4. linux小技巧(一)--文件比较

    2024-04-05 00:44:01       17 阅读
  5. OpenStack and Kolla Ansible Release

    2024-04-05 00:44:01       18 阅读
  6. MySQL常见故障与优化

    2024-04-05 00:44:01       18 阅读