15、FreeRTOS 软件定时器

文章目录

  • 一、什么是定时器?
    • 1.1 定时器的理解
    • 1.2 软件定时器的特性
  • 二、 软件定时器的上下文
    • 2.1 守护任务
    • 2.2 守护任务的调度
    • 2.3 回调函数
  • 三、软件定时器的函数
    • 3.1 创建
    • 3.2 删除
    • 3.3 启动/停止
    • 3.5 修改周期
    • 3.6 定时器ID
  • 四、案例
    • 4.1 一般使用
    • 4.2 消除抖动

一、什么是定时器?

1.1 定时器的理解

简单可以理解为闹钟,到达指定一段时间后,就会响铃。

STM32 芯片自带硬件定时器,精度较高,达到定时时间后会触发中断,也可以生成 PWM 、输入捕获、输出 比较,等等,功能强大,但是由于硬件的限制,个数有限

软件定时器也可以实现定时功能,达到定时时间后可调用回调函数,可以在回调函数里处理信息。

你可以设置闹钟

  • 在30分钟后让你起床工作
  • 每隔1小时让你例行检查机器运行情况

软件定时器也可以完成两类事情

  • "未来"某个时间点,运行函数

  • 周期性地运行函数

日常生活中我们可以定无数个"闹钟",这无数的"闹钟"要基于一个真实的闹钟。 在FreeRTOS里,我们也可以设置无数个"软件定时器",它们都是基于`系统滴答中断(Tick Interrupt)。

1.2 软件定时器的特性

我们在手机上添加闹钟时,需要指定时间、指定类型(一次性的,还是周期性的)、指定做什么事;还有 一些过时的、不再使用的闹钟。

使用定时器跟使用手机闹钟是类似的:

  • 指定时间:启动定时器和运行回调函数,两者的间隔被称为定时器的周期(period)。

  • 指定类型,定时器有两种类型:

    • 一次性(One-shot timers)
      • 这类定时器启动后,它的回调函数只会被调用一次;
      • 可以手工再次启动它,但是不会自动启动它。
    • 自动加载定时器(Auto-reload timers )
      • 这类定时器启动后,时间到之后它会自动启动它;
      • 这使得回调函数被周期性地调用。
  • 指定要做什么事,就是指定回调函数

实际的闹钟分为:有效、无效两类。软件定时器也是类似的,它由两种状态:

  • 运行(Running、Active):运行态的定时器,当指定时间到达之后,它的回调函数会被调用
  • 冬眠(Dormant):冬眠态的定时器还可以通过句柄来访问它,但是它不再运行,它的回调函数不会 被调用

定时器运行情况示例如下:

  • Timer1:它是一次性的定时器,在t1启动,周期是6个Tick。经过6个tick后,在t7执行回调函数。 它的回调函数只会被执行一次,然后该定时器进入冬眠状态。
  • Timer2:它是自动加载的定时器,在t1启动,周期是5个Tick。每经过5个tick它的回调函数都被执 行,比如在t6、t11、t16都会执行

在这里插入图片描述

二、 软件定时器的上下文

2.1 守护任务

要理解软件定时器API函数的参数,特别是里面的 xTicksToWait ,需要知道定时器执行的过程。

FreeRTOS中有一个Tick中断,软件定时器基于Tick来运行。在哪里执行定时器函数?第一印象就是在 Tick中断里执行:

  • 在Tick中断中判断定时器是否超时
  • **如果超时了,调用它的回调函数 **

FreeRTOS是RTOS,它不允许在内核、在中断中执行不确定的代码:如果定时器函数很耗时,会影响整 个系统。

所以,FreeRTOS中,不在Tick中断中执行定时器函数

在哪里执行?

在某个任务里执行,这个任务就是:RTOS Damemon Task,RTOS守护任务。以前被称 为 "Timer server",但是这个任务要做并不仅仅是定时器相关,所以改名为:RTOS Damemon Task

当FreeRTOS的配置项configUSE_TIMERS被设置为1时,在启动调度器时,会自动创建RTOS Damemon Task

我们自己编写的任务函数要使用定时器时,是通过"定时器命令队列"(timer command queue)守护任 务交互,如下图所示:

在这里插入图片描述

守护任务的优先级为:configTIMER_TASK_PRIORITY;定时器命令队列的长度为 configTIMER_QUEUE_LENGTH

2.2 守护任务的调度

守护任务的调度,跟普通的任务并无差别。当守护任务是当前优先级最高的就绪态任务时,它就可以运 行。它的工作有两类:

  • 处理命令:从命令队列里取出命令、处理
  • 执行定时器的回调函数

能否及时处理定时器的命令、能否及时执行定时器的回调函数,严重依赖于守护任务的优先级。下面使 用2个例子来演示。

  • 例子1:守护任务的优先性级较低

    • t1:Task1处于运行态,守护任务处于阻塞态。 守护任务在这两种情况下会退出阻塞态切换为就绪态:命令队列中有数据、某个定时器超时了。 至于守护任务能否马上执行,取决于它的优先级
    • t2:Task1调用 xTimerStart() 要注意的是, xTimerStart() 只是把"start timer"的命令发给"定时器命令队列",使得守护任务 退出阻塞态。 在本例中,Task1的优先级高于守护任务,所以守护任务无法抢占Task1
    • t3:Task1执行完 xTimerStart() 但是定时器的启动工作由守护任务来实现,所以 xTimerStart() 返回并不表示定时器已经被启动 了。
    • t4:Task1由于某些原因进入阻塞态,现在轮到守护任务运行。 守护任务从队列中取出"start timer"命令,启动定时器。
    • t5:守护任务处理完队列中所有的命令,再次进入阻塞态。Idel任务时优先级最高的就绪态任务, 它执行。

    注意:假设定时器在后续某个时刻tX超时了,超时时间是"tX-t2",而非"tX-t4",从 xTimerStart() 函数被调用时算起

在这里插入图片描述

  • 例子2:守护任务的优先性级较高
  • t1:Task1处于运行态,守护任务处于阻塞态。 守护任务在这两种情况下会退出阻塞态切换为就绪态:命令队列中有数据、某个定时器超时了。 至于守护任务能否马上执行,取决于它的优先级。
  • t2:Task1调用 xTimerStart() 要注意的是, xTimerStart() 只是把"start timer"的命令发给"定时器命令队列",使得守护任务 退出阻塞态。 在本例中,守护任务的优先级高于Task1,所以守护任务抢占Task1,守护任务开始处理命令队 列。 Task1在执行 xTimerStart() 的过程中被抢占,这时它无法完成此函数。
  • t3:守护任务处理完命令队列中所有的命令,再次进入阻塞态。 此时Task1是优先级最高的就绪态任务,它开始执行。
  • t4:Task1之前被守护任务抢占,对 xTimerStart() 的调用尚未返回。现在开始继续运行次函 数、返回。
  • t5:Task1由于某些原因进入阻塞态,进入阻塞态。Idel任务时优先级最高的就绪态任务,它执 行
    在这里插入图片描述

注意,定时器的超时时间是基于调用 xTimerStart() 的时刻tX,而不是基于守护任务处理命令的时刻 tY。假设超时时间是10个Tick,超时时间是"tX+10",而非"tY+10"

2.3 回调函数

定时器的回调函数的原型如下:

void ATimerCallback( TimerHandle_t xTimer );

定时器的回调函数是在`守护任务中被调用的,守护任务不是专为某个定时器服务的,它还要处理其他定 时器。

