当前位置: 首页 > article >正文

任务通知的本质(任务通知车辆运行) 软件定时器的本质(增加游戏音效)

 任务通知的本质

没有任务通知

        所谓"任务通知",你可以反过来读"通知任务"。
        我们使用队列、信号量、事件组等等方法时,并不知道对方是谁。使用任务通知时,可
以明确指定:通知哪个任务。
       使用队列、信号量、事件组时,我们都要事先创建对应的结构体,双方通过中间的结构
体通信

任务通知的存在 

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

任务通知的特性

优势及限制

任务通知的优势:
任务通知的优势:
效率更高:使用任务通知来发送事件、数据给某个任务时,效率更高。比队
列、信号量、事件组都有大的优势。
更节省内存:使用其他方法时都要先创建对应的结构体,使用任务通知时无
需额外创建结构体
任务通知的限制: 
不能发送数据给 ISR
ISR 并没有任务结构体,所以无法使用任务通知的功能给 ISR 发送数据。但是
ISR 可以使用任务通知的功能,发数据给任务。
数据只能给该任务独享
使用队列、信号量、事件组时,数据保存在这些结构体中,其他任务、 ISR 都可
以访问这些数据。使用任务通知时,数据存放入目标任务中,只有它可以访问这
些数据。
在日常工作中,这个限制影响不大。因为很多场合是从多个数据源把数据发给某
个任务,而不是把一个数据源的数据发给多个任务。
无法缓冲数据
使用队列时,假设队列深度为 N ,那么它可以保持 N 个数据。
使用任务通知时,任务结构体中只有一个任务通知值,只能保持一个数据。
无法广播给多个任务
使用事件组可以同时给多个任务发送事件。
使用任务通知,只能发个一个任务。
如果发送受阻,发送方无法进入阻塞状态等待
假设队列已经满了,使用 xQueueSendToBack() 给队列发送数据时,任务可以进
入阻塞状态等待发送完成。
使用任务通知时,即使对方无法接收数据,发送方也无法阻塞等待,只能即刻返
回错误。

通知状态和通知值

每个任务都有一个结构体:TCB(Task Control Block),里面有 2 个成员:
一个是 uint8_t 类型,用来表示通知状态
一个是 uint32_t 类型,用来表示通知值
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. */
	#endif

	ListItem_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 not
		used by the FreeRTOS maintainers themselves.  FreeRTOS is not
		responsible for resulting newlib operation.  User must be familiar with
		newlib and must provide system-wide implementations of the necessary
		stubs. Be warned that (at the time of writing) the current newlib design
		implements 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 of
	tskSTATIC_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 提供的一个函数,允许任务通过任务通知机制与其他任务进行通信或同步。与 ulTaskNotifyTakexTaskNotifyGive 函数配合使用,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 常用于任务在执行之前等待其他任务的通知,或在处理某些操作之前确保任务之间的同步。

使用场景
  1. 等待某个通知: 任务等待另一个任务设置某个通知位或通知值。
  2. 设置和清除通知位: 在任务间传递信号并对通知位进行清除或设置。
  3. 等待超时机制: 设置等待超时时间,防止任务一直阻塞等待。

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	6

static 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);
}
#endif

static 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);
	}
