FreeRTOS基础(四):静态创建任务

      上一篇博客,我们讲解了FreeRTOS中如何动态创建任务,那么这一讲,我们从实战出发,规范我们在FreeRTOS下的编码风格,掌握静态创建任务的编码风格,达到实战应用!

目录

一、空闲任务和空闲任务钩子函数

1.0 为什么会有空闲任务?

1.1 空闲任务(Idle Task)

1.2 空闲任务钩子函数(Idle Task Hook)

二、静态创建任务的基本步骤

2.1 使能FreeRTOS的API函数

2.2 定义静态创建任务函数的入口参数

2.3 编写任务函数

2.4主函数进行调用

2.5补充

2.6任务执行顺序

四、静态创建任务和删除任务的API函数解析(选学)

五、静态创建任务和动态创建任务的区别

5.1 创建过程上比较

5.2 整体理解

 5.3 应用场合    

六、临界区保护

6.1 概念

6.2 临界区保护实现方法

6.3 使用示例

6.4 注意事项


一、空闲任务和空闲任务钩子函数

1.0 为什么会有空闲任务?

       思考⼀个问题:在我们的Free RTOS中可以将所有任务阻塞吗?

     不能。

①所有任务都阻塞而没有可运行的任务,会导致系统⽆法处理外部响应,这与实时操作系统的设计 理念相悖。

②可能会出现死锁或系统崩溃。 

        因此,必须始终至少有⼀个任务可以进⼊运行状态,这就是空闲任务。

1.1 空闲任务(Idle Task)

     在FreeRTOS中,空闲任务是在我们调用任务调度器函数的内部自动创建的,它是优先级最低的任务。它在系统中没有其他任务可以运行时执行。其主要作用包括:

  1. CPU利用率:空闲任务的运行表明系统没有其他更高优先级的任务需要运行,因此空闲任务的执行时间可以被用来计算CPU的空闲时间,从而得出CPU利用率。
  2. 内存管理:空闲任务负责清理被删除的任务堆栈内存。

注意:空闲任务不需要我们进行创建!!!

      注意:空闲任务它也是一个任务,只不过比较特殊而已,它是任务调度器自动帮我们进行创建的,在静态创建任务时,我们首先使能空闲任务的API函数,

// FreeRTOSConfig.h文件中
#define configUSE_IDLE_HOOK 1

然后自己实现空闲任务的内存分配函数(因为静态创建的时候,是由我们自己进行分配的,分配在bass段,通常为全局数组),


/************************空闲任务配置**************/
StaticTask_t idle_task_tcb;                              //空闲任务控制块
StackType_t  idle_task_stack[configMINIMAL_STACK_SIZE];  //空闲任务栈的大小

/*空闲任务内存分配函数实现*/
void vApplicationGetIdleTaskMemory( StaticTask_t ** ppxIdleTaskTCBBuffer,
                                        StackType_t ** ppxIdleTaskStackBuffer,
                                        uint32_t * pulIdleTaskStackSize )
{
	*ppxIdleTaskTCBBuffer=&idle_task_tcb;
	*ppxIdleTaskStackBuffer=idle_task_stack;
	* pulIdleTaskStackSize=configMINIMAL_STACK_SIZE;
	
}

如果需要在空闲任务中执行特定操作,可以实现空闲任务钩子函数。

// 空闲任务钩子函数
void vApplicationIdleHook(void)
{
    // 在此处添加空闲时要执行的代码
}

      在动态创建任务时,我们不需要实现空闲任务的内存分配函数,因为这是由 FreeRTOS 自动的从 FreeRTOS 管理的堆中分配。即动态任务的分配不需要管空闲任务。

1.2 空闲任务钩子函数(Idle Task Hook)

      空闲任务钩子函数是一个用户定义的函数,它在每次空闲任务运行时被调用。它允许用户在系统空闲时执行一些特定的任务,比如低功耗模式的处理、监控等。

如何使用空闲任务钩子函数

step1、配置宏(使能空闲任务API函数)

