任务通知的本质
没有任务通知
所谓"任务通知",你可以反过来读"通知任务"。我们使用队列、信号量、事件组等等方法时,并不知道对方是谁。使用任务通知时,可以明确指定:通知哪个任务。使用队列、信号量、事件组时,我们都要事先创建对应的结构体,双方通过中间的结构体通信
任务通知的存在
使用任务通知时,任务结构体 TCB 中就包含了内部对象,可以直接接收别人发过来的 "通知 " :
任务通知的特性
优势及限制
任务通知的优势:
任务通知的优势:⚫ 效率更高:使用任务通知来发送事件、数据给某个任务时,效率更高。比队列、信号量、事件组都有大的优势。⚫ 更节省内存:使用其他方法时都要先创建对应的结构体,使用任务通知时无需额外创建结构体
任务通知的限制:
不能发送数据给 ISR :ISR 并没有任务结构体,所以无法使用任务通知的功能给 ISR 发送数据。但是ISR 可以使用任务通知的功能,发数据给任务。⚫ 数据只能给该任务独享使用队列、信号量、事件组时,数据保存在这些结构体中,其他任务、 ISR 都可以访问这些数据。使用任务通知时,数据存放入目标任务中,只有它可以访问这些数据。在日常工作中,这个限制影响不大。因为很多场合是从多个数据源把数据发给某个任务,而不是把一个数据源的数据发给多个任务。⚫ 无法缓冲数据使用队列时,假设队列深度为 N ,那么它可以保持 N 个数据。使用任务通知时,任务结构体中只有一个任务通知值,只能保持一个数据。⚫ 无法广播给多个任务使用事件组可以同时给多个任务发送事件。使用任务通知,只能发个一个任务。⚫ 如果发送受阻,发送方无法进入阻塞状态等待假设队列已经满了,使用 xQueueSendToBack() 给队列发送数据时,任务可以进入阻塞状态等待发送完成。使用任务通知时,即使对方无法接收数据,发送方也无法阻塞等待,只能即刻返回错误。
通知状态和通知值
typedef struct tskTaskControlBlock
{volatile StackType_t *pxTopOfStack; /*< Points to the location of the last item placed on the tasks stack. THIS MUST BE THE FIRST MEMBER OF THE TCB STRUCT. */#if ( portUSING_MPU_WRAPPERS == 1 )xMPU_SETTINGS xMPUSettings; /*< The MPU settings are defined as part of the port layer. THIS MUST BE THE SECOND MEMBER OF THE TCB STRUCT. */#endifListItem_t xStateListItem; /*< The list that the state list item of a task is reference from denotes the state of that task (Ready, Blocked, Suspended ). */ListItem_t xEventListItem; /*< Used to reference a task from an event list. */UBaseType_t uxPriority; /*< The priority of the task. 0 is the lowest priority. */StackType_t *pxStack; /*< Points to the start of the stack. */char pcTaskName[ configMAX_TASK_NAME_LEN ];/*< Descriptive name given to the task when created. Facilitates debugging only. */ /*lint !e971 Unqualified char types are allowed for strings and single characters only. */#if ( ( portSTACK_GROWTH > 0 ) || ( configRECORD_STACK_HIGH_ADDRESS == 1 ) )StackType_t *pxEndOfStack; /*< Points to the highest valid address for the stack. */#endif#if ( portCRITICAL_NESTING_IN_TCB == 1 )UBaseType_t uxCriticalNesting; /*< Holds the critical section nesting depth for ports that do not maintain their own count in the port layer. */#endif#if ( configUSE_TRACE_FACILITY == 1 )UBaseType_t uxTCBNumber; /*< Stores a number that increments each time a TCB is created. It allows debuggers to determine when a task has been deleted and then recreated. */UBaseType_t uxTaskNumber; /*< Stores a number specifically for use by third party trace code. */#endif#if ( configUSE_MUTEXES == 1 )UBaseType_t uxBasePriority; /*< The priority last assigned to the task - used by the priority inheritance mechanism. */UBaseType_t uxMutexesHeld;#endif#if ( configUSE_APPLICATION_TASK_TAG == 1 )TaskHookFunction_t pxTaskTag;#endif#if( configNUM_THREAD_LOCAL_STORAGE_POINTERS > 0 )void *pvThreadLocalStoragePointers[ configNUM_THREAD_LOCAL_STORAGE_POINTERS ];#endif#if( configGENERATE_RUN_TIME_STATS == 1 )uint32_t ulRunTimeCounter; /*< Stores the amount of time the task has spent in the Running state. */#endif#if ( configUSE_NEWLIB_REENTRANT == 1 )/* Allocate a Newlib reent structure that is specific to this task.Note Newlib support has been included by popular demand, but is notused by the FreeRTOS maintainers themselves. FreeRTOS is notresponsible for resulting newlib operation. User must be familiar withnewlib and must provide system-wide implementations of the necessarystubs. Be warned that (at the time of writing) the current newlib designimplements a system-wide malloc() that must be provided with locks. */struct _reent xNewLib_reent;#endif#if( configUSE_TASK_NOTIFICATIONS == 1 )volatile uint32_t ulNotifiedValue;volatile uint8_t ucNotifyState;#endif/* See the comments above the definition oftskSTATIC_AND_DYNAMIC_ALLOCATION_POSSIBLE. */#if( tskSTATIC_AND_DYNAMIC_ALLOCATION_POSSIBLE != 0 ) /*lint !e731 Macro has been consolidated for readability reasons. */uint8_t ucStaticallyAllocated; /*< Set to pdTRUE if the task is a statically allocated to ensure no attempt is made to free the memory. */#endif#if( INCLUDE_xTaskAbortDelay == 1 )uint8_t ucDelayAborted;#endif} 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 )
任务通知的使用
两类函数
任务通知有 2 套函数,简化版、专业版,列表如下:⚫ 简化版函数的使用比较简单,它实际上也是使用专业版函数实现的⚫ 专业版函数支持很多参数,可以实现很多功能
xTaskNotifyGive
xTaskNotifyGive
是 FreeRTOS 提供的一个用于给指定任务发送通知的函数。它是一个简化的接口,用于将目标任务的通知值增加 1,相当于向该任务发送一个信号。它通常用于同步和信号传递,在某些场景下,可以替代信号量(Semaphore)或消息队列(Queue)等机制。
函数原型
BaseType_t xTaskNotifyGive(TaskHandle_t xTask);
参数说明
-
xTask
: 目标任务的句柄,表示你要通知的任务。- 这个任务句柄可以通过
xTaskCreate
创建任务时返回的任务句柄获得。 - 如果传入
NULL
,则会通知当前任务自己。
- 这个任务句柄可以通过
函数说明
xTaskNotifyGive
会将指定任务的通知值增加 1。通知值是一个 32 位的无符号整数,这个整数在任务间通信时扮演着标志或信号的角色。当任务通过 xTaskNotifyWait
等函数接收通知时,它可以检查或处理这个通知值。
返回值
pdPASS
:表示通知成功发送。pdFAIL
:如果通知未成功发送(例如,目标任务不存在),则返回pdFAIL
。
使用场景
xTaskNotifyGive
主要用于以下场景:
- 任务间同步:一个任务通知另一个任务,表示它已经完成了某个操作,或者发生了某个事件,通知目标任务继续执行。
- 信号传递:用于任务之间传递简单的标志或信号。例如,任务 A 完成某项工作后通知任务 B 执行下一步操作。
ulTaskNotifyTake
ulTaskNotifyTake
是 FreeRTOS 提供的一个函数,用于接收任务通知,它是任务通知机制的一部分。与 xTaskNotifyGive
配合使用,ulTaskNotifyTake
允许一个任务等待并响应来自其他任务的通知。通常,用于任务间同步、信号传递等场景。
函数原型
uint32_t ulTaskNotifyTake(BaseType_t xClearCountOnExit, TickType_t xTicksToWait);
参数说明
-
xClearCountOnExit
:- 这个参数决定了在调用
ulTaskNotifyTake
时是否要清除任务的通知计数器。 - 如果设置为
pdTRUE
,任务通知计数器会在退出时清零,表示已成功处理该通知。 - 如果设置为
pdFALSE
,则计数器不会清除,这样任务可以在后续的调用中继续处理通知(类似于一个“计数器”)。
- 这个参数决定了在调用
-
xTicksToWait
:- 这个参数指定任务等待通知的最长时间(以节拍 tick 为单位)。
- 如果设置为
portMAX_DELAY
,则任务会一直等待通知,直到收到通知为止。如果设置为 0,则任务会立即返回,不会等待。
返回值
0
: 如果任务没有接收到通知,或者接收到的通知计数器已经被清除。>0
: 返回接收到的通知计数器的值(通常是任务通知的增量)。如果xClearCountOnExit
为pdFALSE
,那么返回值表示当前的通知计数器值。如果为pdTRUE
,则会清除计数器,返回接收到的通知值。
函数说明
ulTaskNotifyTake
用于接收一个任务的通知,它可以等待指定的时间。如果在该时间内没有接收到通知,任务将返回 0。如果在指定时间内接收到通知,返回接收到的通知值(通常是 1)。
- 任务在接收到通知后,可以执行相应的操作。
- 如果通知的计数器被清除,则该任务的通知值将被重置为 0(除非
xClearCountOnExit
为pdFALSE
)。
使用场景
ulTaskNotifyTake
通常用于以下场景:
- 任务间同步:一个任务等待另一个任务的通知(例如,当任务 A 完成工作时,通知任务 B 执行后续操作)。
- 信号传递:通过任务通知,任务 A 可以通知任务 B 执行某些操作。
xTaskNotify
xTaskNotify
是 FreeRTOS 提供的一个函数,允许任务通过任务通知机制与其他任务进行通信或同步。与 ulTaskNotifyTake
和 xTaskNotifyGive
函数配合使用,xTaskNotify
提供了更灵活的任务通知机制,它允许任务发送或接收通知,同时支持不同的通知方式和通知值。
函数原型
BaseType_t xTaskNotify(TaskHandle_t xTask, uint32_t ulValue, eNotifyAction eAction);
参数说明
-
xTask
:- 目标任务的句柄,指明哪个任务会接收这个通知。如果任务句柄为
NULL
,则表示将通知调用此函数的任务。
- 目标任务的句柄,指明哪个任务会接收这个通知。如果任务句柄为
-
ulValue
:- 发送的通知值。该值可以是任何 32 位的整数,通常用于携带一些信息或者状态。
-
eAction
:- 通知的操作类型。这个参数使用枚举类型
eNotifyAction
,它定义了任务通知的行为。主要有以下几种常见的操作:eSetBits
: 设置通知位。将通知值中的指定位设置为1
。eIncrement
: 增加通知值(递增通知值的计数)。eSetValueWithOverwrite
: 设置通知值,如果原值存在,则覆盖。eSetValueWithoutOverwrite
: 设置通知值,如果原值已经存在,则不覆盖。eClearBits
: 清除通知位。将通知值中的指定位设置为0
。
- 通知的操作类型。这个参数使用枚举类型
返回值
pdPASS
: 如果通知成功发送或操作完成。errTASK_NOT_FOUND
: 如果目标任务无效(例如xTask
为无效的任务句柄)。
通知机制
xTaskNotify
是一种轻量级的任务间通知机制,通过修改任务的通知值来进行信号传递。它与传统的信号量、队列和事件组相比,具有更低的开销和更快的响应速度。
xTaskNotifyWait
xTaskNotifyWait
是 FreeRTOS 提供的一个函数,用于任务等待另一个任务的通知。与 ulTaskNotifyTake
类似,它可以用于同步任务之间的事件或信号。xTaskNotifyWait
可以让一个任务等待某些通知位被设置或某个通知值的变化,直到发生指定的事件或者超时。
函数原型
BaseType_t xTaskNotifyWait( uint32_t ulBitsToClearOnEntry,uint32_t ulBitsToSetOnExit,uint32_t *pulNotificationValue,TickType_t xTicksToWait );
参数说明
-
ulBitsToClearOnEntry
:- 当任务等待通知时,清除通知值中某些位。即使在任务等待期间这些位被设置,函数返回时也会把这些位清除。
-
ulBitsToSetOnExit
:- 当任务成功接收到通知后,设置通知值中的某些位。这些位将在通知发生时被设置。
-
pulNotificationValue
:- 指向一个
uint32_t
变量的指针,用于接收任务的通知值。若没有兴趣获取通知值,则可以传入NULL
。
- 指向一个
-
xTicksToWait
:- 任务等待通知的时间(以
ticks
为单位)。如果在指定的时间内没有接收到通知,则返回pdFALSE
。
- 任务等待通知的时间(以
返回值
pdPASS
: 表示通知已被成功接收。pdFAIL
: 表示未能接收到通知,可能是因为超时或者其他原因。
典型用途
xTaskNotifyWait
常用于任务在执行之前等待其他任务的通知,或在处理某些操作之前确保任务之间的同步。
使用场景
- 等待某个通知: 任务等待另一个任务设置某个通知位或通知值。
- 设置和清除通知位: 在任务间传递信号并对通知位进行清除或设置。
- 等待超时机制: 设置等待超时时间,防止任务一直阻塞等待。
A通知B,B不接收A的通知
A通知B,B接收A的通知
任务通知车辆运行代码
/** Project: N|Watch* Author: Zak Kemble, contact@zakkemble.co.uk* Copyright: (C) 2013 by Zak Kemble* License: GNU GPL v3 (see License.txt)* Web: http://blog.zakkemble.co.uk/diy-digital-wristwatch/*/
#include <stdlib.h>
#include <stdio.h>
#include <string.h>#include "cmsis_os.h"
#include "FreeRTOS.h" // ARM.FreeRTOS::RTOS:Core
#include "task.h" // ARM.FreeRTOS::RTOS:Core
#include "event_groups.h" // ARM.FreeRTOS::RTOS:Event Groups
#include "semphr.h" // ARM.FreeRTOS::RTOS:Core#include "draw.h"
#include "resources.h"#include "driver_lcd.h"
#include "driver_ir_receiver.h"
#include "driver_rotary_encoder.h"
#include "driver_mpu6050.h"#define NOINVERT false
#define INVERT true#define CAR_COUNT 3
#define CAR_WIDTH 12
#define CAR_LENGTH 15
#define ROAD_SPEED 6static SemaphoreHandle_t g_xSemTicks;
static uint32_t g_xres, g_yres, g_bpp;
static uint8_t *g_framebuffer;
static EventGroupHandle_t g_xEventCar;static TaskHandle_t g_TaskHandleCar2;
static TaskHandle_t g_TaskHandleCar3;struct car {int x;int y;int control_key;
};struct car g_cars[3] = {{0, 0, IR_KEY_1},{0, 17, IR_KEY_2},{0, 34, IR_KEY_3},
};static const byte carImg[] ={0x40,0xF8,0xEC,0x2C,0x2C,0x38,0xF0,0x10,0xD0,0x30,0xE8,0x4C,0x4C,0x9C,0xF0,0x02,0x1F,0x37,0x34,0x34,0x1C,0x0F,0x08,0x0B,0x0C,0x17,0x32,0x32,0x39,0x0F,
};static const byte clearImg[30] ={0};static const byte roadMarking[] ={0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
};#if 0
void car_test(void)
{g_framebuffer = LCD_GetFrameBuffer(&g_xres, &g_yres, &g_bpp);draw_init();draw_end();draw_bitmap(0, 0, carImg, 15, 16, NOINVERT, 0);draw_flushArea(0, 0, 15, 16);draw_bitmap(0, 16, roadMarking, 8, 1, NOINVERT, 0);draw_flushArea(0, 16, 8, 1);while (1);
}
#endifstatic void ShowCar(struct car *pcar)
{draw_bitmap(pcar->x, pcar->y, carImg, 15, 16, NOINVERT, 0);draw_flushArea(pcar->x, pcar->y, 15, 16);
}static void HideCar(struct car *pcar)
{draw_bitmap(pcar->x, pcar->y, clearImg, 15, 16, NOINVERT, 0);draw_flushArea(pcar->x, pcar->y, 15, 16);
}static void Car1Task(void *params)
{struct car *pcar = params;struct ir_data idata;/* 创建自己的队列 */QueueHandle_t xQueueIR = xQueueCreate(10, sizeof(struct ir_data));/* 注册队列 */RegisterQueueHandle(xQueueIR);/* 显示汽车 */ShowCar(pcar);/* 获得信号量 *///xSemaphoreTake(g_xSemTicks, portMAX_DELAY);while (1){/* 读取按键值:读队列 *///xQueueReceive(xQueueIR, &idata, portMAX_DELAY);/* 控制汽车往右移动 *///if (idata.val == pcar->control_key){if (pcar->x < g_xres - CAR_LENGTH){/* 隐藏汽车 */HideCar(pcar); /* 调整位置 */pcar->x += 1;if (pcar->x > g_xres - CAR_LENGTH){pcar->x = g_xres - CAR_LENGTH;}/* 重新显示汽车 */ShowCar(pcar);vTaskDelay(50);if (pcar->x == g_xres - CAR_LENGTH){/* 释放信号量 *///xSemaphoreGive(g_xSemTicks);/* 设置事件组: bit0 *///xEventGroupSetBits(g_xEventCar, (1<<0));/* 发出任务通知给car2,car3 */xTaskNotifyGive(g_TaskHandleCar2);xTaskNotify(g_TaskHandleCar3, 100, eSetValueWithOverwrite);vTaskDelete(NULL);}}}}
}static void Car2Task(void *params)
{struct car *pcar = params;struct ir_data idata;/* 创建自己的队列 */QueueHandle_t xQueueIR = xQueueCreate(10, sizeof(struct ir_data));/* 注册队列 */RegisterQueueHandle(xQueueIR);/* 显示汽车 */ShowCar(pcar);/* 获得信号量 *///xSemaphoreTake(g_xSemTicks, portMAX_DELAY);/* 等待事件:bit0 *///xEventGroupWaitBits(g_xEventCar, (1<<0), pdTRUE, pdFALSE, portMAX_DELAY);ulTaskNotifyTake(pdTRUE, portMAX_DELAY);while (1){/* 读取按键值:读队列 *///xQueueReceive(xQueueIR, &idata, portMAX_DELAY);/* 控制汽车往右移动 *///if (idata.val == pcar->control_key){if (pcar->x < g_xres - CAR_LENGTH){/* 隐藏汽车 */HideCar(pcar);/* 调整位置 */pcar->x += 1;if (pcar->x > g_xres - CAR_LENGTH){pcar->x = g_xres - CAR_LENGTH;}/* 重新显示汽车 */ShowCar(pcar);vTaskDelay(100);//mdelay(50);if (pcar->x == g_xres - CAR_LENGTH){/* 释放信号量 *///xSemaphoreGive(g_xSemTicks);/* 设置事件组: bit1 *///xEventGroupSetBits(g_xEventCar, (1<<1));vTaskDelete(NULL);}}}}
}static void Car3Task(void *params)
{struct car *pcar = params;struct ir_data idata;uint32_t val;/* 创建自己的队列 */QueueHandle_t xQueueIR = xQueueCreate(10, sizeof(struct ir_data));/* 注册队列 */RegisterQueueHandle(xQueueIR);/* 显示汽车 */ShowCar(pcar);/* 获得信号量 *///xSemaphoreTake(g_xSemTicks, portMAX_DELAY);/* 等待事件:bit0 and bit1 *///xEventGroupWaitBits(g_xEventCar, (1<<0)|(1<<1), pdTRUE, pdTRUE, portMAX_DELAY);do {xTaskNotifyWait(~0, ~0, &val, portMAX_DELAY);} while (val != 100);while (1){/* 读取按键值:读队列 *///xQueueReceive(xQueueIR, &idata, portMAX_DELAY);/* 控制汽车往右移动 *///if (idata.val == pcar->control_key){if (pcar->x < g_xres - CAR_LENGTH){/* 隐藏汽车 */HideCar(pcar);/* 调整位置 */pcar->x += 1;if (pcar->x > g_xres - CAR_LENGTH){pcar->x = g_xres - CAR_LENGTH;}/* 重新显示汽车 */ShowCar(pcar);//vTaskDelay(50);mdelay(50);if (pcar->x == g_xres - CAR_LENGTH){/* 释放信号量 *///xSemaphoreGive(g_xSemTicks);vTaskDelete(NULL);}}}}
}void car_game(void)
{int x;int i, j;g_framebuffer = LCD_GetFrameBuffer(&g_xres, &g_yres, &g_bpp);draw_init();draw_end();//g_xSemTicks = xSemaphoreCreateCounting(1, 1);//g_xSemTicks = xSemaphoreCreateMutex();g_xEventCar = xEventGroupCreate();/* 画出路标 */for (i = 0; i < 3; i++){for (j = 0; j < 8; j++){draw_bitmap(16*j, 16+17*i, roadMarking, 8, 1, NOINVERT, 0);draw_flushArea(16*j, 16+17*i, 8, 1);}}/* 创建3个汽车任务 */
#if 0 for (i = 0; i < 3; i++){draw_bitmap(g_cars[i].x, g_cars[i].y, carImg, 15, 16, NOINVERT, 0);draw_flushArea(g_cars[i].x, g_cars[i].y, 15, 16);}
#endifxTaskCreate(Car1Task, "car1", 128, &g_cars[0], osPriorityNormal, NULL);xTaskCreate(Car2Task, "car2", 128, &g_cars[1], osPriorityNormal+2, &g_TaskHandleCar2);xTaskCreate(Car3Task, "car3", 128, &g_cars[2], osPriorityNormal+2, &g_TaskHandleCar3);
}
软件定时器的本质
软件定时器就是"闹钟",你可以设置闹钟,⚫ 在 30 分钟后让你起床工作⚫ 每隔 1 小时让你例行检查机器运行情况软件定时器也可以完成两类事情:⚫ 在 " 未来 " 某个时间点,运行函数⚫ 周期性地运行函数
软件定时器的运行依赖于硬件定时器的运行
软件定时器的函数
一次性定时器
一次性定时器(即 单次定时器),定时器在执行完一次周期后会自动停止
在 FreeRTOS 中,一次性定时器是通过 xTimerCreate()
函数创建的定时器,并且在创建时设置其为一次性定时器(pdFALSE
的 xAutoReload
参数)。一旦定时器到达设定的时间周期,它会执行指定的回调函数,并且在回调函数执行完毕后定时器会停止,并且不能再自动重复。
具体流程:
- 定时器启动后,会在设定的周期时间到达时触发定时器回调。
- 如果定时器是一次性定时器,回调函数执行完毕后,定时器会被自动停止,并且不会再被触发。
- 如果你希望定时器再次触发,需要重新创建和启动定时器。
一次性定时器的生命周期:
- 定时器启动后,周期到期触发回调函数。
- 回调函数执行完毕后,定时器会停止,并且不再自动重新启动。
如果想要定时器再次触发,你需要重新创建并启动一个新的定时器。
重要提示:
- 如果你需要定时器在回调函数执行后仍然能够重新触发,需要将
xAutoReload
设置为pdTRUE
,即创建一个周期性的定时器(重复定时器)。 - 一次性定时器的主要用途是执行一次性操作,例如延时任务、超时控制等,适用于只需要在某个时间点触发的任务。
创建
xTimerCreate
是 FreeRTOS 中创建定时器的函数。通过该函数,你可以创建一个新的定时器,并且配置定时器的一些属性,包括定时周期、自动重载、定时器标识符和回调函数等
函数原型
TimerHandle_t xTimerCreate(const char * const pcTimerName, // 定时器的名称(字符串)const TickType_t xTimerPeriodInTicks, // 定时器的周期(单位:ticks)const UBaseType_t uxAutoReload, // 是否自动重载(0:不重载,1:自动重载)void * const pvTimerID, // 定时器标识符(可以是任何指针类型的数据)TimerCallbackFunction_t pxCallbackFunction // 定时器到期时执行的回调函数
);
参数说明
-
pcTimerName
:- 定时器的名称,类型为
const char *
,这是一个可选的字符串参数,表示定时器的名称。 - 如果不需要定时器名称,可以传入
NULL
。
- 定时器的名称,类型为
-
xTimerPeriodInTicks
:- 定时器周期,以 FreeRTOS 系统 ticks 为单位。
TickType_t
是一个系统特定的数据类型,表示系统时钟的刻度。 - 例如,如果你的系统时钟频率为 1000Hz(即 1ms 每 tick),并且你希望定时器的周期为 1秒,那么
xTimerPeriodInTicks
应该是 1000。
- 定时器周期,以 FreeRTOS 系统 ticks 为单位。
-
uxAutoReload
:- 定时器是否自动重载的标志。
uxAutoReload
为pdTRUE
时,定时器会在触发后自动重新开始计时;为pdFALSE
时,定时器只会触发一次,不会自动重载。 - 设置为
pdFALSE
创建的是一次性定时器,即定时器到期后会停止,不会再次触发。 - 设置为
pdTRUE
创建的是周期性定时器,即定时器到期后会自动重新开始计时。
- 定时器是否自动重载的标志。
-
pvTimerID
:- 这是一个指向数据的指针,用来为定时器关联用户定义的数据。你可以使用它来存储与定时器相关的任何信息或状态(例如计数器、标志等)。
- 你可以将
NULL
传递给它,如果不需要存储额外数据。
-
pxCallbackFunction
:- 当定时器到期时执行的回调函数。该回调函数必须符合
TimerCallbackFunction_t
类型的定义。 - 该回调函数的参数为
TimerHandle_t
类型的定时器句柄,通常通过该句柄可以获取更多关于定时器的信息。
- 当定时器到期时执行的回调函数。该回调函数必须符合
返回值
- 如果定时器创建成功,返回一个有效的定时器句柄(
TimerHandle_t
类型)。 - 如果定时器创建失败,返回
NULL
。
删除
xTimerDelete
是 FreeRTOS 中用于删除定时器的函数。它允许你从 FreeRTOS 系统中删除一个已经创建的定时器,并且可以指定一个最大等待时间来确保删除操作能够成功完成。
函数原型
BaseType_t xTimerDelete( TimerHandle_t xTimer, TickType_t xTicksToWait );
参数说明
-
xTimer
:- 这是要删除的定时器的句柄,类型为
TimerHandle_t
。该句柄是在创建定时器时由xTimerCreate
返回的。
- 这是要删除的定时器的句柄,类型为
-
xTicksToWait
:- 这是一个表示等待时间的参数,单位为 FreeRTOS 系统的
ticks
。当调用xTimerDelete
删除定时器时,如果定时器当前正在执行或处于活动状态,系统会在xTicksToWait
指定的时间内等待,直到该定时器能够安全删除。如果超过指定时间还没有完成删除操作,则该函数会返回失败。 - 通常设置为
portMAX_DELAY
,表示无限期等待,直到定时器成功删除。
- 这是一个表示等待时间的参数,单位为 FreeRTOS 系统的
返回值
pdPASS
:定时器删除成功。errQUEUE_EMPTY
或其他错误代码:定时器删除失败,可能是由于定时器仍在运行或等待删除的条件未满足。
修改周期
xTimerChangePeriod
是 FreeRTOS 中用于更改定时器周期的函数。它允许在定时器创建之后修改其周期(时间间隔)。定时器的周期定义了定时器触发回调函数的间隔时间。
函数原型
BaseType_t xTimerChangePeriod( TimerHandle_t xTimer,TickType_t xNewPeriod,TickType_t xTicksToWait );
参数说明
-
xTimer
:- 要更改周期的定时器的句柄,类型为
TimerHandle_t
。这个句柄是在创建定时器时由xTimerCreate
返回的。
- 要更改周期的定时器的句柄,类型为
-
xNewPeriod
:- 新的周期值,单位是 FreeRTOS 的
ticks
。该值定义了定时器触发回调函数的间隔时间。通过pdMS_TO_TICKS
可以方便地将毫秒转换为ticks
。
- 新的周期值,单位是 FreeRTOS 的
-
xTicksToWait
:- 这个参数表示在尝试更改定时器周期时,系统最多等待的时间,单位为
ticks
。如果在指定的时间内无法完成周期更改,函数将返回错误。如果设置为portMAX_DELAY
,则表示无限期等待,直到成功更改定时器周期。
- 这个参数表示在尝试更改定时器周期时,系统最多等待的时间,单位为
返回值
pdPASS
:定时器周期更改成功。errQUEUE_EMPTY
或其他错误代码:定时器周期更改失败,可能是由于定时器当前处于忙碌状态或其他错误条件。
增加游戏音效代码
beep.c
/* Copyright (s) 2019 深圳百问网科技有限公司* All rights reserved* * 文件名称:beep.c* 摘要:* * 修改历史 版本号 Author 修改内容*--------------------------------------------------* 2023.9.08 v01 百问科技 创建文件*--------------------------------------------------
*/#include <stdlib.h>
#include <stdio.h>
#include <string.h>#include "cmsis_os.h"
#include "FreeRTOS.h" // ARM.FreeRTOS::RTOS:Core
#include "task.h" // ARM.FreeRTOS::RTOS:Core
#include "event_groups.h" // ARM.FreeRTOS::RTOS:Event Groups
#include "semphr.h" // ARM.FreeRTOS::RTOS:Core#include "driver_passive_buzzer.h"static TimerHandle_t g_TimerSound;/*********************************************************************** 函数名称: GameSoundTimer_Func* 功能描述: 游戏声音的定时器函数,用来停止声音* 输入参数: 无* 输出参数: 无* 返 回 值: 无* 修改日期: 版本号 修改人 修改内容* -----------------------------------------------* 2023/09/08 V1.0 韦东山 创建***********************************************************************/
static void GameSoundTimer_Func( TimerHandle_t xTimer )
{PassiveBuzzer_Control(0);
}/*********************************************************************** 函数名称: buzzer_init* 功能描述: 初始化蜂鸣器并创建定时器(用于停止声音)* 输入参数: 无* 输出参数: 无* 返 回 值: 无* 修改日期: 版本号 修改人 修改内容* -----------------------------------------------* 2023/09/08 V1.0 韦东山 创建***********************************************************************/
void buzzer_init(void)
{/* 初始化蜂鸣器 */PassiveBuzzer_Init(); /* 创建定时器 */g_TimerSound = xTimerCreate( "GameSound", 200,pdFALSE,NULL,GameSoundTimer_Func);
}/*********************************************************************** 函数名称: buzzer_buzz* 功能描述: 启动蜂鸣器发出一段声音* 输入参数: freq - 声音频率* time_ms - 持续时间* 输出参数: 无* 返 回 值: 无* 修改日期: 版本号 修改人 修改内容* -----------------------------------------------* 2023/09/08 V1.0 韦东山 创建***********************************************************************/
void buzzer_buzz(int freq, int time_ms)
{PassiveBuzzer_Set_Freq_Duty(freq, 50);/* 启动定时器 */xTimerChangePeriod(g_TimerSound, time_ms, 0);
}
game1.c
/** Project: N|Watch* Author: Zak Kemble, contact@zakkemble.co.uk* Copyright: (C) 2013 by Zak Kemble* License: GNU GPL v3 (see License.txt)* Web: http://blog.zakkemble.co.uk/diy-digital-wristwatch/*/
#include <stdlib.h>
#include <stdio.h>
#include <string.h>#include "cmsis_os.h"
#include "FreeRTOS.h" // ARM.FreeRTOS::RTOS:Core
#include "task.h" // ARM.FreeRTOS::RTOS:Core
#include "event_groups.h" // ARM.FreeRTOS::RTOS:Event Groups
#include "semphr.h" // ARM.FreeRTOS::RTOS:Core#include "draw.h"
#include "resources.h"#include "driver_lcd.h"
#include "driver_ir_receiver.h"
#include "driver_rotary_encoder.h"
#include "driver_mpu6050.h"#include "beep.h"#define NOINVERT false
#define INVERT true#define sprintf_P sprintf
#define PSTR(a) a#define PLATFORM_WIDTH 12
#define PLATFORM_HEIGHT 4
#define UPT_MOVE_NONE 0
#define UPT_MOVE_RIGHT 1
#define UPT_MOVE_LEFT 2
#define BLOCK_COLS 32
#define BLOCK_ROWS 5
#define BLOCK_COUNT (BLOCK_COLS * BLOCK_ROWS)typedef struct{float x;float y;float velX;float velY;
}s_ball;static const byte block[] ={0x07,0x07,0x07,
};static const byte platform[] ={0x60,0x70,0x50,0x10,0x30,0xF0,0xF0,0x30,0x10,0x50,0x70,0x60,
};static const byte ballImg[] ={0x03,0x03,
};static const byte clearImg[] ={0,0,0,0,0,0,0,0,0,0,0,0,
};static bool btnExit(void);
static bool btnRight(void);
static bool btnLeft(void);
void game1_draw(void);static byte uptMove;
static s_ball ball;
static bool* blocks;
static byte lives, lives_origin;
static uint score;
static byte platformX;static uint32_t g_xres, g_yres, g_bpp;
static uint8_t *g_framebuffer;
static QueueSetHandle_t g_xQueueSetInput; /* 输入设备的队列集 */
static QueueHandle_t g_xQueuePlatform; /* 挡球板队列 */
static QueueHandle_t g_xQueueIR;
static QueueHandle_t g_xQueueRotary;
static QueueHandle_t g_xQueueMPU6050; /* MPU6050队列 *//* 挡球板任务 */
static void platform_task(void *params)
{byte platformXtmp = platformX; struct input_data idata;// Draw platformdraw_bitmap(platformXtmp, g_yres - 8, platform, 12, 8, NOINVERT, 0);draw_flushArea(platformXtmp, g_yres - 8, 12, 8);while (1){//if (0 == IRReceiver_Read(&dev, &data))xQueueReceive(g_xQueuePlatform, &idata, portMAX_DELAY);uptMove = idata.val;// Hide platformdraw_bitmap(platformXtmp, g_yres - 8, clearImg, 12, 8, NOINVERT, 0);draw_flushArea(platformXtmp, g_yres - 8, 12, 8);// Move platformif(uptMove == UPT_MOVE_RIGHT)platformXtmp += 3;else if(uptMove == UPT_MOVE_LEFT)platformXtmp -= 3;uptMove = UPT_MOVE_NONE;// Make sure platform stays on screenif(platformXtmp > 250)platformXtmp = 0;else if(platformXtmp > g_xres - PLATFORM_WIDTH)platformXtmp = g_xres - PLATFORM_WIDTH;// Draw platformdraw_bitmap(platformXtmp, g_yres - 8, platform, 12, 8, NOINVERT, 0);draw_flushArea(platformXtmp, g_yres - 8, 12, 8);platformX = platformXtmp; }
}/*********************************************************************** 函数名称: ProcessIRData* 功能描述: 读取红外遥控器键值并转换为游戏控制键,写入挡球板队列* 输入参数: 无* 输出参数: 无* 返 回 值: 无* 修改日期: 版本号 修改人 修改内容* -----------------------------------------------* 2023/09/02 V1.0 韦东山 创建***********************************************************************/
static void ProcessIRData(void)
{struct ir_data idata;static struct input_data input;xQueueReceive(g_xQueueIR, &idata, 0);if (idata.val == IR_KEY_LEFT){input.dev = idata.dev;input.val = UPT_MOVE_LEFT;}else if (idata.val == IR_KEY_RIGHT){input.dev = idata.dev;input.val = UPT_MOVE_RIGHT;}else if (idata.val == IR_KEY_REPEAT){/* 保持不变 */;}else{input.dev = idata.dev;input.val = UPT_MOVE_NONE;}/* 写挡球板队列 */xQueueSend(g_xQueuePlatform, &input, 0);
}/*********************************************************************** 函数名称: ProcessRotaryData* 功能描述: 读取旋转编码器数据并转换为游戏控制键,写入挡球板队列* 输入参数: 无* 输出参数: 无* 返 回 值: 无* 修改日期: 版本号 修改人 修改内容* -----------------------------------------------* 2023/09/02 V1.0 韦东山 创建***********************************************************************/
static void ProcessRotaryData(void)
{struct rotary_data rdata;struct input_data idata;int left;int i, cnt;/* 读旋转编码器队列 */xQueueReceive(g_xQueueRotary, &rdata, 0);/* 处理数据 *//* 判断速度: 负数表示向左转动, 正数表示向右转动 */if (rdata.speed < 0){left = 1;rdata.speed = 0 - rdata.speed;}else{left = 0;}//cnt = rdata.speed / 10;//if (!cnt)// cnt = 1;if (rdata.speed > 100)cnt = 4;else if (rdata.speed > 50)cnt = 2;elsecnt = 1;/* 写挡球板队列 */idata.dev = 1;idata.val = left ? UPT_MOVE_LEFT : UPT_MOVE_RIGHT;for (i = 0; i < cnt; i++){xQueueSend(g_xQueuePlatform, &idata, 0);}
}/*********************************************************************** 函数名称: ProcessMPU6050Data* 功能描述: 读取MPU6050D的角度值并转换为游戏控制键,写入挡球板队列* 输入参数: 无* 输出参数: 无* 返 回 值: 无* 修改日期: 版本号 修改人 修改内容* -----------------------------------------------* 2023/09/05 V1.0 韦东山 创建***********************************************************************/
static void ProcessMPU6050Data(void)
{struct mpu6050_data mdata;struct input_data idata;/* 读旋转编码器队列 */xQueueReceive(g_xQueueMPU6050, &mdata, 0);/* 处理数据 *//* 判断角度, 大于90度表示往左移动挡球板, 小于90度表示往右移动挡球板 */if (mdata.angle_x > 90){idata.val = UPT_MOVE_LEFT;}else if(mdata.angle_x < 90){idata.val = UPT_MOVE_RIGHT;}else{idata.val = UPT_MOVE_NONE;}/* 写挡球板队列 */idata.dev = 2;xQueueSend(g_xQueuePlatform, &idata, 0);
}/*********************************************************************** 函数名称: InputTask* 功能描述: 输入任务,检测多个输入设备并调用对应处理函数* 输入参数: params - 未使用* 输出参数: 无* 返 回 值: 无* 修改日期: 版本号 修改人 修改内容* -----------------------------------------------* 2023/09/02 V1.0 韦东山 创建***********************************************************************/
static void InputTask(void *params)
{QueueSetMemberHandle_t xQueueHandle;while (1){/* 读队列集, 得到有数据的队列句柄 */xQueueHandle = xQueueSelectFromSet(g_xQueueSetInput, portMAX_DELAY);if (xQueueHandle){/* 读队列句柄得到数据,处理数据 */if (xQueueHandle == g_xQueueIR){ProcessIRData();}else if (xQueueHandle == g_xQueueRotary){ProcessRotaryData();} else if (xQueueHandle == g_xQueueMPU6050){ProcessMPU6050Data();} }}
}void game1_task(void *params)
{ g_framebuffer = LCD_GetFrameBuffer(&g_xres, &g_yres, &g_bpp);draw_init();draw_end();buzzer_init();/* 创建队列,队列集,创建输入任务InputTask */g_xQueuePlatform = xQueueCreate(10, sizeof(struct input_data));g_xQueueSetInput = xQueueCreateSet(IR_QUEUE_LEN + ROTARY_QUEUE_LEN + MPU6050_QUEUE_LEN);g_xQueueIR = GetQueueIR();g_xQueueRotary = GetQueueRotary();g_xQueueMPU6050 = GetQueueMPU6050();xQueueAddToSet(g_xQueueIR, g_xQueueSetInput);xQueueAddToSet(g_xQueueRotary, g_xQueueSetInput);xQueueAddToSet(g_xQueueMPU6050, g_xQueueSetInput);xTaskCreate(MPU6050_Task, "MPU6050Task", 128, NULL, osPriorityNormal, NULL);xTaskCreate(InputTask, "InputTask", 128, NULL, osPriorityNormal, NULL);uptMove = UPT_MOVE_NONE;ball.x = g_xres / 2;ball.y = g_yres - 10;ball.velX = -0.5;ball.velY = -0.6;
// ball.velX = -1;
// ball.velY = -1.1;blocks = pvPortMalloc(BLOCK_COUNT);memset(blocks, 0, BLOCK_COUNT);lives = lives_origin = 3;score = 0;platformX = (g_xres / 2) - (PLATFORM_WIDTH / 2);xTaskCreate(platform_task, "platform_task", 128, NULL, osPriorityNormal, NULL);while (1){game1_draw();//draw_end();vTaskDelay(50);}
}static bool btnExit()
{vPortFree(blocks);if(lives == 255){//game1_start();}else{//pwrmgr_setState(PWR_ACTIVE_DISPLAY, PWR_STATE_NONE); //animation_start(display_load, ANIM_MOVE_OFF);vTaskDelete(NULL);}return true;
}static bool btnRight()
{uptMove = UPT_MOVE_RIGHT;return false;
}static bool btnLeft()
{uptMove = UPT_MOVE_LEFT;return false;
}void game1_draw()
{bool gameEnded = ((score >= BLOCK_COUNT) || (lives == 255));byte platformXtmp = platformX;static bool first = 1;// Move ball// hide balldraw_bitmap(ball.x, ball.y, clearImg, 2, 2, NOINVERT, 0);draw_flushArea(ball.x, ball.y, 2, 8);// Draw platform//draw_bitmap(platformX, g_yres - 8, platform, 12, 8, NOINVERT, 0);//draw_flushArea(platformX, g_yres - 8, 12, 8);if(!gameEnded){ball.x += ball.velX;ball.y += ball.velY;}bool blockCollide = false;const float ballX = ball.x;const byte ballY = ball.y;// Block collisionbyte idx = 0;LOOP(BLOCK_COLS, x){LOOP(BLOCK_ROWS, y){if(!blocks[idx] && ballX >= x * 4 && ballX < (x * 4) + 4 && ballY >= (y * 4) + 8 && ballY < (y * 4) + 8 + 4){
// buzzer_buzz(100, TONE_2KHZ, VOL_UI, PRIO_UI, NULL);buzzer_buzz(2000, 100);// led_flash(LED_GREEN, 50, 255); // 100ask todoblocks[idx] = true;// hide blockdraw_bitmap(x * 4, (y * 4) + 8, clearImg, 3, 8, NOINVERT, 0); draw_flushArea(x * 4, (y * 4) + 8, 3, 8); blockCollide = true;score++;}idx++;}}// Side wall collisionif(ballX > g_xres - 2){if(ballX > 240)ball.x = 0; elseball.x = g_xres - 2;ball.velX = -ball.velX; }if(ballX < 0){ball.x = 0; ball.velX = -ball.velX; }// Platform collisionbool platformCollision = false;if(!gameEnded && ballY >= g_yres - PLATFORM_HEIGHT - 2 && ballY < 240 && ballX >= platformX && ballX <= platformX + PLATFORM_WIDTH){platformCollision = true;// buzzer_buzz(200, TONE_5KHZ, VOL_UI, PRIO_UI, NULL); // 100ask todobuzzer_buzz(5000, 200);ball.y = g_yres - PLATFORM_HEIGHT - 2;if(ball.velY > 0)ball.velY = -ball.velY;ball.velX = ((float)rand() / (RAND_MAX / 2)) - 1; // -1.0 to 1.0}// Top/bottom wall collisionif(!gameEnded && !platformCollision && (ballY > g_yres - 2 || blockCollide)){if(ballY > 240){// buzzer_buzz(200, TONE_2_5KHZ, VOL_UI, PRIO_UI, NULL); // 100ask todobuzzer_buzz(2500, 200);ball.y = 0;}else if(!blockCollide){// buzzer_buzz(200, TONE_2KHZ, VOL_UI, PRIO_UI, NULL); // 100ask todobuzzer_buzz(2000, 200);ball.y = g_yres - 1;lives--;}ball.velY *= -1;}// Draw balldraw_bitmap(ball.x, ball.y, ballImg, 2, 2, NOINVERT, 0);draw_flushArea(ball.x, ball.y, 2, 8);// Draw platform//draw_bitmap(platformX, g_yres - 8, platform, 12, 8, NOINVERT, 0);//draw_flushArea(platformX, g_yres - 8, 12, 8);if (first){first = 0;// Draw blocksidx = 0;LOOP(BLOCK_COLS, x){LOOP(BLOCK_ROWS, y){if(!blocks[idx]){draw_bitmap(x * 4, (y * 4) + 8, block, 3, 8, NOINVERT, 0);draw_flushArea(x * 4, (y * 4) + 8, 3, 8); }idx++;}}}// Draw scorechar buff[6];sprintf_P(buff, PSTR("%u"), score);draw_string(buff, false, 0, 0);// Draw livesif(lives != 255){LOOP(lives_origin, i){if (i < lives)draw_bitmap((g_xres - (3*8)) + (8*i), 1, livesImg, 7, 8, NOINVERT, 0);elsedraw_bitmap((g_xres - (3*8)) + (8*i), 1, clearImg, 7, 8, NOINVERT, 0);draw_flushArea((g_xres - (3*8)) + (8*i), 1, 7, 8); }} // Got all blocksif(score >= BLOCK_COUNT)draw_string_P(PSTR(STR_WIN), false, 50, 32);// No lives left (255 because overflow)if(lives == 255)draw_string_P(PSTR(STR_GAMEOVER), false, 34, 32);}