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

 任务通知的本质

没有任务通知

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

任务通知的存在 

使用任务通知时,任务结构体 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. */#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 提供的一个函数,允许任务通过任务通知机制与其他任务进行通信或同步。与 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	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 小时让你例行检查机器运行情况
软件定时器也可以完成两类事情:
" 未来 " 某个时间点,运行函数
周期性地运行函数
使用定时器跟使用手机闹钟是类似的:
指定时间:启动定时器和运行回调函数,两者的间隔被称为定时器的周期
(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 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);}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/476783.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Kubernetes的pod控制器

文章目录 一&#xff0c;什么是pod控制器二&#xff0c;pod控制器类型&#xff08;重点&#xff09;1.ReplicaSet2.Deployment3.DaemonSet4.StatefulSet5.Job6.Cronjob 三&#xff0c;pod与控制器的关系1.Deployment2.SatefulSet2.1StatefulSet组成2.2headless的由来2.3有状态服…

【单元测试】【Android】JUnit 4 和 JUnit 5 的差异记录

背景 Jetbrain IDE 支持生成 Test 类&#xff0c;其中选择JUnit5 和 JUnit&#xff0c;但是感觉这不是标准的单元测试&#xff0c;因为接口命名吧。 差异对比 两者生成的单测API名称同原API&#xff0c;没加test前缀的。使用差异主要表现在&#xff1a; setUp &#xff06; …

知识中台在多语言客户中的应用

在全球化的商业环境中&#xff0c;企业面临着多语言客户服务的挑战。HelpLook知识中台作为一种智能化解决方案&#xff0c;为企业提供了一个强大的工具&#xff0c;以实现多语言客户服务的自动化和优化。 一、多语言客户服务的重要性 多语言客户服务对于跨国企业至关重要&…

使用 Elastic AI Assistant for Search 和 Azure OpenAI 实现从 0 到 60 的转变

作者&#xff1a;来自 Elastic Greg Crist Elasticsearch 推出了一项新功能&#xff1a;Elastic AI Assistant for Search。你可以将其视为 Elasticsearch 和 Kibana 开发人员的内置指南&#xff0c;旨在回答问题、引导你了解功能并让你的生活更轻松。在 Microsoft AI Services…

【K8S问题系列 |18 】如何解决 imagePullSecrets配置正确,但docker pull仍然失败问题

如果 imagePullSecrets 配置正确&#xff0c;但在执行 docker pull 命令时仍然失败&#xff0c;可能存在以下几种原因。以下是详细的排查步骤和解决方案。 1. 检查 Docker 登录凭证 确保你使用的是与 imagePullSecrets 中相同的凭证进行 Docker 登录&#xff1a; 1.1 直接登录…

Redis的特性ubuntu进行安装

文章目录 1.六大特性1.1内存存储数据1.2可编程1.3可扩展1.4持久化1.5集群1.6高可用1.7速度快 2.具体应用场景&#xff08;了解&#xff09;3.Ubuntu安装Redis3.1安装指令3.2查看状态3.3查找配置文件3.4修改文件内容3.5重启服务器生效3.6安装客户端并进行检查 4.Redis客户端介绍…

【ASE】第八课_冰(ice)的效果

今天我们一起来学习ASE插件&#xff0c;希望各位点个关注&#xff0c;一起跟随我的步伐 今天我们来学习一个简单的冰的效果&#xff0c;这个是根据油管上的视频制作的 可在我的资源里下载模型&#xff0c;贴图&#xff0c;材质 思路 1.物体表面结冰的效果&#xff0c;也就是…

回溯法基础入门解析

回溯法 前 言 回溯法也可以叫做回溯搜索法&#xff0c;它是一种搜索的方式。回溯是递归的副产品&#xff0c;只要有递归就会有回溯。回溯法&#xff0c;一般可以解决如下几种问题&#xff1a; 组合问题&#xff1a;N个数里面按一定规则找出k个数的集合切割问题&#xff1a;一…

Redis原理及应用

Redis简介 Redis是开源的&#xff08;BSD许可&#xff09;&#xff0c;数据结构存储于内存中&#xff0c;被用来作为数据库&#xff0c;缓存和消息代理。它支持多种数据结构&#xff0c;例如&#xff1a;字符串&#xff08;string&#xff09;&#xff0c;哈希&#xff08;hash…

Ubuntu ESP32开发环境搭建