所以,定时器的回调函数不要影响其他人:

  • 回调函数要尽快实行,不能进入阻塞状态
  • 不要调用会导致阻塞的API函数,比如 vTaskDelay()
  • 可以调用 xQueueReceive() 之类的函数,但是超时时间要设为0:即刻返回,不可阻塞

三、软件定时器的函数

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

在这里插入图片描述

在这里插入图片描述

3.1 创建

要使用定时器,需要先创建它,得到它的句柄。 有两种方法创建定时器:动态分配内存、静态分配内存。函数原型 如下:

/* 使用动态分配内存的方法创建定时器
* pcTimerName:定时器名字, 用处不大, 尽在调试时用到
* xTimerPeriodInTicks: 周期, 以Tick为单位
* uxAutoReload: 类型, pdTRUE表示自动加载, pdFALSE表示一次性
* pvTimerID: 回调函数可以使用此参数, 比如分辨是哪个定时器
* pxCallbackFunction: 回调函数
* 返回值: 成功则返回TimerHandle_t, 否则返回NULL
*/
TimerHandle_t xTimerCreate( const char * const pcTimerName,const TickType_t xTimerPeriodInTicks,const UBaseType_t uxAutoReload,void * const pvTimerID,TimerCallbackFunction_t pxCallbackFunction );
/* 使用静态分配内存的方法创建定时器
回调函数的类型是:
10.3.2 删除
动态分配的定时器,不再需要时可以删除掉以回收内存。删除函数原型如下:
定时器的很多API函数,都是通过发送"命令"到命令队列,由守护任务来实现。
如果队列满了,"命令"就无法即刻写入队列。我们可以指定一个超时时间 xTicksToWait ,等待一会。
10.3.3 启动/停止
启动定时器就是设置它的状态为运行态(Running、Active)。
停止定时器就是设置它的状态为冬眠(Dormant),让它不能运行。
涉及的函数原型如下:
* pcTimerName:定时器名字, 用处不大, 尽在调试时用到
* xTimerPeriodInTicks: 周期, 以Tick为单位
* uxAutoReload: 类型, pdTRUE表示自动加载, pdFALSE表示一次性
* pvTimerID: 回调函数可以使用此参数, 比如分辨是哪个定时器
* pxCallbackFunction: 回调函数
* pxTimerBuffer: 传入一个StaticTimer_t结构体, 将在上面构造定时器
* 返回值: 成功则返回TimerHandle_t, 否则返回NULL
*/
TimerHandle_t xTimerCreateStatic(const char * const pcTimerName,TickType_t xTimerPeriodInTicks,UBaseType_t uxAutoReload,void * pvTimerID,TimerCallbackFunction_t pxCallbackFunction,StaticTimer_t *pxTimerBuffer );