step2、实现空闲任务的内存分配函数(注意:只有静态创建任务才需要!)

step3、自定义实现空闲任务钩子函数(根据自己的需求)

//1、FreeRTOSConfig.h文件中
#define configUSE_IDLE_HOOK 1


/************************空闲任务配置**************/
StaticTask_t idle_task_tcb;                              //空闲任务控制块
StackType_t  idle_task_stack[configMINIMAL_STACK_SIZE];  //空闲任务栈的大小

/*2、空闲任务内存分配函数实现*/       只有静态创建任务才需要
void vApplicationGetIdleTaskMemory( StaticTask_t ** ppxIdleTaskTCBBuffer,
                                        StackType_t ** ppxIdleTaskStackBuffer,
                                        uint32_t * pulIdleTaskStackSize )
{
	*ppxIdleTaskTCBBuffer=&idle_task_tcb;
	*ppxIdleTaskStackBuffer=idle_task_stack;
	* pulIdleTaskStackSize=configMINIMAL_STACK_SIZE;
	
}



//3、空闲任务钩子函数具体实现(根据需求)
void vApplicationIdleHook(void)
{
    // 在此处添加空闲时要执行的代码
}

通过上述配置,空闲任务钩子函数将在系统空闲时被调用,执行用户定义的代码。

二、静态创建任务的基本步骤

2.1 使能FreeRTOS的API函数

        同样在使用FreeRTOS任务创建函数之前,我们需要在配置文件里(FreertosConfig.h)使能API函数

  1.  需将宏configSUPPORT_STATIC_ALLOCATION 配置为 1 ,此时便支持静态创建。利用Ctrl+F搜索即可。
  2. 使能空闲任务的API函数,在FreeRTOSConfig.h文件中#define configUSE_IDLE_HOOK 1

 

2.2 定义静态创建任务函数的入口参数

     通过前面的讲解,我们知道动态创建任务的API函数如下:

      与动态创建的任务相比,只是后面两个参数发生了变化,其实这在前面讲过了,这是因为:静态创建任务时,任务的任务控制块以及任务的栈空间所需的内存,需用户分配提供,通常在bass段申请一个足够大的全局数组即可!该函数返回该任务的任务句柄!

其实,我们需要定义的入口参数就是这个API函数的参数,提前定义好,然后传入参数,他就会自动的为我们创建好对应的任务,并且处于一种就绪态。   从上面我们可以看到:

1、任务函数指针:

       其实就是函数名,我们知道函数名就是函数的入口地址,就是一个函数指针

2、任务名字:

        其实也就是函数名对应的字符串,要用双引号括起来

3、任务堆栈大小:

        静态创建任务,任务的任务控制块以及任务的栈空间所需的内存,均需要我们自己进行内存分配,通常在bass段申请一个足够大的全局数组即可!我们需要定义好任务栈的大小,也就是数组的大小!使用宏:

#define     START_TASK_STACK_SIZE  128   //定义堆栈大小为128字(1字等于4字节)

4、传递给任务的参数:

       不需要传参,我们直接给NULL即可;

5、任务优先级:

        我们使用的是硬件的方式,因此,它要在0-31之间,使用宏定义即可:

#define        START_TASK_PRIO         1    //定义任务优先级,0-31根据任务需求

6、任务堆栈地址:

      这个参数就是我们自己申请的任务堆栈的地址,我们使用的是全局数组,使用宏定义即可,需要我们提前定义好,然后传入数组名即可,因为数组名就是地址。

StackType_t    start_task_stack[START_TASK_STACK_SIZE];  //申请的任务堆栈(全局数组)

7、任务控制块地址:

     动态创建任务,任务控制块也是由我们自己进行内存分配的,任务控制块就是一个结构体,因此需要我们提前定义好任务控制块,然后传入任务控制块的地址。使用宏即可:

StaticTask_t   start_task_tcb;   //创建任务控制块结构体