#endif
    xTaskCreate(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 小时让你例行检查机器运行情况
软件定时器也可以完成两类事情:
" 未来 " 某个时间点,运行函数
周期性地运行函数
使用定时器跟使用手机闹钟是类似的:
指定时间:启动定时器和运行回调函数,两者的间隔被称为定时器的周期
(period)
指定类型,定时器有两种类型:
一次性 (One-shot timers)
这类定时器启动后,它的回调函数只会被调用一次;
可以手工再次启动它,但是不会自动启动它。
自动加载定时器 (Auto-reload timers )
这类定时器启动后,时间到之后它会自动启动它;
这使得回调函数被周期性地调用。
指定要做什么事,就是指定回调函数
实际的闹钟分为:有效、无效两类。软件定时器也是类似的,它由两种状态:
运行 (Running Active) :运行态的定时器,当指定时间到达之后,它的回调
函数会被调用
冬眠 (Dormant) :冬眠态的定时器还可以通过句柄来访问它,但是它不再运
行,它的回调函数不会被调用

软件定时器的运行依赖于硬件定时器的运行 

 软件定时器的函数

根据定时器的状态转换图,就可以知道所涉及的函数:
 一次性定时器

 一次性定时器(即 单次定时器),定时器在执行完一次周期后会自动停止

在 FreeRTOS 中,一次性定时器是通过 xTimerCreate() 函数创建的定时器,并且在创建时设置其为一次性定时器(pdFALSExAutoReload 参数)。一旦定时器到达设定的时间周期,它会执行指定的回调函数,并且在回调函数执行完毕后定时器会停止,并且不能再自动重复。

具体流程:
  1. 定时器启动后,会在设定的周期时间到达时触发定时器回调。
  2. 如果定时器是一次性定时器,回调函数执行完毕后,定时器会被自动停止,并且不会再被触发。
  3. 如果你希望定时器再次触发,需要重新创建和启动定时器。
一次性定时器的生命周期:
  • 定时器启动后,周期到期触发回调函数。
  • 回调函数执行完毕后,定时器会停止,并且不再自动重新启动。

如果想要定时器再次触发,你需要重新创建并启动一个新的定时器。

重要提示:
  • 如果你需要定时器在回调函数执行后仍然能够重新触发,需要将 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 // 定时器到期时执行的回调函数
);
参数说明
  1. pcTimerName:

    • 定时器的名称,类型为 const char *,这是一个可选的字符串参数,表示定时器的名称。
    • 如果不需要定时器名称,可以传入 NULL
  2. xTimerPeriodInTicks:

    • 定时器周期,以 FreeRTOS 系统 ticks 为单位。TickType_t 是一个系统特定的数据类型,表示系统时钟的刻度。
    • 例如,如果你的系统时钟频率为 1000Hz(即 1ms 每 tick),并且你希望定时器的周期为 1秒,那么 xTimerPeriodInTicks 应该是 1000。
  3. uxAutoReload:

    • 定时器是否自动重载的标志。uxAutoReload 为 pdTRUE 时,定时器会在触发后自动重新开始计时;为 pdFALSE 时,定时器只会触发一次,不会自动重载。
    • 设置为 pdFALSE 创建的是一次性定时器,即定时器到期后会停止,不会再次触发。
    • 设置为 pdTRUE 创建的是周期性定时器,即定时器到期后会自动重新开始计时。
  4. pvTimerID:

    • 这是一个指向数据的指针,用来为定时器关联用户定义的数据。你可以使用它来存储与定时器相关的任何信息或状态(例如计数器、标志等)。
    • 你可以将 NULL 传递给它,如果不需要存储额外数据。
  5. pxCallbackFunction:

    • 当定时器到期时执行的回调函数。该回调函数必须符合 TimerCallbackFunction_t 类型的定义。
    • 该回调函数的参数为 TimerHandle_t 类型的定时器句柄,通常通过该句柄可以获取更多关于定时器的信息。
返回值
  • 如果定时器创建成功,返回一个有效的定时器句柄(TimerHandle_t 类型)。
  • 如果定时器创建失败,返回 NULL
删除

xTimerDelete 是 FreeRTOS 中用于删除定时器的函数。它允许你从 FreeRTOS 系统中删除一个已经创建的定时器,并且可以指定一个最大等待时间来确保删除操作能够成功完成。

函数原型
BaseType_t xTimerDelete( TimerHandle_t xTimer, TickType_t xTicksToWait );
参数说明
  1. xTimer:

    • 这是要删除的定时器的句柄,类型为 TimerHandle_t。该句柄是在创建定时器时由 xTimerCreate 返回的。
  2. xTicksToWait:

    • 这是一个表示等待时间的参数,单位为 FreeRTOS 系统的 ticks。当调用 xTimerDelete 删除定时器时,如果定时器当前正在执行或处于活动状态,系统会在 xTicksToWait 指定的时间内等待,直到该定时器能够安全删除。如果超过指定时间还没有完成删除操作,则该函数会返回失败。
    • 通常设置为 portMAX_DELAY,表示无限期等待,直到定时器成功删除。
返回值
  • pdPASS:定时器删除成功。
  • errQUEUE_EMPTY 或其他错误代码:定时器删除失败,可能是由于定时器仍在运行或等待删除的条件未满足。
修改周期

xTimerChangePeriod 是 FreeRTOS 中用于更改定时器周期的函数。它允许在定时器创建之后修改其周期(时间间隔)。定时器的周期定义了定时器触发回调函数的间隔时间。