回调函数的类型是:

void ATimerCallback( TimerHandle_t xTimer );
typedef void (* TimerCallbackFunction_t)( TimerHandle_t xTimer );

3.2 删除

动态分配的定时器,不再需要时可以删除掉以回收内存。删除函数原型如下:

/* 删除定时器
* xTimer: 要删除哪个定时器
* xTicksToWait: 超时时间
* 返回值: pdFAIL表示"删除命令"在xTicksToWait个Tick内无法写入队列
* pdPASS表示成功
*/
BaseType_t xTimerDelete( TimerHandle_t xTimer, TickType_t xTicksToWait );

定时器的很多API函数,都是通过发送"命令"到命令队列,由守护任务来实现。 如果队列满了,"命令"就无法即刻写入队列。我们可以指定一个超时时间 xTicksToWait ,等待一会。

3.3 启动/停止

启动定时器就是设置它的状态为运行态(Running、Active)

停止定时器就是设置它的状态为冬眠(Dormant)

让它不能运行。 涉及的函数原型如下:

/* 启动定时器
* xTimer: 哪个定时器
* xTicksToWait: 超时时间
* 返回值: pdFAIL表示"启动命令"在xTicksToWait个Tick内无法写入队列
* pdPASS表示成功
*/
BaseType_t xTimerStart( TimerHandle_t xTimer, TickType_t xTicksToWait );
/* 启动定时器(ISR版本)
* xTimer: 哪个定时器
* pxHigherPriorityTaskWoken: 向队列发出命令使得守护任务被唤醒,
* 如果守护任务的优先级比当前任务的高,
* 则"*pxHigherPriorityTaskWoken = pdTRUE",
* 表示需要进行任务调度
* 返回值: pdFAIL表示"启动命令"无法写入队列
* pdPASS表示成功
*/
BaseType_t xTimerStartFromISR( TimerHandle_t xTimer,BaseType_t *pxHigherPriorityTaskWoken );
/* 停止定时器
* xTimer: 哪个定时器
* xTicksToWait: 超时时间
* 返回值: pdFAIL表示"停止命令"在xTicksToWait个Tick内无法写入队列
* pdPASS表示成功
*/
BaseType_t xTimerStop( TimerHandle_t xTimer, TickType_t xTicksToWait );
/* 停止定时器(ISR版本)
* xTimer: 哪个定时器
* pxHigherPriorityTaskWoken: 向队列发出命令使得守护任务被唤醒,
* 如果守护任务的优先级比当前任务的高,
* 则"*pxHigherPriorityTaskWoken = pdTRUE",
* 表示需要进行任务调度
* 返回值: pdFAIL表示"停止命令"无法写入队列
* pdPASS表示成功
*/
BaseType_t xTimerStopFromISR( TimerHandle_t xTimer,BaseType_t *pxHigherPriorityTaskWoken );