8、任务句柄:

     这个是因为:这个函数的返回值为任务句柄,我们后续对任务的删除等操作,都是通过该任务句柄进行操作,因此,我们需要提前定义好任务句柄,然后接收创建任务的返回值即可对该任务进行操作!使用宏即可:

TaskHandle_t   start_task_handler;    //定义任务句柄(结构体指针)

从上面我们可以知道:其实我们只需要提前利用宏定义好五个参数即可,其他的参数只要任务函数编写好,便可以确定。示例如下:

/**********************START_TASK任务配置******************************/
/***********包括任务堆栈大小、任务优先级、任务句柄、创建任务***********/

#define        START_TASK_STACK_SIZE  128   //定义堆栈大小为128字(1字等于4字节)
#define        START_TASK_PRIO         1    //定义任务优先级,0-31根据任务需求
StackType_t    start_task_stack[START_TASK_STACK_SIZE];  //申请的任务堆栈(全局数组)
StaticTask_t   start_task_tcb;           //申请的任务控制块(创建结构体)
TaskHandle_t   start_task_handler;      //定义任务句柄(结构体指针)
void start_task(void* args);

注意:

  1. 为了编码规范,我们使用的宏都是大写,虽然较长,但是通俗易懂;
  2. 使用API函数进行任务创建,里面的参数需要进行强制转换,以免报错。
  3. 为了任务执行的顺序是按照我们设定好的优先级执行的,我们可以在创建任务的任务中,使用临界段保护,那么在这个任务体中,可以屏蔽中断(中断优先级在5-15之内)比如切换任务的PendSV,此时,我们创建任务的过程中,不会进行任务的调度,然后我们创建任务结束后,在打开临界段保护,此时不会对所有中断进行屏蔽,也就是任务切换PendSV(中断)才会进行任务调度。如下代码所示,在创建任务开始之前和创建任务之后加入,后面详细讲解。
  4. 静态创建任务函数,有返回值,返回任务句柄,用提前定义好的任务句柄接收,后面便可以使用任务句柄操作任务。
#include "stm32f4xx.h"                  // Device header
#include "stdio.h"
#include "FreeRTOS.h"
#include "task.h"
#include "dynamic.h"



/**********************START_TASK任务配置******************************/
/***********包括任务堆栈大小、任务优先级、任务句柄、创建任务***********/

#define        START_TASK_STACK_SIZE  128   //定义堆栈大小为128字(1字等于4字节)
#define        START_TASK_PRIO         1    //定义任务优先级,0-31根据任务需求
TaskHandle_t   start_task_handler;    //定义任务句柄(结构体指针)
StackType_t    start_task_stack[START_TASK_STACK_SIZE];
StaticTask_t   start_task_tcb;
void start_task(void* args);




/**********************TASK1任务配置******************************/
/***********包括任务堆栈大小、任务优先级、任务句柄、创建任务***********/
#define  TASK1_STACK_SIZE  128            //定义堆栈大小为128字(1字等于4字节)
#define  TASK1_PRIO         2             //定义任务优先级,0-31根据任务需求
TaskHandle_t   task1_handler;           //定义任务句柄(结构体指针)
StackType_t    task1_stack[TASK1_STACK_SIZE];
StaticTask_t   task1_tcb;
void task1(void* args);




/**********************TASK2任务配置******************************/
/***********包括任务堆栈大小、任务优先级、任务句柄、创建任务***********/
#define  TASK2_STACK_SIZE  128            //定义堆栈大小为128字(1字等于4字节)
#define  TASK2_PRIO         3             //定义任务优先级,0-31根据任务需求
TaskHandle_t   task2_handler;           //定义任务句柄(结构体指针)
StackType_t    task2_stack[TASK2_STACK_SIZE];
StaticTask_t   task2_tcb;
void task2(void* args);



