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 中就包含了内部对象,可以直接接收别人发过来的“通知”。