注意,这些函数的 xTicksToWait 表示的是,把命令写入命令队列的超时时间。命令队列可能已经满 了,无法马上把命令写入队列里,可以等待一会。 xTicksToWait 不是定时器本身的超时时间,不是定时器本身的"周期"。 创建定时器时,设置了它的周期(period)。 xTimerStart() 函数是用来启动定时器。假设调用 xTimerStart() 的时刻是tX,定时器的周期是n,那么在 tX+n 时刻定时器的回调函数被调用。 如果定时器已经被启动,但是它的函数尚未被执行,再次执行 xTimerStart() 函数相当于执行 xTimerReset() ,重新设定它的启动时间。

3.4 复位

从定时器的状态转换图可以知道,使用 xTimerReset() 函数可以让定时器的状态从冬眠态转换为运行 态,相当于使用 xTimerStart() 函数。

如果定时器已经处于运行态,使用 xTimerReset() 函数就相当于重新确定超时时间。假设调用 xTimerReset() 的时刻是tX,定时器的周期是n,那么 tX+n 就是重新确定的超时时间。

复位函数的原型如下:

/* 复位定时器
* xTimer: 哪个定时器
* xTicksToWait: 超时时间
* 返回值: pdFAIL表示"复位命令"在xTicksToWait个Tick内无法写入队列
* pdPASS表示成功
*/
BaseType_t xTimerReset( TimerHandle_t xTimer, TickType_t xTicksToWait );
/* 复位定时器(ISR版本)
* xTimer: 哪个定时器
* pxHigherPriorityTaskWoken: 向队列发出命令使得守护任务被唤醒,
* 如果守护任务的优先级比当前任务的高,
* 则"*pxHigherPriorityTaskWoken = pdTRUE",
* 表示需要进行任务调度
* 返回值: pdFAIL表示"停止命令"无法写入队列
* pdPASS表示成功
*/
BaseType_t xTimerResetFromISR( TimerHandle_t xTimer,BaseType_t *pxHigherPriorityTaskWoken );

3.5 修改周期

从定时器的状态转换图可以知道,使用 xTimerChangePeriod() 函数,处理能修改它的周期外,还可以 让定时器的状态从冬眠态转换为运行态。

修改定时器的周期时,会使用新的周期重新计算它的超时时间。假设调用 xTimerChangePeriod() 函数 的时间tX,新的周期是n,则 tX+n 就是新的超时时间。