函数原型
BaseType_t xTimerChangePeriod( TimerHandle_t xTimer,
                                TickType_t xNewPeriod,
                                TickType_t xTicksToWait );
参数说明
  1. xTimer:

    • 要更改周期的定时器的句柄,类型为 TimerHandle_t。这个句柄是在创建定时器时由 xTimerCreate 返回的。
  2. xNewPeriod:

    • 新的周期值,单位是 FreeRTOS 的 ticks。该值定义了定时器触发回调函数的间隔时间。通过 pdMS_TO_TICKS 可以方便地将毫秒转换为 ticks
  3. 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 platform
	draw_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 platform
		draw_bitmap(platformXtmp, g_yres - 8, clearImg, 12, 8, NOINVERT, 0);
		draw_flushArea(platformXtmp, g_yres - 8, 12, 8);
		
		// Move platform
		if(uptMove == UPT_MOVE_RIGHT)
			platformXtmp += 3;
		else if(uptMove == UPT_MOVE_LEFT)
			platformXtmp -= 3;
		uptMove = UPT_MOVE_NONE;
		
		// Make sure platform stays on screen
		if(platformXtmp > 250)
			platformXtmp = 0;
		else if(platformXtmp > g_xres - PLATFORM_WIDTH)
			platformXtmp = g_xres - PLATFORM_WIDTH;
		
		// Draw platform
		draw_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;
	else
		cnt = 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 ball
	draw_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 collision
	byte 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 todo
				blocks[idx] = true;

                // hide block
                draw_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 collision
	if(ballX > g_xres - 2)
	{
		if(ballX > 240)
			ball.x = 0;		
		else
			ball.x = g_xres - 2;
		ball.velX = -ball.velX;		
	}
	if(ballX < 0)
  {
		ball.x = 0;		
		ball.velX = -ball.velX;	
  }

	// Platform collision
	bool 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 todo
		buzzer_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 collision
	if(!gameEnded && !platformCollision && (ballY > g_yres - 2 || blockCollide))
	{
		if(ballY > 240)
		{
			// buzzer_buzz(200, TONE_2_5KHZ, VOL_UI, PRIO_UI, NULL); // 100ask todo
			buzzer_buzz(2500, 200);
			ball.y = 0;
		}
		else if(!blockCollide)
		{
			// buzzer_buzz(200, TONE_2KHZ, VOL_UI, PRIO_UI, NULL); // 100ask todo
			buzzer_buzz(2000, 200);
			ball.y = g_yres - 1;
			lives--;
		}
		ball.velY *= -1;
	}

	// Draw ball
	draw_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 blocks
    	idx = 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 score
	char buff[6];
	sprintf_P(buff, PSTR("%u"), score);
	draw_string(buff, false, 0, 0);

    // Draw lives
    if(lives != 255)
    {
        LOOP(lives_origin, i)
        {
            if (i < lives)
                draw_bitmap((g_xres - (3*8)) + (8*i), 1, livesImg, 7, 8, NOINVERT, 0);
            else
                draw_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 blocks
	if(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);

}


http://www.kler.cn/a/405868.html

相关文章:

  • MybatisPlus编写join查询
  • 记录下jekins新建个前端部署配置项
  • 单片机学习笔记 9. 8×8LED点阵屏
  • ffmpeg区域颜色覆盖
  • Echarts中柱状图完成横向布局
  • 【MySQL】数据库的隔离级
  • 使用 vscode 调试 nodejs 代码
  • 数据分析-51-时间序列分解之局部均值分解LMD
  • 【Three.js基础学习】28.Coffee Smoke
  • 鸿蒙进阶篇-TextInputTextArea和Checkbox
  • E. Counting Arrays
  • 设计模式-创建型-工厂模式
  • 深度学习笔记24_天气预测
  • 51单片机--- 矩阵按键仿真
  • Mac M4苹果电脑M4上支持的AE/PR/PS/AI/ID/LrC/AU/DC/ME有哪些?
  • 【大模型】docker部署glm-4-9b-chat,并部署到服务器上
  • DHECDH密钥交互算法
  • 金融数据中心容灾“大咖说” | 英方软件的“分而治之”之道
  • YOLOv11来了,使用YOLOv11训练自己的数据集和预测 (保姆级无代码操作版)
  • 智象未来(HiDream.ai)技术赋能,开启AR眼镜消费时代