/**********************TASK3任务配置******************************/
/***********包括任务堆栈大小、任务优先级、任务句柄、创建任务***********/
#define  TASK3_STACK_SIZE  128            //定义堆栈大小为128字(1字等于4字节)
#define  TASK3_PRIO         4            //定义任务优先级,0-31根据任务需求
TaskHandle_t   task3_handler;           //定义任务句柄(结构体指针)
StackType_t    task3_stack[TASK3_STACK_SIZE];
StaticTask_t   task3_tcb;
void task3(void* args);



/*********开始任务用来创建其他三个任务,只创建一次,不能是死循环,同时创建完3个任务后删除任务1本身***********/
void start_task(void* args)
{
	taskENTER_CRITICAL();        /*进入临界区*/
	
	task1_handler = xTaskCreateStatic( (TaskFunction_t)        task1,
                                ( char * )    "task1", 
                                (uint32_t)    TASK1_STACK_SIZE,
                                 (void * )    NULL,
                             (UBaseType_t)    TASK1_PRIO,
                          (StackType_t * )    task1_stack,
                         (StaticTask_t * )    &task1_tcb );
								 
     task2_handler= xTaskCreateStatic( (TaskFunction_t)       task2,
                                ( char * )    "task2", 
                                (uint32_t)    TASK2_STACK_SIZE,
                                 (void * )    NULL,
                             (UBaseType_t)    TASK2_PRIO,
                          (StackType_t * )    task2_stack,
                         (StaticTask_t * )    &task2_tcb );
								 
	task3_handler= xTaskCreateStatic( (TaskFunction_t)       task3,
                                ( char * )    "task3", 
                                (uint32_t)    TASK3_STACK_SIZE,
                                 (void * )    NULL,
                             (UBaseType_t)    TASK3_PRIO,
                          (StackType_t * )    task3_stack,
                         (StaticTask_t * )    &task3_tcb );							 
								 
														 
							
	vTaskDelete(start_task_handler);    //删除开始任务自身,传参NULL或者开始任务句柄
							
	taskEXIT_CRITICAL();   /*退出临界区*/
		

    //临界区内不会进行任务的调度切换,出了临界区才会进行任务调度,抢占式						
}

此外,还要实现空闲任务的内存分配函数,至于空闲任务的钩子函数可根据自己需求是否实现,这里就不实现了。 

/************************空闲任务配置**************/
StaticTask_t idle_task_tcb;                              //空闲任务控制块
StackType_t  idle_task_stack[configMINIMAL_STACK_SIZE];  //空闲任务栈的大小

/*空闲任务内存分配*/
void vApplicationGetIdleTaskMemory( StaticTask_t ** ppxIdleTaskTCBBuffer,
                                        StackType_t ** ppxIdleTaskStackBuffer,
                                        uint32_t * pulIdleTaskStackSize )
{
	*ppxIdleTaskTCBBuffer=&idle_task_tcb;
	*ppxIdleTaskStackBuffer=idle_task_stack;
	* pulIdleTaskStackSize=configMINIMAL_STACK_SIZE;
	
}

2.3 编写任务函数

对每个任务具体实现的功能进行函数的实现:需要注意,任务函数没有返回值并且是死循环的!

/********其余三个任务的任务函数,无返回值且是死循环***********/

/***任务1:实现LED0每500ms翻转一次*******/
void task1(void* args)
{
	while(1)
	{
		 printf("任务1正在运行!\n");
		 GPIO_ToggleBits(GPIOF,GPIO_Pin_9 );
         vTaskDelay(500);       //FreeRTOS自带的延时函数
	
	}
	
	
}

/***任务2:实现LED1每500ms翻转一次*******/
void task2(void* args)
{
	while(1)
	{
		printf("任务2正在运行!\n");
		 GPIO_ToggleBits(GPIOF,GPIO_Pin_10 );
         vTaskDelay(500);       //FreeRTOS自带的延时函数
	}
	
	
}