相关函数的原型如下:

/* 修改定时器的周期
* xTimer: 哪个定时器
* xNewPeriod: 新周期
* xTicksToWait: 超时时间, 命令写入队列的超时时间
* 返回值: pdFAIL表示"修改周期命令"在xTicksToWait个Tick内无法写入队列
* pdPASS表示成功
*/
BaseType_t xTimerChangePeriod( TimerHandle_t xTimer,TickType_t xNewPeriod,TickType_t xTicksToWait );
/* 修改定时器的周期
* xTimer: 哪个定时器
* xNewPeriod: 新周期
* pxHigherPriorityTaskWoken: 向队列发出命令使得守护任务被唤醒,
* 如果守护任务的优先级比当前任务的高,
* 则"*pxHigherPriorityTaskWoken = pdTRUE",
* 表示需要进行任务调度
* 返回值: pdFAIL表示"修改周期命令"在xTicksToWait个Tick内无法写入队列
* pdPASS表示成功
*/
BaseType_t xTimerChangePeriodFromISR( TimerHandle_t xTimer,TickType_t xNewPeriod,BaseType_t *pxHigherPriorityTaskWoken );

3.6 定时器ID

定时器的结构体如下,里面有一项 pvTimerID ,它就是定时器 ID

在这里插入图片描述

怎么使用定时器ID,完全由程序来决定:

  • 可以用来标记定时器,表示自己是什么定时器
  • 可以用来保存参数,给回调函数使用

它的初始值在创建定时器时由 xTimerCreate() 这类函数传入,后续可以使用这些函数来操作:

  • 更新ID:使用 vTimerSetTimerID() 函数
  • 查询ID:查询pvTimerGetTimerID()函数 这两个函数不涉及命令队列,它们是直接操作定时器结构体。

函数原型如下

/* 获得定时器的ID
* xTimer: 哪个定时器
* 返回值: 定时器的ID
*/
void *pvTimerGetTimerID( TimerHandle_t xTimer );
/* 设置定时器的ID
* xTimer: 哪个定时器
* pvNewID: 新ID
* 返回值: 无
*/
void vTimerSetTimerID( TimerHandle_t xTimer, void *pvNewID );

四、案例

4.1 一般使用

要使用定时器,需要做些准备工作:

/* 1. 工程中 */
添加 timer.c
/* 2. 配置文件FreeRTOSConfig.h中 */
#define configUSE_TIMERS 				1 /* 使能定时器 */
#define configTIMER_TASK_PRIORITY 		31 /* 守护任务的优先级, 尽可能高一些 */
#define configTIMER_QUEUE_LENGTH 		5 /* 命令队列长度 */
#define configTIMER_TASK_STACK_DEPTH 	32 /* 守护任务的栈大小 */
/* 3. 源码中 */
#include "timers.h"

main函数中创建、启动了2个定时器:一次性的、周期

static volatile uint8_t flagONEShotTimerRun = 0;
static volatile uint8_t flagAutoLoadTimerRun = 0;
static void vONEShotTimerFunc( TimerHandle_t xTimer );
static void vAutoLoadTimerFunc( TimerHandle_t xTimer );
/*-----------------------------------------------------------*/
##define mainONE_SHOT_TIMER_PERIOD pdMS_TO_TICKS( 10 )
##define mainAUTO_RELOAD_TIMER_PERIOD pdMS_TO_TICKS( 20 )
int main( void )
{TimerHandle_t xOneShotTimer;TimerHandle_t xAutoReloadTimer;prvSetupHardware();xOneShotTimer = xTimerCreate("OneShot", /* 名字, 不重要 */mainONE_SHOT_TIMER_PERIOD, /* 周期 */pdFALSE, /* 一次性 */0, /* ID */vONEShotTimerFunc /* 回调函数 */);xAutoReloadTimer = xTimerCreate("AutoReload", /* 名字, 不重要 */mainAUTO_RELOAD_TIMER_PERIOD, /* 周期 */pdTRUE, /* 自动加载 */0, /* ID */vAutoLoadTimerFunc /* 回调函数 */);if (xOneShotTimer && xAutoReloadTimer){/* 启动定时器 */xTimerStart(xOneShotTimer, 0);xTimerStart(xAutoReloadTimer, 0);/* 启动调度器 */vTaskStartScheduler();}/* 如果程序运行到了这里就表示出错了, 一般是内存不足 */return 0;
}

