04.FreeRTOS任务创建

04. FreeRTOS任务创建与任务删除

1. FreeRTOS创建和删除任务相关API函数

函数描述
xTaskCreate()动态方式创建任务
xTaskCreateStatic()静态方式创建任务
xTaskCreateRestricted()动态方式创建使用 MPU 限制的任务
xTaskCreateRestrictedStatic()静态方式创建使用 MPU 限制的任务
vTaskDelete()删除任务

在这里插入图片描述

  1. 函数xTaskCreate()

    动态方式创建函数,任务的任务控制块及栈空间所需的内存,均由FreeRTOS从FreeRTOS管理的堆中分配,若使用此函数,需要在FreeRTOSConfig.h文件中将宏configSUPPORT_DYNAMIC_ALLOCATION配置为1。此函数创建的任务会立刻进入就绪态,由任务调度器运行。函数原型如下所示:

    在这里插入图片描述
    在这里插入图片描述

  2. 函数xTaskCreateStatic()

    静态方式创建任务,任务的任务控制块及任务的栈空间所需的内存,需要由用户分配提供,若使用此函数,需要在FreeRTOSConfig.h文件中将宏 configSUPPORT_STATIC_ALLOCATION配置为1。此函数创建的任务会立刻进入就绪态,由任务调度器调度运行。函数原型如下所示:

    在这里插入图片描述
    在这里插入图片描述

  3. 函数vTaskDelete()

    此函数用于删除已被创建的任务,被删除的任务将被从就绪态任务列表、阻塞态任务列表、挂起态任务列表和事件列表中移除,要注意的是,空闲任务会负责释放被删除任务中由系统分配的内存,但是由用户在任务删除前申请的内存,则需要由用户在任务被删除前提前释放,否则将导致内存泄露。若使用此函数,需要在FreeRTOSConfig.h文件中将宏INCLUDE_vTaskDelete配置为1。函数原型如下所示:

    在这里插入图片描述