/***任务3:判断按键KEY0,按下KEY0,任务1删除*******/
void task3(void* args)
{
	while(1)
	{
		 printf("任务3正在运行!\n");
		 if(GPIO_ReadInputDataBit(GPIOE,GPIO_Pin_4)==0)  //表示按键按下
	     {
			   if(task1_handler!=NULL)  //防止重复删除
			   {
			        printf("删除任务1!\n");
		            vTaskDelete(task1_handler);    //删除任务1,传任务1的句柄
				    task1_handler=NULL;
			   }
	     }	  
		 vTaskDelay(10); 
	}
	
	
}

此外,我们再自定义一个入口函数,用来创建开始任务,然后将要创建的任务全部放在这个开始任务中,主函数只需调用这个入口函数,即可在这个开始任务中 , 创建其他的任务,这样做,规范代码,梳理代码逻辑,清晰易懂任务的运行顺序!如下所示:

//FreeRTO入口例程函数,无参数,无返回值,用来创建开始任务
void freertos_demo(void)
{
	//静态创建任务会返回任务句柄
	
	start_task_handler  =  xTaskCreateStatic( (TaskFunction_t)    start_task,
                                ( char * )    "start_task", 
                                (uint32_t)    START_TASK_STACK_SIZE,
                                 (void * )    NULL,
                             (UBaseType_t)    START_TASK_PRIO,
                          (StackType_t * )    start_task_stack,
                         (StaticTask_t * )    &start_task_tcb );
							
							
	vTaskStartScheduler();  //开启任务调度器
	
}

2.4主函数进行调用

      在完成上述的编写后,主函数内部只需要引入对应的头文件,然后在函数内部调用相应的函数对使用到的外设进行初始化,然后调用入口函数即可进行按照我们设定的优先级进行任务的调度,如下所示:

#include "stm32f4xx.h"                  // Device header
#include "stdio.h"
#include "myled.h"
#include "mykey.h"
#include "myusart.h"
 
#include "FreeRTOS.h"
#include "task.h"
#include "dynamic.h"    //可以用来单独存放任务函数的声明以及配置相关的宏定义,然后直接引入头文件使用
 
extern TaskHandle_t Start_Handle;  
/*使用任务句柄可以对任务操作,如果没有添加上面的单独头文件存放,
那么使用其他文件的全局变量利用extern关键字引入即可。*/
 
int main(void)
{
    //1、外设初始化
     My_UsartInit();
     LED_Init();
     KEY_Init();
    
	
	//2、调用入口函数
     freertos_demo();
	
 
    
}

2.5补充

    为进行模块化的编程,我们可以将创建相应的头文件可以用来单独存放任务函数的声明以及任务配置相关的宏定义,然后在主函数直接引入头文件使用即可,这样工程结构清晰易懂!

2.6任务执行顺序

编写完程序后,一定要进行验证,验证程序是否按照我们设定的顺序及进行执行,类似于操作系统的线程同步问题!

     首先主函数调用入口函数,在入口函数内部创建开始任务函数,该开始任务进入就绪状态,启用任务调度器,调度器启动后,FreeRTOS 将接管系统控制,开始调度任务。此时CPU就会去执行开始任务,然后,在开始任务中创建三个任务,注意:由于使用了临界保护:taskENTER_CRITICAL();        /*进入临界区*/  它会对5-15优先级的中断进行屏蔽,即不会发生作用,其中PendSV是用来任务切换的内核中断,它的优先级是13,因此,会被屏蔽,也就是说,我在创建三个任务的过程中,不会进行其他任务的切换,保证我的开始任务创建其他的三个任务不会被打断!!!创建完三个任务后,它们都进入了就绪态,然后,再删除这个开始任务(因为每个任务只需要创建一次,多次创建占用堆栈内存,造成栈溢出!)此时,我在关闭临界区保护,taskEXIT_CRITICAL();   /*退出临界区*/,也就是打开所有中断,此时PendSV中断就会被打开,按照任务的优先级进行抢占式调度,分别执行任务3、任务2、任务1,在三个任务执行的过程中,加入适当的延时,他就会进行任务的切换,去就绪列表寻找优先级最高的任务去运行!

四、静态创建任务和删除任务的API函数解析(选学)

五、静态创建任务和动态创建任务的区别