这两个定时器的回调函数比较简单:

static void vONEShotTimerFunc( TimerHandle_t xTimer )
{static int cnt = 0;flagONEShotTimerRun = !flagONEShotTimerRun;printf("run vONEShotTimerFunc %d\r\n", cnt++);
}
static void vAutoLoadTimerFunc( TimerHandle_t xTimer )
{static int cnt = 0;flagAutoLoadTimerRun = !flagAutoLoadTimerRun;printf("run vAutoLoadTimerFunc %d\r\n", cnt++);
}

逻辑分析仪如下图所示:

在这里插入图片描述

运行结果如下图所示:

在这里插入图片描述

4.2 消除抖动

在嵌入式开发中,我们使用机械开关时经常碰到抖动问题:引脚电平在短时间内反复变化。

怎么读到确定的按键状态?

  • 连续读很多次,知道数值稳定:浪费CPU资源
  • 使用定时器:要结合中断来使用

对于第2种方法,处理方法如下图所示,按下按键后:

  • t1产生中断,这时不马上确定按键,而是复位定时器,假设周期时20ms,超时时间 为"t1+20ms"

  • 由于抖动,在t2再次产生中断,再次复位定时器,超时时间变为"t2+20ms"

  • 由于抖动,在t3再次产生中断,再次复位定时器,超时时间变为"t3+20ms"

  • 在"t3+20ms"处,按键已经稳定,读取按键值

    在这里插入图片描述

main 函数中创建了一个一次性的定时器,从来处理抖动;创建了一个任务,用来模拟产生抖动。代码 如下:

/*-----------------------------------------------------------*/
static void vKeyFilteringTimerFunc( TimerHandle_t xTimer );
void vEmulateKeyTask( void *pvParameters );
static TimerHandle_t xKeyFilteringTimer;
/*-----------------------------------------------------------*/
#define KEY_FILTERING_PERIOD pdMS_TO_TICKS( 20 )
int main( void )
{prvSetupHardware();xKeyFilteringTimer = xTimerCreate("KeyFiltering", /* 名字, 不重要 */KEY_FILTERING_PERIOD, /* 周期 */pdFALSE, /* 一次性 */0, /* ID */vKeyFilteringTimerFunc /* 回调函数 */);/* 在这个任务中多次调用xTimerReset来模拟按键抖动 */xTaskCreate( vEmulateKeyTask, "EmulateKey", 1000, NULL, 1, NULL );/* 启动调度器 */vTaskStartScheduler();/* 如果程序运行到了这里就表示出错了, 一般是内存不足 */return 0;
}

模拟产生按键:每个循环里调用3次xTimerReset,代码如下:

void vEmulateKeyTask( void *pvParameters )
{int cnt = 0;const TickType_t xDelayTicks = pdMS_TO_TICKS( 200UL );for( ;; ){/* 模拟按键抖动, 多次调用xTimerReset */xTimerReset(xKeyFilteringTimer, 0); cnt++;xTimerReset(xKeyFilteringTimer, 0); cnt++;xTimerReset(xKeyFilteringTimer, 0); cnt++;printf("Key jitters %d\r\n", cnt);vTaskDelay(xDelayTicks);}
}

定时器回调函数代码如下:

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

在人户函数中多次调用xTimerReset,只触发1次定时器回调函数,运行结果如下图所示:

在这里插入图片描述