2. 动态创建任务及具体实现

  • 步骤:

    1. 将宏configSUPPORT_DYNAMIC_ALLOCATION 配置为 1
    2. 定义函数入口参数
    3. 编写任务函数
  • 动态创建任务函数内部实现:

    1. 申请堆栈内存和任务控制块内存
    2. TCB结构体成员赋值
    3. 添加新任务到就绪列表中
  • 任务控制块:

    在这里插入图片描述

  • 具体代码实现:

    本实验共创建6个任务,1个开始任务和5个不同功能的任务函数

    任务优先级、任务堆栈大小、任务句柄:

    #define START_TASK_PRIO       1
    #define START_TASK_STACK_SIZE 128
    TaskHandle_t start_task_handler;#define TASK1_TASK_PRIO       2
    #define TASK1_TASK_STACK_SIZE 128
    TaskHandle_t task1_task_handler;#define TASK2_TASK_PRIO       3
    #define TASK2_TASK_STACK_SIZE 128
    TaskHandle_t task2_task_handler;#define TASK3_TASK_PRIO       4
    #define TASK3_TASK_STACK_SIZE 128
    TaskHandle_t task3_task_handler;#define TASK4_TASK_PRIO       5
    #define TASK4_TASK_STACK_SIZE 128
    TaskHandle_t task4_task_handler;#define TASK5_TASK_PRIO       6
    #define TASK5_TASK_STACK_SIZE 128
    TaskHandle_t task5_task_handler;//LCD刷屏时使用的颜色
    uint16_t lcd_discolor[11] = {WHITE, BLACK, BLUE, RED, MAGENTA, GREEN, CYAN, YELLOW,BROWN, BRRED, GRAY};
    

    主函数入口:

    void freertos_Dynamic_Create(void)
    {lcd_show_string(10, 10, 220, 32, 32, "STM32", RED);lcd_show_string(10, 47, 220, 24, 24, "Task Create&Delete", LIGHTGREEN);lcd_draw_rectangle(5, 110, 115, 314, BLACK);lcd_draw_rectangle(125, 110, 234, 314, BLACK);lcd_draw_line(5, 130, 115, 130, BLACK);lcd_draw_line(125, 130, 234, 130, BLACK);lcd_show_string(15, 80, 110, 16, 16, "Task1: 000", GREEN);lcd_show_string(135, 80, 110, 16, 16, "Task2: 000", GREEN);lcd_show_string(15, 111, 110, 16, 16, "Task4: 000", BLUE);lcd_show_string(135, 111, 110, 16, 16, "Task5: 000", BLUE);xTaskCreate((TaskFunction_t        )   start_task,           //指向任务函数的指针(char *                )   "start_task",         //任务名称(configSTACK_DEPTH_TYPE)   START_TASK_STACK_SIZE,//任务堆栈大小,字节为单位(void *                )   NULL,                 //传递给任务函数的参数(UBaseType_t           )   START_TASK_PRIO,      //任务优先级(TaskHandle_t *        )   &start_task_handler    //任务句柄:任务控制块);vTaskStartScheduler();  //开启任务调度
    }
    

    开始任务:

    void start_task(void* pvParamter)
    {taskENTER_CRITICAL();   // 进入临界区 xTaskCreate((TaskFunction_t        )   task1,                 //指向任务函数的指针(char *                )   "task1",               //任务名称(configSTACK_DEPTH_TYPE)   TASK1_TASK_STACK_SIZE, //任务堆栈大小,字节为单位(void *                )   NULL,                  //传递给任务函数的参数(UBaseType_t           )   TASK1_TASK_PRIO,       //任务优先级(TaskHandle_t *        )   &task1_task_handler    //任务句柄:任务控制块);xTaskCreate((TaskFunction_t        )   task2,                 //指向任务函数的指针(char *                )   "task2",               //任务名称(configSTACK_DEPTH_TYPE)   TASK2_TASK_STACK_SIZE, //任务堆栈大小,字节为单位(void *                )   NULL,                  //传递给任务函数的参数(UBaseType_t           )   TASK2_TASK_PRIO,       //任务优先级(TaskHandle_t *        )   &task2_task_handler    //任务句柄:任务控制块);	xTaskCreate((TaskFunction_t        )   task3,                  //指向任务函数的指针(char *                )   "task3",                //任务名称(configSTACK_DEPTH_TYPE)   TASK3_TASK_STACK_SIZE,  //任务堆栈大小,字节为单位(void *                )   NULL,                   //传递给任务函数的参数(UBaseType_t           )   TASK3_TASK_PRIO,        //任务优先级(TaskHandle_t *        )   &task3_task_handler     //任务句柄:任务控制块);xTaskCreate((TaskFunction_t        )   task4,                  //指向任务函数的指针(char *                )   "task4",                //任务名称(configSTACK_DEPTH_TYPE)   TASK4_TASK_STACK_SIZE,  //任务堆栈大小,字节为单位(void *                )   NULL,                   //传递给任务函数的参数(UBaseType_t           )   TASK4_TASK_PRIO,        //任务优先级(TaskHandle_t *        )   &task4_task_handler     //任务句柄:任务控制块);xTaskCreate((TaskFunction_t        )   task5,                  //指向任务函数的指针(char *                )   "task5",                //任务名称(configSTACK_DEPTH_TYPE)   TASK5_TASK_STACK_SIZE,  //任务堆栈大小,字节为单位(void *                )   NULL,                   //传递给任务函数的参数(UBaseType_t           )   TASK5_TASK_PRIO,        //任务优先级(TaskHandle_t *        )   &task5_task_handler     //任务句柄:任务控制块				);	vTaskDelete(NULL);taskEXIT_CRITICAL();    // 退出临界区 
    }
    

    任务一:实现LED0每500ms翻转一次

    void task1(void* pvParamter)
    {uint32_t task1_num = 0;while(1){printf("task1正在运行!!!\r\n");lcd_show_xnum(71, 80, ++task1_num, 3, 16, 0x80, GREEN);LED0_TOGGLE();vTaskDelay(500);}
    }
    

    任务二:实现LED1每500ms翻转一次

    void task2(void* pvParamter)
    {uint32_t task2_num = 0;while(1){printf("task2正在运行!!!\r\n");lcd_show_xnum(191, 80, ++task2_num, 3, 16, 0x80, GREEN);LED1_TOGGLE();vTaskDelay(500);}
    }
    

    任务三:删除任务一

    void task3(void* pvParamter)
    {uint8_t key = 0;while(1){printf("task3正在运行!!!\r\n");key = key_scan(0);if(key == KEY0){if(task1_task_handler != NULL){printf("删除task1任务!!!\r\n");vTaskDelete(task1_task_handler);task1_task_handler = NULL;}			}vTaskDelay(10);}
    }
    

    任务四:不同颜色填充块一

    void task4(void *pvParameter)
    {uint32_t task4_num = 0;while(1){lcd_fill(6, 131, 114, 313, lcd_discolor[++task4_num % 11]);lcd_show_xnum(71, 111, task4_num, 3, 16, 0x80, BLUE);vTaskDelay(500);}
    }
    

    任务五:不同颜色填充块二

    void task5(void *pvParameter)
    {uint32_t task5_num = 0;while(1){lcd_fill(126, 131, 233, 313, lcd_discolor[11 - (++task5_num % 11)]);lcd_show_xnum(191, 111, task5_num, 3, 16, 0x80, BLUE);vTaskDelay(500);}
    }
    
  • 函数xTaskCreate()的内部实现(源码):

    在这里插入图片描述

    步骤:

    1. 申请堆栈内存(返回首地址)

      pxStack = pvPortMallocStack( ( ( ( size_t ) usStackDepth ) * sizeof( StackType_t ) ) );
      
    2. 申请任务控制块内存(返回首地址)

      pxNewTCB = ( TCB_t * ) pvPortMalloc( sizeof( TCB_t ) ); 
      
    3. 把前面申请的堆栈地址 赋值给控制块的堆栈成员

      pxNewTCB->pxStack = pxStack;
      
    4. 调用prvInitialiseNewTask初始化任务控制块中的成员

      prvInitialiseNewTask( pxTaskCode, pcName, ( uint32_t ) usStackDepth, pvParameters, uxPriority, pxCreatedTask, pxNewTCB, NULL );
      
    5. 调用prvAddNewTaskToReadyList添加新创建的任务到就绪列表

      prvAddNewTaskToReadyList( pxNewTCB );
      

      具体源码:

          BaseType_t xTaskCreate( TaskFunction_t pxTaskCode,const char * const pcName, /*lint !e971 Unqualified char types are allowed for strings and single characters only. */const configSTACK_DEPTH_TYPE usStackDepth,void * const pvParameters,UBaseType_t uxPriority,TaskHandle_t * const pxCreatedTask ){TCB_t * pxNewTCB;BaseType_t xReturn;/* If the stack grows down then allocate the stack then the TCB so the stack* does not grow into the TCB.  Likewise if the stack grows up then allocate* the TCB then the stack.如果堆栈向下扩展,则先分配堆栈然后分配TCB,这样堆栈*不会扩展到TCB。同样,如果堆栈向上扩展,则再分配TCB然后堆栈*/#if ( portSTACK_GROWTH > 0 ){/* Allocate space for the TCB.  Where the memory comes from depends on* the implementation of the port malloc function and whether or not static* allocation is being used. */pxNewTCB = ( TCB_t * ) pvPortMalloc( sizeof( TCB_t ) );if( pxNewTCB != NULL ){/* Allocate space for the stack used by the task being created.* The base of the stack memory stored in the TCB so the task can* be deleted later if required. */pxNewTCB->pxStack = ( StackType_t * ) pvPortMallocStack( ( ( ( size_t ) usStackDepth ) * sizeof( StackType_t ) ) ); /*lint !e961 MISRA exception as the casts are only redundant for some ports. */if( pxNewTCB->pxStack == NULL ){/* Could not allocate the stack.  Delete the allocated TCB. */vPortFree( pxNewTCB );pxNewTCB = NULL;}}}#else /* portSTACK_GROWTH */{StackType_t * pxStack;/* Allocate space for the stack used by the task being created.为正在创建的任务所使用的堆栈分配空间*///1.申请堆栈内存(返回首地址)pxStack = pvPortMallocStack( ( ( ( size_t ) usStackDepth ) * sizeof( StackType_t ) ) ); /*lint !e9079 All values returned by pvPortMalloc() have at least the alignment required by the MCU's stack and this allocation is the stack. */if( pxStack != NULL ){/* Allocate space for the TCB. *///2.申请任务控制块内存(返回首地址)pxNewTCB = ( TCB_t * ) pvPortMalloc( sizeof( TCB_t ) ); /*lint !e9087 !e9079 All values returned by pvPortMalloc() have at least the alignment required by the MCU's stack, and the first member of TCB_t is always a pointer to the task's stack. */if( pxNewTCB != NULL ){/* Store the stack location in the TCB. *///3.把前面申请的堆栈地址 赋值给控制块的堆栈成员pxNewTCB->pxStack = pxStack;}else{/* The stack cannot be used as the TCB was not created.  Free* it again. */vPortFreeStack( pxStack );}}else{pxNewTCB = NULL;}}#endif /* portSTACK_GROWTH */if( pxNewTCB != NULL ){#if ( tskSTATIC_AND_DYNAMIC_ALLOCATION_POSSIBLE != 0 ) /*lint !e9029 !e731 Macro has been consolidated for readability reasons. */{/* Tasks can be created statically or dynamically, so note this* task was created dynamically in case it is later deleted. */pxNewTCB->ucStaticallyAllocated = tskDYNAMICALLY_ALLOCATED_STACK_AND_TCB;}#endif /* tskSTATIC_AND_DYNAMIC_ALLOCATION_POSSIBLE *///4.调用prvInitialiseNewTask初始化任务控制块中的成员prvInitialiseNewTask( pxTaskCode, pcName, ( uint32_t ) usStackDepth, pvParameters, uxPriority, pxCreatedTask, pxNewTCB, NULL );//5.调用prvAddNewTaskToReadyList添加新创建的任务到就绪列表prvAddNewTaskToReadyList( pxNewTCB );xReturn = pdPASS;}else{xReturn = errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY;}return xReturn;}
      

      上述第4步调用prvInitialiseNewTask初始化任务控制块中的成员的步骤:

      1. 用已知值填充堆栈以协助调试,初始化堆栈0xa5

        ( void ) memset( pxNewTCB->pxStack, ( int ) tskSTACK_FILL_BYTE, ( size_t ) ulStackDepth * sizeof( StackType_t ) );
        
      2. 记录栈顶,保存在pxTopOfStack,并8字节向下对齐

        pxTopOfStack = &( pxNewTCB->pxStack[ ulStackDepth - ( uint32_t ) 1 ] );
        pxTopOfStack = ( StackType_t * ) ( ( ( portPOINTER_SIZE_TYPE ) pxTopOfStack ) & ( ~( ( portPOINTER_SIZE_TYPE ) portBYTE_ALIGNMENT_MASK ) ) );
        
      3. 保存任务名字到pxNewTCB->pcTaskName[x]

        for( x = ( UBaseType_t ) 0; x < ( UBaseType_t ) configMAX_TASK_NAME_LEN; x++ )
        {pxNewTCB->pcTaskName[ x ] = pcName[ x ];if( pcName[ x ] == ( char ) 0x00 ){break;}else{mtCOVERAGE_TEST_MARKER();}
        }
        
      4. 保存任务优先级到pxNewTCB->uxPriority中

        pxNewTCB->uxPriority = uxPriority;
        
      5. 设置状态列表项的所属控制块,设置事件列表项的值

        vListInitialiseItem( &( pxNewTCB->xStateListItem ) );
        vListInitialiseItem( &( pxNewTCB->xEventListItem ) );
        
      6. 列表项的插入是从小到大插入,所以这里将越高优先级的任务他的事件列表项值设置越小,这样就可以拍到前面

        listSET_LIST_ITEM_OWNER( &( pxNewTCB->xStateListItem ), pxNewTCB );
        
      7. 调用pxPortInitialiseStack:初始化任务堆栈,用于保存当前任务上下文寄存器信息已备后续任务切换使用

        pxNewTCB->pxTopOfStack = pxPortInitialiseStack( pxTopOfStack, pxTaskCode, pvParameters );
        
      8. 将任务句柄等于任务控制块

        *pxCreatedTask = ( TaskHandle_t ) pxNewTCB;
        

      具体源码:

      static void prvInitialiseNewTask( TaskFunction_t pxTaskCode,const char * const pcName, /*lint !e971 Unqualified char types are allowed for strings and single characters only. */const uint32_t ulStackDepth,void * const pvParameters,UBaseType_t uxPriority,TaskHandle_t * const pxCreatedTask,TCB_t * pxNewTCB,const MemoryRegion_t * const xRegions )
      {StackType_t * pxTopOfStack;UBaseType_t x;#if ( portUSING_MPU_WRAPPERS == 1 )/* Should the task be created in privileged mode? */BaseType_t xRunPrivileged;if( ( uxPriority & portPRIVILEGE_BIT ) != 0U ){xRunPrivileged = pdTRUE;}else{xRunPrivileged = pdFALSE;}uxPriority &= ~portPRIVILEGE_BIT;#endif /* portUSING_MPU_WRAPPERS == 1 *//* Avoid dependency on memset() if it is not required. */#if ( tskSET_NEW_STACKS_TO_KNOWN_VALUE == 1 ){/* Fill the stack with a known value to assist debugging. *///1.用已知值填充堆栈以协助调试,初始化堆栈0xa5( void ) memset( pxNewTCB->pxStack, ( int ) tskSTACK_FILL_BYTE, ( size_t ) ulStackDepth * sizeof( StackType_t ) );}#endif /* tskSET_NEW_STACKS_TO_KNOWN_VALUE *//* Calculate the top of stack address.  This depends on whether the stack* grows from high memory to low (as per the 80x86) or vice versa.* portSTACK_GROWTH is used to make the result positive or negative as required* by the port. */#if ( portSTACK_GROWTH < 0 ){//2.记录栈顶,保存在pxTopOfStackpxTopOfStack = &( pxNewTCB->pxStack[ ulStackDepth - ( uint32_t ) 1 ] );//8字节向下对齐pxTopOfStack = ( StackType_t * ) ( ( ( portPOINTER_SIZE_TYPE ) pxTopOfStack ) & ( ~( ( portPOINTER_SIZE_TYPE ) portBYTE_ALIGNMENT_MASK ) ) ); /*lint !e923 !e9033 !e9078 MISRA exception.  Avoiding casts between pointers and integers is not practical.  Size differences accounted for using portPOINTER_SIZE_TYPE type.  Checked by assert(). *//* Check the alignment of the calculated top of stack is correct. */configASSERT( ( ( ( portPOINTER_SIZE_TYPE ) pxTopOfStack & ( portPOINTER_SIZE_TYPE ) portBYTE_ALIGNMENT_MASK ) == 0UL ) );#if ( configRECORD_STACK_HIGH_ADDRESS == 1 ){/* Also record the stack's high address, which may assist* debugging. */pxNewTCB->pxEndOfStack = pxTopOfStack;}#endif /* configRECORD_STACK_HIGH_ADDRESS */}#else /* portSTACK_GROWTH */{pxTopOfStack = pxNewTCB->pxStack;/* Check the alignment of the stack buffer is correct. */configASSERT( ( ( ( portPOINTER_SIZE_TYPE ) pxNewTCB->pxStack & ( portPOINTER_SIZE_TYPE ) portBYTE_ALIGNMENT_MASK ) == 0UL ) );/* The other extreme of the stack space is required if stack checking is* performed. */pxNewTCB->pxEndOfStack = pxNewTCB->pxStack + ( ulStackDepth - ( uint32_t ) 1 );}#endif /* portSTACK_GROWTH *//* Store the task name in the TCB. */if( pcName != NULL ){//3.保存任务名字到pxNewTCB->pcTaskName[x]for( x = ( UBaseType_t ) 0; x < ( UBaseType_t ) configMAX_TASK_NAME_LEN; x++ ){pxNewTCB->pcTaskName[ x ] = pcName[ x ];/* Don't copy all configMAX_TASK_NAME_LEN if the string is shorter than* configMAX_TASK_NAME_LEN characters just in case the memory after the* string is not accessible (extremely unlikely). */if( pcName[ x ] == ( char ) 0x00 ){break;}else{mtCOVERAGE_TEST_MARKER();}}/* Ensure the name string is terminated in the case that the string length* was greater or equal to configMAX_TASK_NAME_LEN. *///末尾加结束符pxNewTCB->pcTaskName[ configMAX_TASK_NAME_LEN - 1 ] = '\0';}else{/* The task has not been given a name, so just ensure there is a NULL* terminator when it is read out. */pxNewTCB->pcTaskName[ 0 ] = 0x00;}/* This is used as an array index so must ensure it's not too large. */configASSERT( uxPriority < configMAX_PRIORITIES );if( uxPriority >= ( UBaseType_t ) configMAX_PRIORITIES ){uxPriority = ( UBaseType_t ) configMAX_PRIORITIES - ( UBaseType_t ) 1U;}else{mtCOVERAGE_TEST_MARKER();}//4.保存任务优先级到pxNewTCB->uxPriority中pxNewTCB->uxPriority = uxPriority;#if ( configUSE_MUTEXES == 1 ){pxNewTCB->uxBasePriority = uxPriority;pxNewTCB->uxMutexesHeld = 0;}#endif /* configUSE_MUTEXES *///5.设置状态列表项的所属控制块,设置事件列表项的值vListInitialiseItem( &( pxNewTCB->xStateListItem ) );vListInitialiseItem( &( pxNewTCB->xEventListItem ) );/* Set the pxNewTCB as a link back from the ListItem_t.  This is so we can get* back to  the containing TCB from a generic item in a list. *///6.列表项的插入是从小到大插入,所以这里将越高优先级的任务他的事件列表项值设置越小,这样就可以拍到前面listSET_LIST_ITEM_OWNER( &( pxNewTCB->xStateListItem ), pxNewTCB );/* Event lists are always in priority order. */listSET_LIST_ITEM_VALUE( &( pxNewTCB->xEventListItem ), ( TickType_t ) configMAX_PRIORITIES - ( TickType_t ) uxPriority ); /*lint !e961 MISRA exception as the casts are only redundant for some ports. */listSET_LIST_ITEM_OWNER( &( pxNewTCB->xEventListItem ), pxNewTCB );#if ( portCRITICAL_NESTING_IN_TCB == 1 ){pxNewTCB->uxCriticalNesting = ( UBaseType_t ) 0U;}#endif /* portCRITICAL_NESTING_IN_TCB */#if ( configUSE_APPLICATION_TASK_TAG == 1 ){pxNewTCB->pxTaskTag = NULL;}#endif /* configUSE_APPLICATION_TASK_TAG */#if ( configGENERATE_RUN_TIME_STATS == 1 ){pxNewTCB->ulRunTimeCounter = ( configRUN_TIME_COUNTER_TYPE ) 0;}#endif /* configGENERATE_RUN_TIME_STATS */#if ( portUSING_MPU_WRAPPERS == 1 ){vPortStoreTaskMPUSettings( &( pxNewTCB->xMPUSettings ), xRegions, pxNewTCB->pxStack, ulStackDepth );}#else{/* Avoid compiler warning about unreferenced parameter. */( void ) xRegions;}#endif#if ( configNUM_THREAD_LOCAL_STORAGE_POINTERS != 0 ){memset( ( void * ) &( pxNewTCB->pvThreadLocalStoragePointers[ 0 ] ), 0x00, sizeof( pxNewTCB->pvThreadLocalStoragePointers ) );}#endif#if ( configUSE_TASK_NOTIFICATIONS == 1 ){memset( ( void * ) &( pxNewTCB->ulNotifiedValue[ 0 ] ), 0x00, sizeof( pxNewTCB->ulNotifiedValue ) );memset( ( void * ) &( pxNewTCB->ucNotifyState[ 0 ] ), 0x00, sizeof( pxNewTCB->ucNotifyState ) );}#endif#if ( configUSE_NEWLIB_REENTRANT == 1 ){/* Initialise this task's Newlib reent structure.* See the third party link http://www.nadler.com/embedded/newlibAndFreeRTOS.html* for additional information. */_REENT_INIT_PTR( ( &( pxNewTCB->xNewLib_reent ) ) );}#endif#if ( INCLUDE_xTaskAbortDelay == 1 ){pxNewTCB->ucDelayAborted = pdFALSE;}#endif/* Initialize the TCB stack to look as if the task was already running,* but had been interrupted by the scheduler.  The return address is set* to the start of the task function. Once the stack has been initialised* the top of stack variable is updated. */#if ( portUSING_MPU_WRAPPERS == 1 ){/* If the port has capability to detect stack overflow,* pass the stack end address to the stack initialization* function as well. */#if ( portHAS_STACK_OVERFLOW_CHECKING == 1 ){#if ( portSTACK_GROWTH < 0 ){pxNewTCB->pxTopOfStack = pxPortInitialiseStack( pxTopOfStack, pxNewTCB->pxStack, pxTaskCode, pvParameters, xRunPrivileged );}#else /* portSTACK_GROWTH */{pxNewTCB->pxTopOfStack = pxPortInitialiseStack( pxTopOfStack, pxNewTCB->pxEndOfStack, pxTaskCode, pvParameters, xRunPrivileged );}#endif /* portSTACK_GROWTH */}#else /* portHAS_STACK_OVERFLOW_CHECKING */{pxNewTCB->pxTopOfStack = pxPortInitialiseStack( pxTopOfStack, pxTaskCode, pvParameters, xRunPrivileged );}#endif /* portHAS_STACK_OVERFLOW_CHECKING */}#else /* portUSING_MPU_WRAPPERS */{/* If the port has capability to detect stack overflow,* pass the stack end address to the stack initialization* function as well. */#if ( portHAS_STACK_OVERFLOW_CHECKING == 1 ){#if ( portSTACK_GROWTH < 0 ){pxNewTCB->pxTopOfStack = pxPortInitialiseStack( pxTopOfStack, pxNewTCB->pxStack, pxTaskCode, pvParameters );}#else /* portSTACK_GROWTH */{pxNewTCB->pxTopOfStack = pxPortInitialiseStack( pxTopOfStack, pxNewTCB->pxEndOfStack, pxTaskCode, pvParameters );}#endif /* portSTACK_GROWTH */}#else /* portHAS_STACK_OVERFLOW_CHECKING */{//7.调用pxPortInitialiseStack:初始化任务堆栈,用于保存当前任务上下文寄存器信息已备后续任务切换使用pxNewTCB->pxTopOfStack = pxPortInitialiseStack( pxTopOfStack, pxTaskCode, pvParameters );}#endif /* portHAS_STACK_OVERFLOW_CHECKING */}#endif /* portUSING_MPU_WRAPPERS */if( pxCreatedTask != NULL ){/* Pass the handle out in an anonymous way.  The handle can be used to* change the created task's priority, delete the created task, etc.*///8.将任务句柄等于任务控制块*pxCreatedTask = ( TaskHandle_t ) pxNewTCB;}else{mtCOVERAGE_TEST_MARKER();}
      }
      

      上述第5步调用prvAddNewTaskToReadyList添加新创建的任务到就绪列表的步骤:

      1. 记录任务数量uxCurrentNumberOfTasks++

        uxCurrentNumberOfTasks++;
        
      2. 判断新创建的任务是否为第一个任务

        //如果创建的是第一个任务,初始化任务列表prvInitialiseTaskLists
        if( pxCurrentTCB == NULL )
        {pxCurrentTCB = pxNewTCB;if( uxCurrentNumberOfTasks == ( UBaseType_t ) 1 ){ prvInitialiseTaskLists();}else{mtCOVERAGE_TEST_MARKER();}
        }
        //如果创建的不是第一个任务,并且调度器还未开始启动,比较新任务与正在执行的任务
        //优先级大小,新任务优先级大的话,将当前控制块重新指向新的控制块
        else
        {if( xSchedulerRunning == pdFALSE ){if( pxCurrentTCB->uxPriority <= pxNewTCB->uxPriority ){pxCurrentTCB = pxNewTCB;}else{mtCOVERAGE_TEST_MARKER();}}else{mtCOVERAGE_TEST_MARKER();}
        }
        
      3. 将新的任务控制块添加到就绪列表中,使用函数prvAddTaskToReadyList。将uxTopReadyPriority相应bit置一,表示相应优先级有就绪任务,比如任务优先级为5,就将该变量的位5置一,方便后续任务切换判断,对应的就绪列表是否有任务存在。

        prvAddTaskToReadyList( pxNewTCB );
        
      4. 如果调度器已经开始运行,并且新任务的优先级更大的话,进行一次任务切换

        if( xSchedulerRunning != pdFALSE )
        {if( pxCurrentTCB->uxPriority < pxNewTCB->uxPriority ){taskYIELD_IF_USING_PREEMPTION();}else{mtCOVERAGE_TEST_MARKER();}
        }
        else
        {mtCOVERAGE_TEST_MARKER();
        }
        

      具体源码:

      static void prvAddNewTaskToReadyList( TCB_t * pxNewTCB )
      {/* Ensure interrupts don't access the task lists while the lists are being* updated. */taskENTER_CRITICAL();{//1.记录任务数量uxCurrentNumberOfTasks++uxCurrentNumberOfTasks++;//2.判断新创建的任务是否为第一个任务//如果创建的是第一个任务,初始化任务列表prvInitialiseTaskListsif( pxCurrentTCB == NULL ){/* There are no other tasks, or all the other tasks are in* the suspended state - make this the current task. */pxCurrentTCB = pxNewTCB;if( uxCurrentNumberOfTasks == ( UBaseType_t ) 1 ){/* This is the first task to be created so do the preliminary* initialisation required.  We will not recover if this call* fails, but we will report the failure. */prvInitialiseTaskLists();}else{mtCOVERAGE_TEST_MARKER();}}//如果创建的不是第一个任务,并且调度器还未开始启动,比较新任务与正在执行的任务//优先级大小,新任务优先级大的话,将当前控制块重新指向新的控制块else{/* If the scheduler is not already running, make this task the* current task if it is the highest priority task to be created* so far. */if( xSchedulerRunning == pdFALSE ){if( pxCurrentTCB->uxPriority <= pxNewTCB->uxPriority ){pxCurrentTCB = pxNewTCB;}else{mtCOVERAGE_TEST_MARKER();}}else{mtCOVERAGE_TEST_MARKER();}}uxTaskNumber++;#if ( configUSE_TRACE_FACILITY == 1 ){/* Add a counter into the TCB for tracing only. */pxNewTCB->uxTCBNumber = uxTaskNumber;}#endif /* configUSE_TRACE_FACILITY */traceTASK_CREATE( pxNewTCB );//3.将新的任务控制块添加到就绪列表中,使用函数prvAddTaskToReadyList//将uxTopReadyPriority相应bit置一,表示相应优先级有就绪任务,比如任务优先级为5,就将该//变量的位5置一,方便后续任务切换判断,对应的就绪列表是否有任务存在prvAddTaskToReadyList( pxNewTCB );portSETUP_TCB( pxNewTCB );}taskEXIT_CRITICAL();//4.如果调度器已经开始运行,并且新任务的优先级更大的话,进行一次任务切换if( xSchedulerRunning != pdFALSE ){/* If the created task is of a higher priority than the current task* then it should run now. */if( pxCurrentTCB->uxPriority < pxNewTCB->uxPriority ){taskYIELD_IF_USING_PREEMPTION();}else{mtCOVERAGE_TEST_MARKER();}}else{mtCOVERAGE_TEST_MARKER();}
      }
      
  • 实验结果:
    一共有五个任务,任务点亮LED0;任务二点亮LED;任务三删除任务1;任务四填充块一;任务五填充块二。从实验结果可以看出,LED0和LED1同时闪烁,间隔500ms;两个填充块已填充不同的颜色;当按键KEY0按下时,任务1被删除,LED0停止闪烁。

3. 静态创建任务及具体实现

  • 步骤:

    1. 需将宏configSUPPORT_STATIC_ALLOCATION 配置为 1

      // 1: 支持静态申请内存, 默认: 0 
      #define configSUPPORT_STATIC_ALLOCATION                 1                       
      
    2. 定义空闲任务和定时器任务的任务堆栈及TCB

      //空闲任务配置
      StaticTask_t idle_task_tcb;
      StackType_t idle_task_stack[configMINIMAL_STACK_SIZE];//软件定时器任务配置
      StaticTask_t timer_task_tcb;
      StackType_t timer_task_stack[configTIMER_TASK_STACK_DPTH];
      
    3. 实现两个接口函数

      /*空闲任务内存分配*/
      void vApplicationGetIdleTaskMemory( StaticTask_t ** ppxIdleTaskTCBBuffer,StackType_t ** ppxIdleTaskStackBuffer,uint32_t * pulIdleTaskStackSize )
      {* ppxIdleTaskTCBBuffer = &idle_task_tcb;* ppxIdleTaskStackBuffer = idle_task_stack;* pulIdleTaskStackSize = configMINIMAL_STACK_SIZE;
      }/*软件定时器内存分配*/
      void vApplicationGetTimerTaskMemory( StaticTask_t ** ppxTimerTaskTCBBuffer,StackType_t ** ppxTimerTaskStackBuffer,uint32_t * pulTimerTaskStackSize )
      {* ppxTimerTaskTCBBuffer = &timer_task_tcb;* ppxTimerTaskStackBuffer = timer_task_stack;* pulTimerTaskStackSize = configTIMER_TASK_STACK_DEPTH;
      }
      
    4. 定义函数入口参数

      freertos_Static_Create();
      
    5. 编写任务函数

      #define START_TASK_PRIO       1
      #define START_TASK_STACK_SIZE 128
      TaskHandle_t start_task_handler;
      StackType_t start_task_stack[START_TASK_STACK_SIZE];
      StaticTask_t start_task_tcb;#define TASK1_TASK_PRIO       2
      #define TASK1_TASK_STACK_SIZE 128
      TaskHandle_t task1_task_handler;
      StackType_t  task1_task_stack[TASK1_TASK_STACK_SIZE];
      StaticTask_t task1_task_tcb;#define TASK2_TASK_PRIO       3
      #define TASK2_TASK_STACK_SIZE 128
      TaskHandle_t task2_task_handler;
      StackType_t  task2_task_stack[TASK2_TASK_STACK_SIZE];
      StaticTask_t task2_task_tcb;#define TASK3_TASK_PRIO       4
      #define TASK3_TASK_STACK_SIZE 128
      TaskHandle_t task3_task_handler;
      StackType_t  task3_task_stack[TASK3_TASK_STACK_SIZE];
      StaticTask_t task3_task_tcb;void start_task(void * pvParamter)
      {taskENTER_CRITICAL();task1_task_handler = xTaskCreateStatic((TaskFunction_t)  task1,       (char *        )  "task1",         (uint32_t      )   TASK1_TASK_STACK_SIZE,      ( void *       )   NULL,  (UBaseType_t   )   TASK1_TASK_PRIO,(StackType_t * )   task1_task_stack,          (StaticTask_t *)   &task1_task_tcb); task2_task_handler = xTaskCreateStatic((TaskFunction_t)  task2,       (char *        )  "task2",         (uint32_t      )   TASK2_TASK_STACK_SIZE,      ( void *       )   NULL,  (UBaseType_t   )   TASK2_TASK_PRIO,(StackType_t * )   task2_task_stack,          (StaticTask_t *)   &task2_task_tcb);  	task3_task_handler = xTaskCreateStatic((TaskFunction_t)  task3,       (char *        )  "task3",         (uint32_t      )   TASK3_TASK_STACK_SIZE,      ( void *       )   NULL,  (UBaseType_t   )   TASK3_TASK_PRIO,(StackType_t * )   task3_task_stack,          (StaticTask_t *)   &task3_task_tcb);  vTaskDelete(start_task_handler);taskEXIT_CRITICAL();
      }/*任务一:实现LED0每500ms翻转一次*/
      void task1(void* pvParamter)
      {uint32_t task1_num = 0;while(1){printf("task1正在运行!!!\r\n");lcd_show_xnum(71, 80, ++task1_num, 3, 16, 0x80, GREEN);LED0_TOGGLE();vTaskDelay(500);}
      }/*任务二:实现LED1每500ms翻转一次*/
      void task2(void* pvParamter)
      {uint32_t task2_num = 0;while(1){printf("task2正在运行!!!\r\n");lcd_show_xnum(191, 80, ++task2_num, 3, 16, 0x80, GREEN);LED1_TOGGLE();vTaskDelay(500);}
      }/*任务三:删除任务一*/
      void task3(void* pvParamter)
      {uint8_t key = 0;while(1){printf("task3正在运行!!!\r\n");key = key_scan(0);if(key == KEY0){if(task1_task_handler != NULL){printf("删除task1任务!!!\r\n");vTaskDelete(task1_task_handler);task1_task_handler = NULL;}			}vTaskDelay(10);}
      }
      

4. 删除任务及具体实现

在这里插入图片描述

内部实现:

  1. 获取所要删除任务的控制块:通过传入的任务句柄,判断所需要删除哪个任务,NULL代表删除自身

    pxTCB = prvGetTCBFromHandle( xTaskToDelete );
    
  2. 将被删除任务,移除所在列表:将该任务从列表中移除,包括:就绪、阻塞、挂起、事件等列表

    //2.将被删除任务,移除所在列表:将该任务从列表中移除,包括:就绪、阻塞、挂起、事件等列表
    if( uxListRemove( &( pxTCB->xStateListItem ) ) == ( UBaseType_t ) 0 )
    {taskRESET_READY_PRIORITY( pxTCB->uxPriority );
    }
    else
    {mtCOVERAGE_TEST_MARKER();
    }
    
  3. 判断所需要删除的任务

    删除任务自身,需先添加到等待删除列表,内存释放将在空闲任务执行
    if( pxTCB == pxCurrentTCB )
    {vListInsertEnd( &xTasksWaitingTermination, &( pxTCB->xStateListItem ) );++uxDeletedTasksWaitingCleanUp;traceTASK_DELETE( pxTCB );portPRE_TASK_DELETE_HOOK( pxTCB, &xYieldPending );
    }
    //删除其他任务,当前任务数量--
    else
    {--uxCurrentNumberOfTasks;traceTASK_DELETE( pxTCB );prvResetNextTaskUnblockTime();
    }
  4. 更新下一个任务的阻塞超时时间,以防被删除的任务就是下一个阻塞超时的任务

    prvResetNextTaskUnblockTime();
    

具体实现:

    void vTaskDelete( TaskHandle_t xTaskToDelete ){TCB_t * pxTCB;taskENTER_CRITICAL();{/* If null is passed in here then it is the calling task that is* being deleted. *///1.获取所要删除任务的控制块:通过传入的任务句柄,判断所需要删除哪个任务,NULL代表删除自身pxTCB = prvGetTCBFromHandle( xTaskToDelete );/* Remove task from the ready/delayed list. *///2.将被删除任务,移除所在列表:将该任务从列表中移除,包括:就绪、阻塞、挂起、事件等列表if( uxListRemove( &( pxTCB->xStateListItem ) ) == ( UBaseType_t ) 0 ){taskRESET_READY_PRIORITY( pxTCB->uxPriority );}else{mtCOVERAGE_TEST_MARKER();}/* Is the task waiting on an event also? */if( listLIST_ITEM_CONTAINER( &( pxTCB->xEventListItem ) ) != NULL ){( void ) uxListRemove( &( pxTCB->xEventListItem ) );}else{mtCOVERAGE_TEST_MARKER();}/* Increment the uxTaskNumber also so kernel aware debuggers can* detect that the task lists need re-generating.  This is done before* portPRE_TASK_DELETE_HOOK() as in the Windows port that macro will* not return. */uxTaskNumber++;//判断所需要删除的任务//删除任务自身,需先添加到等待删除列表,内存释放将在空闲任务执行if( pxTCB == pxCurrentTCB ){/* A task is deleting itself.  This cannot complete within the* task itself, as a context switch to another task is required.* Place the task in the termination list.  The idle task will* check the termination list and free up any memory allocated by* the scheduler for the TCB and stack of the deleted task. */vListInsertEnd( &xTasksWaitingTermination, &( pxTCB->xStateListItem ) );/* Increment the ucTasksDeleted variable so the idle task knows* there is a task that has been deleted and that it should therefore* check the xTasksWaitingTermination list. */++uxDeletedTasksWaitingCleanUp;/* Call the delete hook before portPRE_TASK_DELETE_HOOK() as* portPRE_TASK_DELETE_HOOK() does not return in the Win32 port. */traceTASK_DELETE( pxTCB );/* The pre-delete hook is primarily for the Windows simulator,* in which Windows specific clean up operations are performed,* after which it is not possible to yield away from this task -* hence xYieldPending is used to latch that a context switch is* required. */portPRE_TASK_DELETE_HOOK( pxTCB, &xYieldPending );}//删除其他任务,当前任务数量--//更新下一个任务的阻塞超时时间,以防被删除的任务就是下一个阻塞超时的任务else{--uxCurrentNumberOfTasks;traceTASK_DELETE( pxTCB );/* Reset the next expected unblock time in case it referred to* the task that has just been deleted. */prvResetNextTaskUnblockTime();}}taskEXIT_CRITICAL();/* If the task is not deleting itself, call prvDeleteTCB from outside of* critical section. If a task deletes itself, prvDeleteTCB is called* from prvCheckTasksWaitingTermination which is called from Idle task. */if( pxTCB != pxCurrentTCB ){prvDeleteTCB( pxTCB );}/* Force a reschedule if it is the currently running task that has just* been deleted. */if( xSchedulerRunning != pdFALSE ){if( pxTCB == pxCurrentTCB ){configASSERT( uxSchedulerSuspended == 0 );portYIELD_WITHIN_API();}else{mtCOVERAGE_TEST_MARKER();}}}

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

如若内容造成侵权/违法违规/事实不符,请联系瞎胡闹网进行投诉反馈,一经查实,立即删除!

相关文章

C# Unity 面向对象补全计划 之 继承(字段与属性)

本文仅作学习笔记与交流&#xff0c;不作任何商业用途&#xff0c;作者能力有限&#xff0c;如有不足还请斧正 本系列旨在通过补全学习之后&#xff0c;给出任意类图都能实现并做到逻辑上严丝合缝 Q&#xff1a;为什么要单讲继承字段与属性&#xff0c;不讲继承方法了吗&#x…

Centos 7配置问题

在VMWare12上面安装Centos 7 Linux虚拟机&#xff0c;在切换到命令界面时&#xff0c;需要登录用户名和密码&#xff0c;但发现输入用户后有字符显示&#xff0c;但是密码没有。 经过一系列查看后&#xff0c;发现这个是Linux的一种机制&#xff0c;即当你输入密码时不显示&…

为什么阿里开发手册不建议使用Date类?

在日常编码中&#xff0c;基本上99%的项目都会有一个DateUtil工具类&#xff0c;而时间工具类里用的最多的就是java.util.Date。 大家都这么写&#xff0c;这还能有问题&#xff1f;&#xff1f; 当你的“默认常识”出现问题&#xff0c;这个打击&#xff0c;就是毁灭性的。 …

Python学习计划——7.2数据可视化

数据可视化是数据分析的重要组成部分&#xff0c;通过图表和图形将数据直观地展示出来&#xff0c;帮助我们发现数据中的模式和趋势。Python中常用的数据可视化库有matplotlib和seaborn。以下是对这些库的详细讲解及可运行的Python案例。 1. matplotlib 库 matplotlib 是一个…

Git 基础操作手册:轻松掌握常用命令

Git 操作完全手册&#xff1a;轻松掌握常用命令 引言一、暂存&#xff1a;git add ✏️二、提交&#xff1a;git commit &#x1f4dd;三、拉取、拉取合并 &#x1f504;四、推送&#xff1a;git push &#x1f310;五、查看状态&#xff1a;git status &#x1f4ca;六、查看历…

数据库管理-第225期 Oracle DB 23.5新特性一览(20240730)

数据库管理225期 2024-07-30 数据库管理-第225期 Oracle DB 23.5新特性一览&#xff08;20240730&#xff09;1 二进制向量维度格式2 RAC上的复制HNSW向量索引3 JSON集合4 JSON_ID SQL函数5 优化的通过网络对NVMe设备的Oracle的原生访问6 DBCA支持PMEM存储7 DBCA支持标准版高可…

[PM]面试题-工作问题

画一个原型需要多久?写一篇PRD文档需求多久? 时间长短取决于项目规模和业务难度, 规模大难度高,就要花费很长的时间, 规模下难度低时间就短, 一般来说, 1-2周的时间就可以完成原型和RED文档 市场需求文档写什么? 从打到下进行编写, 大的方面以市场为主体,包括市场规模, 发…

AI-PaddleOCR2.8在VS2019编译运行基于C++引擎推理CPU版本

1、下载PaddleOCR-release-2.8开源项目 https://github.com/PaddlePaddle/PaddleOCR https://github.com/PaddlePaddle/PaddleOCR/releases https://gitee.com/paddlepaddle/PaddleOCR?_fromgitee_search 2、下载安装Windows预测库 https://paddleinference.paddlepaddle.o…

轻量级服务器资源监控平台Beszel

什么是 Beszel &#xff1f; Beszel 是一个轻量级平台&#xff0c;借助 Beszel&#xff0c;可以访问 CPU 和内存使用情况的历史数据&#xff0c;以及 Docker 容器指标&#xff08;例如特定于容器的 CPU 和内存统计信息&#xff09;。还能收到针对潜在问题的可自定义警报通知&am…

【Docker】安装 Docker(Server-Centos、GUI-Windows11)—— 超详细教程

一、各版本平台支持情况 1、Server 版本 2、桌面版本 二、Server 版本安装&#xff08;Centos&#xff09; 1、安装依赖 &#xff08;1&#xff09;支持的操作系统 CentOS 7&#xff1a;推荐 CentOS 8 (stream) CentOS 9 (stream) &#xff08;2&#xff09;支持的 CPU A…

spring源码 循环依赖

spring框架两大核心&#xff1a;IOC和AOP IOC(Inverse of Control)控制反转 将对象的创建权交给 Spring 容器去创建&#xff0c;利用了工厂模式将对象交给容器管理&#xff0c;只需要在spring配置文件中配置相应的bean&#xff0c;以及设置相关的属性&#xff0c;让spring容器…

华为机试HJ76尼科彻斯定理

华为机试HJ76尼科彻斯定理 题目&#xff1a; 想法&#xff1a; 从题目可以找到规律&#xff0c;输出的第一个奇数为 ( 当前输入数值 − 1 ) 当前输入数值 1 (当前输入数值-1)当前输入数值1 (当前输入数值−1)当前输入数值1&#xff0c;输出是连续的输入数值个数个奇数&#…

具身智能又进一步!卡内基梅隆Meta苏黎世联邦实现虚拟人超灵活抓取

论文链接&#xff1a;https://arxiv.org/pdf/2407.11385 github链接&#xff1a;https://www.zhengyiluo.com/Omnigrasp-Site/ 亮点直击 本文设计了一种灵巧且通用的人形机器人运动表示&#xff0c;这显著提高了样本效率&#xff0c;并使得通过简单而有效的状态和奖励设计来学习…

51单片机嵌入式开发:22、STC89C52R控制 实现单总线温度传感器DS18b20的温度读取

STC89C52R控制 实现单总线温度传感器DS18b20的温度读取 1 概述1.1 介绍1.2 特点1.3 应用领域 2 DS18B20原理详解2.1 内部机理2.2 读写时序2.3 DS18B20操作代码程序 3 演示4 总结 配套演示例程 1 概述 DS18B20是一款数字温度传感器&#xff0c;由Maxim Integrated&#xff08;美…

【linux】【操作系统】head.s 源码阅读

head.s是Intel x86架构下的汇编语言代码&#xff0c;用于设置操作系统的内存管理和中断处理。主要完成以下内容&#xff1a; 设置数据段、代码段、附加段和全局段寄存器为0x10。设置堆栈指针为_stack_start。设置中断描述符表&#xff08;IDT&#xff09;和全局描述符表&#…

负载均衡、软件平滑升级

安装nginx 1.26.1 平滑升级、负载均衡 安装依赖 gcc gcc-c pcre-devel openssl-devel 七层负载均衡配置&#xff1a; [rootf ~]# vim /usr/local/nginx/conf/nginx.conf 43 location / {44 # root html;45 # index index.html index…

airtest的demo实现多设备并行

airtest的demo实现多设备并行 它实现是的获取adb连接上的所有设备&#xff0c;然后在每一台设备上跑给定的测试用例&#xff0c;跑完之后生成单机的测试报告&#xff0c;最后再汇总这些单机测试报告的结果&#xff0c;形成汇总&#xff08;聚合&#xff09;报告&#xff1a; 同…

html+css 实现4角移动悬停按钮

前言&#xff1a;哈喽&#xff0c;大家好&#xff0c;今天给大家分享htmlcss 绚丽效果&#xff01;并提供具体代码帮助大家深入理解&#xff0c;彻底掌握&#xff01;创作不易&#xff0c;如果能帮助到大家或者给大家一些灵感和启发&#xff0c;欢迎收藏关注哦 &#x1f495; 文…

基于funasr+pyaudio实现电脑本地麦克风实时语音识别项目语音转文本python实现

【框架地址】 https://github.com/modelscope/FunASR 【简单介绍】 FunASR是一个功能全面的语音识别工具包&#xff0c;支持多种预训练模型的推理和微调&#xff0c;提供高精度和高效能的语音识别服务。结合PyAudio库&#xff0c;可以实现电脑本地麦克风实时语音识别项目。 …

ZeST:Zero-shot material transfer from a single image

1.Introduction 编辑图像中的物体材料。本文中,给定一个物体的单一图像和另一个材料示例图像,目标是直接在2D中将材料外观从示例转移到目标物体。1.材料迁移的明确方法需要理解示例和输入图像中的许多物体级属性,例如几何形状和光照。随后,将材料信息从这些属性中分离出来,…