5.1 创建过程上比较

       动态创建的任务,任务的任务控制块以及任务的栈空间所需的内存,均由 FreeRTOS 从 FreeRTOS 管理的堆中分配(真正需要我们定义的也就是任务句柄);

       静态创建的任务,任务的任务控制块以及任务的栈空间所需的内存,需用户分配提供。

需要我们额外定义这两个。

 此外,静态创建任务,需要我们实现空闲任务的内存分配函数。

5.2 整体理解

  1.  静态创建任务是在编译时为任务分配内存(全局数组),任务在运行之前已分配内存。这种方法不需要在运行时使用动态内存分配函数,因此更加可靠和节省内存。
  2.  动态创建任务是在运行时通过动态内存分配函数分配任务内存。这种分配方式可以更灵活地适应不同大小和数量的任务。然而,在动态方式下,程序需要在运行时使用动态内存分配函数,这可能会导致内存泄漏和堆碎片等问题。

 

 5.3 应用场合    

       1、在实际的应用中,动态方式创建任务是比较常用的,除非有特殊的需求,一般都会使用动态方式创建任务,动态创建相对简单,更为常用,静态创建:可将任务堆栈放置在特定的内存位置,并且无需关心对内存分配失败的处理。
      2、如果应用程序中的任务数量和大小已知,则可以使用静态方式分配内存,并且无需动态内存分配。 如果应用程序需要更多的灵活性,并且需要在运行时根据需要创建或删除任务,则应使用动态方式分配内存。

六、临界区保护

6.1 概念

      在FreeRTOS(实时操作系统)中,临界区保护是一个关键概念,用于确保共享资源在多任务环境中被安全地访问和修改。临界区保护防止多个任务在同一时间访问共享资源,从而避免数据竞争和不一致的情况。  它可以保护那些不想被打断的程序段,关闭freertos所管理的中断,中断无法打断,滴答中断和PendSV中断无法进行不能实现任务调度 。

6.2 临界区保护实现方法

在FreeRTOS中,临界区保护主要通过以下两种方法实现:

  1. 任务级临界区保护

    • vTaskSuspendAll() 和 xTaskResumeAll():这对函数用于挂起和恢复任务调度。当调用vTaskSuspendAll()时,FreeRTOS内核暂停任务切换,直到调用xTaskResumeAll()。在此期间,任务调度被禁止,确保当前任务独占地执行临界区内的代码。
    • 适用于临界区执行时间较长的场景,因为任务调度被完全禁止。
  2. 中断级临界区保护(常用方法)

    • taskENTER_CRITICAL() 和 taskEXIT_CRITICAL():这对宏用于进入和退出临界区。当调用taskENTER_CRITICAL()时,内核禁用所有中断,确保在临界区内的代码不会被任何中断打断。当调用taskEXIT_CRITICAL()时,恢复中断的状态。
    • 适用于临界区执行时间较短的场景,因为禁用中断的时间较短,能减少系统的中断响应延迟。

6.3 使用示例

任务级临界区保护示例:

void vTaskFunction(void *pvParameters)
{
    // 其他代码
    vTaskSuspendAll(); // 挂起任务调度

    // 临界区代码
    // 访问或修改共享资源
    // 例如:共享变量++
    sharedVariable++;

    xTaskResumeAll(); // 恢复任务调度
    // 其他代码
}

中断级临界区保护示例:

void vTaskFunction(void *pvParameters)
{
    // 其他代码
    taskENTER_CRITICAL(); // 进入临界区,禁用中断

    // 临界区代码
    // 访问或修改共享资源
    // 例如:共享变量++
    sharedVariable++;

    taskEXIT_CRITICAL(); // 退出临界区,恢复中断
    // 其他代码
}

6.4 注意事项