文章是自己总结而记录,有些知识点没说明白的,请各位看官多多提意见,多多交流,欢迎大家留言
如果技术交流可以加以下群,方便沟通
QQ群:370278903
点击链接加入群聊【蜡笔小芯的嵌入式交流群】
![])

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

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

相关文章

Midjourney Imagine API 申请及使用

Midjourney Imagine API 申请及使用 申请流程 要使用 Midjourney Imagine API,首先可以到 Midjourney Imagine API 页面点击「Acquire」按钮,获取请求所需要的凭证: 如果你尚未登录或注册,会自动跳转到登录页面邀请您来注册和登…

语音转文字服务的调用接口

语音转文字(Speech-to-Text,STT)技术允许将口语化的语音转换成书面文字。以下是一些提供语音转文字服务的调用接口及其特点。北京木奇移动技术有限公司,专业的软件外包开发公司,欢迎交流合作。 1.讯飞开放平台语音转写…

[猫头虎分享21天微信小程序基础入门教程]第1天:微信小程序概述与开发环境搭建教程

第1天:微信小程序概述与开发环境搭建 😺 文章目录 第1天:微信小程序概述与开发环境搭建 😺自我介绍微信小程序概述特点 开发环境搭建步骤1: 注册微信小程序账号步骤2: 安装开发者工具步骤3: 熟悉开发者工具界面 今日学习总结小测试…

炒股开户佣金最低万1和万0.854,融资融券现在利率最低4.0%~5%

​​炒股开户佣金一般是万1和万0.854,万0.854有一定的资金量要求,高于万1的是可以申请降低的。 开户万1佣金和万0.854佣金只需要联系证券公司客户经理协商就行。 开户流程: 1、向客户经理索要开户链接或者扫描二维码、进入申请页面&#x…

本地搭建各大直播平台录屏服务结合内网穿透工具实现远程管理录屏任务

文章目录 1. Bililive-go与套件下载1.1 获取ffmpeg1.2 获取Bililive-go1.3 配置套件 2. 本地运行测试3. 录屏设置演示4. 内网穿透工具下载安装5. 配置Bililive-go公网地址6. 配置固定公网地址 本文主要介绍如何在Windows系统电脑本地部署直播录屏利器Bililive-go,并…

Nachi那智不二越机器人维修技术合集

一、Nachi机械手维护基础知识 1. 定期检查:定期检查机器人的各个部件,如机械手伺服电机、机器人减速器、机械臂传感器等,确保其运行正常。 2. 清洁与润滑:定期清洁Nachi工业机器人表面和内部,并使用合适的润滑油进行润…

VRRP协议-负载分担配置【分别在路由器与交换机上配置】

VRRP在路由器与交换机上的不同配置 一、使用路由器实现负载分担二、使用交换机实现负载分担一、使用路由器实现负载分担 使用R1与R2两台设备分别进行VRRP备份组 VRRP备份组1,虚拟pc1的网关地址10.1.1.254 VRRP备份组2,虚拟pc2的网关地址10.1.1.253 ①备份组1的vrid=1,vrip=…

vue3中使用cherry-markdown

附cherry-markdown官网及api使用示例 官网:https://github.com/Tencent/cherry-markdown/blob/main/README.CN.md api:Cherry Markdown API 考虑到复用性,我在插件的基础上做了二次封装,步骤如下: 1.下载 (一定要指定版本0.8.22,否则会报错: [vitel Internal server e…

初识指针(5)<C语言>

前言 在前几篇文章中,已经介绍了指针一些基本概念、用途和一些不同类型的指针,下文将介绍某些指针类型的运用。本文主要介绍函数指针数组、转移表(函数指针的用途)、回调函数、qsort使用举例等。 函数指针数组 函数指针数组即每个…

深度学习知识点全面总结

ChatGPT 深度学习是一种使用神经网络来模拟人脑处理数据和创建模式的机器学习方法。下面是深度学习的一些主要知识点的总结: 1. 神经网络基础: - 神经元:基本的计算单元,模拟人脑神经元。 - 激活函数:用于增加神…