文章目录 ESP32开发环境搭建安装ESP-IDF搭建一个最小工程现象 ESP32开发环境搭建 最近有个小项目需要用到能够联网的mcu驱动&#xff0c;准备玩玩esp的芯片&#xff0c;记录下ESP32开发环境搭建的过程。 ESP-IDF 是乐鑫科技为其 ESP32 系列芯片提供的官方开发框架。这个框架主…

【C#设计模式(14)——责任链模式( Chain-of-responsibility Pattern)】

前言 责任链模式通过将请求和处理者解耦&#xff0c;关联多个处理者形成一个链条&#xff0c;使每个处理者都有机会处理请求&#xff0c;避免了将所有处理逻辑集中在一个对象中的复杂性。 代码 //请求者 public class Requestor {private string content;public string Cont…

用python将一个扫描pdf文件改成二值图片组成的pdf文件

使用墨水屏读书现在似乎越来越流行&#xff0c;这确实有一定的好处&#xff0c;例如基本不发热&#xff0c;电池续航时间超长&#xff0c;基本不能游戏所以有利于沉浸式阅读&#xff0c;还有不知道是不是真的有用的所谓防蓝光伤害。但是&#xff0c;如果阅读的书籍是扫描图片组…

vue3封装Element Plus table表格组件

支持绝大部分Element Plus原有设置属性&#xff0c;支持分页&#xff0c;支持动态适配高度 效果展示 组件代码&#xff1a; <template><div class"table-wrap" ref"tableWrap"><el-tableclass"w100 h100":data"tableInfo.…

IText创建加盖公章的pdf文件并生成压缩文件

第一、前言 此前已在文章&#xff1a;Java使用IText根据pdf模板创建pdf文件介绍了Itex的基本使用技巧&#xff0c;本篇以一个案例为基础&#xff0c;主要介绍IText根据pdf模板填充生成pdf文件&#xff0c;并生成压缩文件。 第二、案例 以下面pdf模板为例&#xff0c;生成一个p…

组会 | 大语言模型 + LoRA

目录 1 大语言模型概述1.1 模型的架构1.2 模型的细节&#xff1a;标记化和嵌入化1.3 模型的核心 2 多头注意力机制3 LoRA 概述3.1 冻结部分模型参数3.2 低秩适配&#xff08;LoRA&#xff09;3.2.1 核心工作原理&#xff1a;冻结模型参数3.2.2 核心工作原理&#xff…

对象:是什么,使用,遍历对象,内置对象

对象使用&#xff1a; 对象访问&#xff1a;&#xff08;对象每个属性之间用逗号隔开&#xff09; 补充&#xff1a;也可以通过 对象名[‘属性名’] 对象方法&#xff1a; 方法名:匿名函数 调用方法不需要控制台打印&#xff0c;只要调用就自动输出值 遍历对象&#xff1a; …

小程序24-滚动效果:scroll-view组件详解

在微信小程序中如果想实现内容滚动&#xff0c;需要使用 scroll-view 组件 scroll-view&#xff1a;可滚动视图区域&#xff0c;适用于需要滚动展示内容的场景&#xff0c;用户可以通过手指滑动或者点击滚动条滚动内容。 scroll-x允许横向滚动scroll-y允许纵向滚动 实现横向…

C++设计模式行为模式———中介者模式

文章目录 一、引言二、中介者模式三、总结 一、引言 中介者模式是一种行为设计模式&#xff0c; 能让你减少对象之间混乱无序的依赖关系。 该模式会限制对象之间的直接交互&#xff0c; 迫使它们通过一个中介者对象进行合作。 中介者模式可以减少对象之间混乱无序的依赖关系&…

AUTOSAR_EXP_ARAComAPI的7章笔记(6)

☞返回总目录 相关总结&#xff1a;ara::com 与 AUTOSAR 元模型的关系总结 7.4 ara::com 与 AUTOSAR 元模型的关系 在本文档中&#xff0c;我们一直在不涉及具体的AP元模型&#xff08;其清单部分&#xff09;的情况下解释 ara::com API的思想和机制&#xff0c;AP元模型是正…

LINUX系统编程之——环境变量

目录 环境变量 1、基本概念 2、查看环境变量的方法 三、查看PATH环境变量的內容 1&#xff09;不带路径也能运行的自己的程序 a、将自己的程序直接添加到PATH指定的路径下 b、将程序所在的路径添加到PATH环境中 四、环境变量与本地变量 1、本地变量创建 2、环境变量创…