临界区保护的注意事项

  1. 使用范围临界区保护应该仅用于保护需要保护的代码,避免长时间禁用任务调度或中断,影响系统的实时性能。
  2. 开销:禁用任务调度和中断都有一定的开销,特别是禁用中断会影响系统的实时响应,需要慎重使用。
  3. 嵌套:在FreeRTOS中,taskENTER_CRITICAL()taskEXIT_CRITICAL()可以嵌套使用,但要确保成对出现,防止中断长时间被禁用。

       通过以上的介绍,相信你对静态创建任务的过程会有清晰的认识,其实步骤也是非常简单的,接下来去实践吧!熟练后就不难了,万事开头难! 至此,静态创建任务就已经讲解完毕!初次学习,循序渐进,一步步掌握即可!以上就是全部内容!请务必掌握,创作不易,欢迎大家点赞加关注评论,您的支持是我前进最大的动力!下期再见!

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

决定短视频打开率的要素:成都鼎茂宏升文化传媒公司

​ 在当下这个短视频盛行的时代,无论是个人创作者还是企业品牌,都希望通过短视频平台获得更多的曝光和关注。然而,如何让自己的短视频在众多内容中脱颖而出,吸引用户的点击和观看,成为了摆在我们面前的重要问题。成都…

【爬虫工具】油管视频批量采集软件

一、背景介绍 1.1 爬取目标 我用Python独立开发了一款爬虫软件,作用是:通过搜索关键词采集ytb的搜索结果,包含14个关键字段:关键词,页码,视频标题,视频id,视频链接,发布时间,视频时长,频道名称,频道id,频道链接,播放数,点赞数,评…

开源模型应用落地-LangSmith试炼-入门初体验-监控和自动化(五)

一、前言 在许多应用程序中,特别是在大型语言模型(LLM)应用程序中,收集用户反馈以了解应用程序在实际场景中的表现是非常重要的。 LangSmith可以轻松地将用户反馈附加到跟踪数据中。通常最好提供一个简单的机制(如赞成和反对按钮)来收集用户对应用程序响…

工控一体机5寸显示器电容触摸屏(YA05WK)产品规格说明书

如果您对工控一体机有任何疑问或需求,或者对如何集成工控一体机到您的业务感兴趣,可移步控芯捷科技。 一、硬件功能介绍 YA05WK是我公司推出的一款新型安卓屏,4核Cortex-A7 架构,主频1.2GHz的CPU。采用12V供电,标配5寸…

使用QT生成二维码的两种方式

目录 使用QRenCode生成二维码编译生成QRenCode库使用QRenCode结果演示优缺点: 使用QZXing进行二维码的编码和解码编译源码使用QZXing库运行结果优缺点 使用QRenCode生成二维码 编译生成QRenCode库 QRenCode开源库 下载好之后使用cmake-gui打开进行构建生成。 点击…

mathtype7.0产品密钥及2024最新软件激活教程步骤

在数字化教育日益普及的今天,如何有效利用技术工具来提高数学学习的效率和质量,成为了教育工作者和学生共同关注的热点。特别是在处理复杂的数学公式、符号以及方程式时,传统的输入方式往往费时费力,且容易出错。为此,…

如何用python做一个用户登录界面——浔川python社

1 需解决的问题: 1.1如何用python做一个用户登录界面? 1.2需要用到哪些库、模块? 2 问题解决: 2.1 回答 1.1 :合理即可,无标准回答。 2.2 回答 1.2 :tk库(缩写)、GUL界面…

redis基础学习

redis是一个键值对类型的NoSql类型的数据库。 NoSql(Non-relational SQL的缩写,也有人看作是not only sql的缩写)型数据库,具有以下特征: 1、非结构化:几乎没有约束,约束很少,这要看…

【LLM】两篇多模态LLM综述MultiModal Large Language Models

note (一)现有的 MM-LLM 的趋势: (1)从专门强调 MM 理解对特定模态的生成的进展,并进一步演变为任何到任何模态的转换(例如,MiniGPT-4 → MiniGPT-5 → NExT-GPT); (2) 从 MM PT 提…

神经网络与深度学习——第7章 网络优化与正则化