【CSP CCF记录】数组推导

题目 过程 思路 每次输入一个Bi即可确定一个Ai值,用temp记录1~B[i-1],的最大值分为两种情况: 当temp不等于Bi时,则说明Bi值之前未出现过,Ai必须等于Bi才能满足Bi是Ai前缀最大的定义。当temp等于Bi时,则说…

SpringAMQP-消息转换器

这边发送消息接收消息默认是jdk的序列化方式,发送到服务器是以字节码的形式,我们看不懂也很占内存,所以我们要手动设置一下 我这边设置成json的序列化方式,注意发送方和接收方的序列化方式要保持一致 不然回报错。 引入依赖&#…

重磅推出:135届广交会采购商名录,囊括28个行业数据!

5.5日,第135届中国进出口商品交易会(简称广交会)在广州圆满闭幕,这一全球贸易盛典再次展现了中国制造的卓越实力和文化魅力,成就斐然,吸引了全球目光。 本届广交会线下出口成交额达247亿美元,对…

项目-坦克大战-让坦克动起来

为什么写这个项目 好玩涉及到java各个方面的技术 1,java面向对象 2,多线程 3,文件i/o操作 4,数据库巩固知识 java绘图坐标体系 坐标体系-介绍 坐标体系-像素 计算机在屏幕上显示的内容都是由屏幕上的每一个像素组成的像素是一…

力扣HOT100 - 70. 爬楼梯

解题思路&#xff1a; 动态规划 注意 if 判断和 for 循环 class Solution {public int climbStairs(int n) {if (n < 2) return n;int[] dp new int[n 1];dp[1] 1;dp[2] 2;for (int i 3; i < n; i) {dp[i] dp[i - 1] dp[i - 2];}return dp[n];} }

品牌设计理念和logo设计方法

一 品牌设计的目的 设计是为了传播&#xff0c;让传播速度更快&#xff0c;传播效率更高&#xff0c;减少宣传成本 二 什么是好的品牌设计 好的设计是为了让消费者更容易看懂、记住的设计&#xff0c; 从而辅助传播&#xff0c; 即 看得懂、记得住。 1 看得懂 就是让别人看懂…

树莓派|采集视频并实时显示画面

1、使用SSH远程连接到树莓派 2、新建存放代码的目录 mkdir /home/pi/my_code_directory 3、进入存放代码的目录 cd /home/pi/my_code_directory 4、新建py文件 nano cv2test.py 5、输入代码 import cv2# 打开摄像头 cap cv2.VideoCapture(0)while True:# 读取视频帧ret…

BGP学习二:BGP通告原则,BGP反射器,BGP路径属性细致讲解,新手小白无负担

目录 一.AS号 二.BGP路由生成 1.network 2.import-route引入 三.BGP通告原则 1.只发布最优且有效的路由 2.从EBGP获取的路由&#xff0c;会发布给所有对等体 3.水平分割原则 4.IBGP学习BGP默认不发送给EBGP&#xff0c;但如果也从IGP学习到了这条路由&#xff0c;就发…

java项目之智慧图书管理系统设计与实现(springboot+vue+mysql)

风定落花生&#xff0c;歌声逐流水&#xff0c;大家好我是风歌&#xff0c;混迹在java圈的辛苦码农。今天要和大家聊的是一款基于springboot的智慧图书管理系统设计与实现。项目源码以及部署相关请联系风歌&#xff0c;文末附上联系信息 。 项目简介&#xff1a; 智慧图书管理…

新闻资讯微信小程序开发后端+php【附源码,文档说明】

博主介绍&#xff1a;✌IT徐师兄、7年大厂程序员经历。全网粉丝15W、csdn博客专家、掘金/华为云//InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精彩专栏推荐订阅&#x1f447;&#x1f3…