本文讨论的内容参考自《神经网络与深度学习》https://nndl.github.io/ 第7章 网络优化与正则化 网络优化与正则化 网络优化 网络结构多样性 高维变量的非凸优化 神经网络优化的改善方法 优化算法 小批量梯度下降 批量大小选择 学习率调整 学习率衰减 学习率预热 周期性学习率调…

装甲车启动电源的安全性能分析

装甲车辆启动电源是一种为装甲车辆提供启动动力的专业设备。它通常被用于 火箭兵 、步兵战车、装甲运兵车等JS车辆,这些车辆通常需要较高的启动功率来启动其发动机,尤其是装甲车的发动机,由于其功率大,启动对电力要求很高。在现代…

3DMAX一键虚线图形插件DashedShape使用方法

3DMAX一键虚线图形插件使用方法 3dMax一键虚线图形插件,允许从场景中拾取的样条线创建虚线形状。该工具使你能够创建完全自定义的填充图案,为线段设置不同的材质ID,并在视口中进行方便的预览。 【版本要求】 3dMax 2012 – 2025(…

HarmonyOS应用开发学习历程(1)初识DevEco Studio

1.create project Bundle name:包名,标识应用程序,默认应用ID也使用该名 Compile SDK:编译时API版本 2.工程目录 AppScope:应用全局所需资源 entry:应用的主模块,含代码、资源 hvigor&#…

Java多线程问题

线程 何为线程:线程是一个比进程更小的执行单位。一个进程在其执行的过程中可以产生多个线程。与进程不同的是同类的多个线程共享进程的堆和方法区资源,但每个线程有自己的程序计数器、虚拟机栈和本地方法栈,所以系统在产生一个线程&#xf…

OSEK错误处理及跟踪调试

1 前言 如表1所示,OSEK提供了一些特殊的钩子例程(Hook routines),应用层可以在钩子函数中自定义操作,以参与到操作系统的内部处理中。 表1 钩子函数类型 钩子函数例程功能用途ErrorHook用于错误处理StartupHook在系统启…

【论文复现|智能算法改进】基于多策略麻雀搜索算法的机器人路径规划

目录 1.算法原理2.改进点3.结果展示4.参考文献5.代码获取 1.算法原理 【智能算法】麻雀搜索算法(SSA)原理及实现 2.改进点 改进的无限折叠迭代混沌映射 无限折叠迭代映射(ICMIC) 常用于图像加密方向的研究, 基本思想是首先生成[0,1]之间的混沌序列, …

【Qt秘籍】[008]-Qt中的connect函数

在Qt框架中,connect函数是一个非常核心的函数,用于实现信号(Signals)和槽(Slots)之间的连接,它是Qt信号槽机制的关键所在。信号槽机制是一种高级的通信方式,允许对象在状态改变时通知…

掘金AI 商战宝典-系统班:2024掘金AIGC课程(30节视频课)

课程目录 1-第一讲学会向Al提问:万能提问公式_1.mp4 2-第二讲用AI写视频脚本_1.mp4 3-第三讲用AI写视频口播文案_1.mp4 4-第四讲用AI自动做视频(上)_1.mp4 5-第五讲用AI自动做视频(中)_1.mp4 6-第六讲用AI自动做视…

1. Mybatis基础操作

目录 1.1 需求 1.2 准备 1.3 删除 1.3.1 功能实现 1.3.2 日志输入 1.3.3 预编译SQL 1.3.3.1 介绍 1.3.3.2 SQL注入 1.3.3.3 参数占位符 1.4 新增 1.4.1 基本新增 1.4.2 主键返回 1.5 更新 1.6 查询 1.6.1 根据ID查询 1.6.2 数据封装 1.6.3 条件查询 1.6.4 参…

Tree——输出项目的文件结构(Linux)

输出项目中的文件结构可以使用tree命令。tree是一个用于以树状结构显示目录内容的命令行工具。它非常适合快速查看项目的文件结构。安装: sudo apt-get install tree 使用: 在命令行中导航到项目的根目录,输出文件结构。 tree 也可以将结构输…