FreeRTOS源码解析(4)任务状态查询

1.获取任务优先级

  • 功能:查询指定任务的优先级。

  • 参数xTask 为要查询的任务句柄,若为 NULL,则查询当前调用任务的优先级。

  • 返回值:该任务的当前优先级(数值越大,优先级越高)。

  • const 修饰句柄,表明不会通过该句柄修改任务数据。

1.1 源码分析

复制代码
UBaseType_t uxTaskPriorityGet( const TaskHandle_t xTask )
    {
        TCB_t const * pxTCB;
        UBaseType_t uxReturn;

        taskENTER_CRITICAL();       //程序进入临界区
        {
            /* If null is passed in here then it is the priority of the task
             * that called uxTaskPriorityGet() that is being queried. */
            /* 若 xTask 为 NULL 则返回当前任务 TCB;否则根据句柄查找对应 TCB */
            pxTCB = prvGetTCBFromHandle( xTask );
            /* 从 TCB 中直接读取任务的当前调度优先级。
             * 注意:如果任务正持有互斥量并且发生了优先级继承,
             * 此值可能是暂时提升后的优先级,而非任务原本的基础优先级。
             * 基础优先级记录在 pxTCB->uxBasePriority 中。 */
            uxReturn = pxTCB->uxPriority;
        }
        taskEXIT_CRITICAL();       //程序退出临界区

        return uxReturn;
    }

1.2 使用示例

复制代码
    /* 通过句柄,获取任务的优先级 */
    UBaseType_t task_priority = 0;
    task_priority = uxTaskPriorityGet(task1_handle);
    printf("task1任务优先级=%d\r\n", task_priority);

2.获取系统中任务数量

  • 功能 :返回当前系统中已创建且未被删除的任务总数。

  • 返回值uxCurrentNumberOfTasks 的当前值,类型为 UBaseType_t(通常为 uint32_t

2.1 源码分析

复制代码
UBaseType_t uxTaskGetNumberOfTasks( void )
{
    /* A critical section is not required because the variables are of type
     * BaseType_t. */
    /* 该变量是BaseType_t类型,不需要临界区 */
    /* 在 32 位平台上正好是 CPU 的自然字长,且由编译器保证对齐,
     * 所以硬件可用单条指令完成读取,该操作是不可打断的(原子),
     * 不存在读到"半新半旧"撕裂值的可能,因此无需进入临界区。 */
    return uxCurrentNumberOfTasks;
}

2.2 使用示例

复制代码
    /* 获取系统中任务的数量 */
    UBaseType_t task_num = 0;
    task_num = uxTaskGetNumberOfTasks();
    printf("系统中任务数量=%d\r\n", task_num);

3.获取任务状态

  • 功能:查询任务的状态。

  • 参数xTask 为待查任务句柄。

  • 返回值eTaskState 枚举值,包括 eRunningeReadyeBlockedeSuspendedeDeleted

3.1 源码分析

复制代码
eTaskState eTaskGetState( TaskHandle_t xTask )
    {
eTaskState eReturn;                                     //存储最终状态
        List_t const * pxStateList;                     //指向 xStateListItem 当前所在的链表(如就绪列表、延迟列表、挂起列表等)
        List_t const * pxDelayedList;                   //指向当前使用的延迟列表
        List_t const * pxOverflowedDelayedList;         //指向当前使用的溢出延迟列表
        const TCB_t * const pxTCB = xTask;              //指向目标任务的 TCB,防止修改

        /* 断言保证句柄有效,需要用户自定义实现 */
        configASSERT( pxTCB );

        if( pxTCB == pxCurrentTCB )
        {
            /* The task calling this function is querying its own state. */
            /* 如果查询的是当前任务自己,那它必定处于运行态,直接返回 eRunning
             * 这里没有进入临界区,因为当前任务正在运行,其状态不可能同时被改变,读取到的值是确定的 */
            eReturn = eRunning;
        }
        else
        {
            taskENTER_CRITICAL();       //程序进入临界区,确保读取的列表指针和任务状态一致
            {
                /* listLIST_ITEM_CONTAINER 返回状态列表项 xStateListItem 当前所在的链表 */
                pxStateList = listLIST_ITEM_CONTAINER( &( pxTCB->xStateListItem ) );
                /* 保存两个延迟列表的当前指针,用于后续比较 */
                pxDelayedList = pxDelayedTaskList;
                pxOverflowedDelayedList = pxOverflowDelayedTaskList;
            }
            taskEXIT_CRITICAL();        //程序退出临界区

            /* 判断是否处于阻塞状态 */
            /* 如果状态列表项位于任一个延迟列表中,说明任务正在延时(vTaskDelay)或等待事件超时,即处于阻塞态 */
            if( ( pxStateList == pxDelayedList ) || ( pxStateList == pxOverflowedDelayedList ) )
            {
                /* The task being queried is referenced from one of the Blocked
                 * lists. */
                eReturn = eBlocked;
            }

            /* 判断挂起(Suspended)或挂起下的特殊情况(阻塞于通知) */
            #if ( INCLUDE_vTaskSuspend == 1 )
                else if( pxStateList == &xSuspendedTaskList )       //判断任务的状态列表项在挂起列表中
                {
                    /* The task being queried is referenced from the suspended
                     * list.  Is it genuinely suspended or is it blocked
                     * indefinitely? */
                    /* 处理任务因等待通知而同时位于挂起列表的情况 */
                    if( listLIST_ITEM_CONTAINER( &( pxTCB->xEventListItem ) ) == NULL )
                    {
                        #if ( configUSE_TASK_NOTIFICATIONS == 1 )
                        {
                            BaseType_t x;

                            /* The task does not appear on the event list item of
                             * and of the RTOS objects, but could still be in the
                             * blocked state if it is waiting on its notification
                             * rather than waiting on an object.  If not, is
                             * suspended. */
                            /* 若 xEventListItem 容器为 NULL,正常情况下任务可以判定为 eSuspended。但 FreeRTOS 引入了一个特殊机制:
                             * 任务通知可以导致任务在无事件列表项的情况下依然处于阻塞态。当任务调用 xTaskNotifyWait 等待通知时,
                             * 它会被置入延迟列表或直接挂起?其实等待通知的任务如果设置了超时,会进入延迟列表;
                             * 如果无限期等待,则会进入挂起列表(vTaskSuspend 并非唯一进入挂起列表的路径,
                             * xTaskNotifyWait 在无限等待时也会将任务放到挂起列表,同时设置 ucNotifyState 为 taskWAITING_NOTIFICATION)。
                             * 因此,当看到任务在挂起列表中且事件列表项为空时,还需遍历通知状态数组,检查是否有任何一项为 taskWAITING_NOTIFICATION。
                             * 如果有,则任务实际上是在阻塞等待通知,应返回 eBlocked;否则才是真正的挂起。 */
                            eReturn = eSuspended;

                            for( x = 0; x < configTASK_NOTIFICATION_ARRAY_ENTRIES; x++ )
                            {
                                if( pxTCB->ucNotifyState[ x ] == taskWAITING_NOTIFICATION )
                                {
                                    eReturn = eBlocked;
                                    break;
                                }
                            }
                        }
                        #else /* if ( configUSE_TASK_NOTIFICATIONS == 1 ) */
                        {
                            eReturn = eSuspended;       //未开启任务通知则直接标记挂起
                        }
                        #endif /* if ( configUSE_TASK_NOTIFICATIONS == 1 ) */
                    }
                    else
                    {
                        /* 若事件列表项不为 NULL,说明任务在等待一个内核对象(如队列、信号量),此时无论是否在挂起列表,都应视为 eBlocked。
                        * 不过正常情况下挂起任务的事件列表项会被清除,此分支更多是防御性编程。 */
                        eReturn = eBlocked;
                    }
                }
            #endif /* if ( INCLUDE_vTaskSuspend == 1 ) */

            /* 判断删除(Deleted) */
            #if ( INCLUDE_vTaskDelete == 1 )
                /* 如果任务在等待终止列表(自身删除但尚未被空闲任务回收),或者状态列表项未插入任何列表(NULL,比如内存刚被部分释放),则返回 eDeleted */
                else if( ( pxStateList == &xTasksWaitingTermination ) || ( pxStateList == NULL ) )
                {
                    /* The task being queried is referenced from the deleted
                     * tasks list, or it is not referenced from any lists at
                     * all. */
                    eReturn = eDeleted;
                }
            #endif
            /* 就绪态(Ready)*/
            else /*lint !e525 Negative indentation is intended to make use of pre-processor clearer. */
            {
                /* If the task is not in any other state, it must be in the
                 * Ready (including pending ready) state. */
                /* 排除掉以上所有情况(运行、阻塞、挂起、删除),那么任务必定在就绪列表中(包括被抢占后处于就绪队列中),返回 eReady */
                eReturn = eReady;
            }
        }

        return eReturn;
    }

3.2 使用示例

复制代码
    /* 获取任务状态 */
    eTaskState e_task_state = 0;
    e_task_state = eTaskGetState(task1_handle);
    printf("task1任务状态=%d\r\n",e_task_state);

4.获取单个任务信息

  • 功能 :获取指定任务的各项信息,填充到 TaskStatus_t 结构体中。

  • 参数

    • xTask:目标任务句柄,若为 NULL 则查询调用者自身。

    • pxTaskStatus:输出参数,指向用户提供的 TaskStatus_t 结构体,用于存储任务信息。

    • xGetFreeStackSpace:是否计算剩余栈空间(pdTRUE 则计算,否则跳过以节省时间)。

    • eState:如果调用者已知 任务状态,可直接传入;若传入 eInvalid,则函数内部通过 eTaskGetState 重新查询。

  • 返回值 :无,结果通过 pxTaskStatus 返回

4.1 源码分析

复制代码
void vTaskGetInfo( TaskHandle_t xTask,
                       TaskStatus_t * pxTaskStatus,
                       BaseType_t xGetFreeStackSpace,
                       eTaskState eState )
    {
        TCB_t * pxTCB;

        /* xTask is NULL then get the state of the calling task. */
        /* 获取任务 TCB */
        /* 若 xTask == NULL,返回当前任务 TCB;否则根据句柄查找对应 TCB */
        pxTCB = prvGetTCBFromHandle( xTask );

        /* 填充信息 */
        pxTaskStatus->xHandle = ( TaskHandle_t ) pxTCB;     //任务句柄
        pxTaskStatus->pcTaskName = ( const char * ) &( pxTCB->pcTaskName[ 0 ] );        //任务名称
        pxTaskStatus->uxCurrentPriority = pxTCB->uxPriority;        //任务当前优先级
        pxTaskStatus->pxStackBase = pxTCB->pxStack;                 //栈起始地址
        #if ( ( portSTACK_GROWTH > 0 ) && ( configRECORD_STACK_HIGH_ADDRESS == 1 ) )        //栈向上增长且开启记录栈末端地址
            pxTaskStatus->pxTopOfStack = pxTCB->pxTopOfStack;       //栈顶地址
            pxTaskStatus->pxEndOfStack = pxTCB->pxEndOfStack;       //栈结束地址
        #endif
        pxTaskStatus->xTaskNumber = pxTCB->uxTCBNumber;     //任务编号

        #if ( configUSE_MUTEXES == 1 )      //启用互斥信号量
        {
            pxTaskStatus->uxBasePriority = pxTCB->uxBasePriority;       //任务基础优先级
        }
        #else
        {
            pxTaskStatus->uxBasePriority = 0;       //未启用互斥量则基础优先级记为 0,因为此时 uxPriority 已经是永远不变的原始优先级
        }
        #endif

        #if ( configGENERATE_RUN_TIME_STATS == 1 )      //启用运行时间统计
        {
            pxTaskStatus->ulRunTimeCounter = pxTCB->ulRunTimeCounter;       //记录任务消耗的 CPU 时间
        }
        #else
        {
            pxTaskStatus->ulRunTimeCounter = ( configRUN_TIME_COUNTER_TYPE ) 0;     //未启用运行时间统计则记录为 0
        }
        #endif

        /* Obtaining the task state is a little fiddly, so is only done if the
         * value of eState passed into this function is eInvalid - otherwise the
         * state is just set to whatever is passed in. */
        /* 确定任务状态 */
        if( eState != eInvalid )            //调用者传入了状态   
        {
            if( pxTCB == pxCurrentTCB )
            {
                /* 传入任务为当前运行任务,标记运行态 */
                pxTaskStatus->eCurrentState = eRunning; 
            }
            else
            {
                pxTaskStatus->eCurrentState = eState;       //直接拷贝传入状态

                #if ( INCLUDE_vTaskSuspend == 1 )
                {
                    /* If the task is in the suspended list then there is a
                     *  chance it is actually just blocked indefinitely - so really
                     *  it should be reported as being in the Blocked state. */
                    /* 即使认为任务是 eSuspended,也必须检查其事件列表项。若事件列表项不为空(等待队列/信号量/通知),
                     * 若事件列表项非空,说明任务正无限期阻塞于某个内核对象(如队列、信号量、事件组),
                     * 此时逻辑上属于阻塞态,应改为 eBlocked。
                     * 注意:此检查不覆盖"无限等待任务通知"的情况,后者事件列表项为空,
                     * 但任务实际上也属于无限期阻塞(这在 eTaskGetState 中会通过通知状态纠正,此处因性能原因未处理)。 */
                    if( eState == eSuspended )
                    {
                        vTaskSuspendAll();
                        {
                            if( listLIST_ITEM_CONTAINER( &( pxTCB->xEventListItem ) ) != NULL )
                            {
                                pxTaskStatus->eCurrentState = eBlocked;
                            }
                        }
                        ( void ) xTaskResumeAll();
                    }
                }
                #endif /* INCLUDE_vTaskSuspend */
            }
        }
        else        //调用者未传入状态,调用函数进行状态查询
        {
            pxTaskStatus->eCurrentState = eTaskGetState( pxTCB );
        }

        /* Obtaining the stack space takes some time, so the xGetFreeStackSpace
         * parameter is provided to allow it to be skipped. */
        /* 计算剩余栈空间(可选) */
        if( xGetFreeStackSpace != pdFALSE )     //需要计算
        {
            /* 计算任务的剩余栈空间(高水位标记法)。
             * 任务栈在初始化时全部填充为魔数 0xA5 (tskSTACK_FILL_BYTE)。
             * 随着任务运行,栈的使用会覆盖这些魔数值。
             * 本函数从栈的"远端"(即最后被使用的区域)开始扫描,
             * 统计仍保持为 0xA5 的连续字节数,从而得到未被使用的最小栈空间。
             *
             * 栈的生长方向由 portSTACK_GROWTH 宏决定,影响扫描的起点和方向:
             *
             * 【栈向下生长 (portSTACK_GROWTH < 0, 如 ARM Cortex-M)】
             *   - 内存布局:高地址为栈顶(起始),低地址为栈底(pxStack)。
             *   - 栈使用方向:从高地址向低地址推进。
             *   - 检查起点:从最低地址的栈底(pxStack)开始。
             *   - 扫描方向:向高地址移动,寻找连续 0xA5 的区域。
             *   - 指针移动:pucStackByte -= portSTACK_GROWTH;
             *     由于 GROWTH 为 -1,实际等同于 pucStackByte += 1(向后移动)。
             *
             * 【栈向上生长 (portSTACK_GROWTH > 0, 如部分 MIPS/PIC32)】
             *   - 内存布局:低地址为栈顶(起始),高地址为栈底。
             *   - 栈使用方向:从低地址向高地址推进。
             *   - 检查起点:从最高地址的栈末端(pxEndOfStack)开始。
             *   - 扫描方向:向低地址移动,寻找连续 0xA5 的区域。
             *   - 指针移动:pucStackByte -= portSTACK_GROWTH;
             *     由于 GROWTH 为 1,等同于 pucStackByte -= 1(向前移动)。
             *
             * 返回值为连续未使用"字(通常4字节)"的数量,而非字节数。*/
            #if ( portSTACK_GROWTH > 0 )
            {
                /* 栈向上增长时,则从 pxEndOfStack 开始 */
                pxTaskStatus->usStackHighWaterMark = prvTaskCheckFreeStackSpace( ( uint8_t * ) pxTCB->pxEndOfStack );
            }
            #else
            {
                /* 对栈向下增长的情况(portSTACK_GROWTH < 0),传入 pxTCB->pxStack(栈底)作为扫描起点,背离栈顶方向检查直至遇到第一个改变了的值 */
                pxTaskStatus->usStackHighWaterMark = prvTaskCheckFreeStackSpace( ( uint8_t * ) pxTCB->pxStack );
            }
            #endif
        }
        else        //不需要计算,直接传递0
        {
            pxTaskStatus->usStackHighWaterMark = 0;
        }
    }

4.2 使用示例

复制代码
    /* 获取单个任务的信息 */
    TaskStatus_t task_state;
    vTaskGetInfo(
        task1_handle, // 要获取的任务信息的句柄
        &task_state,  // 用来保存查询到的任务状态信息
        pdTRUE,       // 要查询 任务栈 历史剩余最小值
        eInvalid);    // 设为eInvalid,才会真正去获取指定任务的状态
    printf("任务名称:%s\r\n", task_state.pcTaskName);
    printf("任务编号:%d\r\n", task_state.xTaskNumber);
    printf("任务状态:%d\r\n", task_state.eCurrentState);
    printf("任务优先级:%d\r\n", task_state.uxCurrentPriority);

5.获取所有任务的任务信息

  • 功能:获取系统中当前所有任务的状态快照,填充到数组中,并可选地提供总 CPU 运行时间。

  • 参数

    • pxTaskStatusArray:用户分配的 TaskStatus_t 数组,用于存储结果。

    • uxArraySize:数组的容量(最多能存多少个任务)。

    • pulTotalRunTime:指向一个变量的指针,用于返回总运行时间;若为 NULL 则跳过。

  • 返回值:实际填充到数组中的任务数量

5.1 源码分析

复制代码
UBaseType_t uxTaskGetSystemState( TaskStatus_t * const pxTaskStatusArray,
                                      const UBaseType_t uxArraySize,
                                      configRUN_TIME_COUNTER_TYPE * const pulTotalRunTime )
    {
        /* uxTask 记录已经填充到数组中的任务个数(即数组的当前写入索引)
         * uxQueue 初始化为最高优先级,用于从高到低遍历就绪列表 */
        UBaseType_t uxTask = 0, uxQueue = configMAX_PRIORITIES; 

        vTaskSuspendAll();  //挂起调度器(但不关中断)。这确保在获取所有任务信息的过程中,任务不会被切换,也不会被添加到/移出各种列表,从而得到一个一致的快照
        {
            /* Is there a space in the array for each task in the system? */
            /* 只有当数组足够大,能容纳当前系统所有任务时,才执行填充
             * 如果数组太小,则不进行任何操作,直接返回(此时 uxTask 仍为初始值 0) */
            if( uxArraySize >= uxCurrentNumberOfTasks )
            {
                /* prvListTasksWithinSingleList本质上就是遍历由 pxList 指定的内核链表(例如某个优先级的就绪列表,或延迟列表),
                 * 对链表中的每个任务调用 vTaskGetInfo 填写一个 TaskStatus_t 结构体,并将预设状态 eState 传递给该结构体 */
                /* Fill in an TaskStatus_t structure with information on each
                 * task in the Ready state. */
                /* 这里从最高优先级 configMAX_PRIORITIES-1 一直遍历到 tskIDLE_PRIORITY(空闲任务优先级),确保所有就绪列表中的任务都被收录 */
                /* 每次调用 prvListTasksWithinSingleList,uxTask 都会增加对应数量,数组的写入位置也相应后移 */
                do
                {
                    uxQueue--;
                    uxTask += prvListTasksWithinSingleList( &( pxTaskStatusArray[ uxTask ] ), &( pxReadyTasksLists[ uxQueue ] ), eReady );
                } while( uxQueue > ( UBaseType_t ) tskIDLE_PRIORITY ); /*lint !e961 MISRA exception as the casts are only redundant for some ports. */

                /* Fill in an TaskStatus_t structure with information on each
                 * task in the Blocked state. */
                /* pxDelayedTaskList 和 pxOverflowDelayedTaskList 存放正在延时或超时等待的任务(即阻塞态)。
                 * 两个列表都要遍历,以覆盖所有可能因系统节拍计数溢出而分散在两个列表中的任务。
                 * 分配给这些任务的状态均标记为 eBlocked */
                uxTask += prvListTasksWithinSingleList( &( pxTaskStatusArray[ uxTask ] ), ( List_t * ) pxDelayedTaskList, eBlocked );
                uxTask += prvListTasksWithinSingleList( &( pxTaskStatusArray[ uxTask ] ), ( List_t * ) pxOverflowDelayedTaskList, eBlocked );

                /* 如果启用了任务删除功能,则把 xTasksWaitingTermination 列表中所有等待空闲任务清理的任务也纳入快照,状态标记为 eDeleted */
                #if ( INCLUDE_vTaskDelete == 1 )
                {
                    /* Fill in an TaskStatus_t structure with information on
                     * each task that has been deleted but not yet cleaned up. */
                    uxTask += prvListTasksWithinSingleList( &( pxTaskStatusArray[ uxTask ] ), &xTasksWaitingTermination, eDeleted );
                }
                #endif

                /* 若支持任务挂起,则把 xSuspendedTaskList 中的任务信息填入数组,并标记为 eSuspended */
                #if ( INCLUDE_vTaskSuspend == 1 )
                {
                    /* Fill in an TaskStatus_t structure with information on
                     * each task in the Suspended state. */
                    uxTask += prvListTasksWithinSingleList( &( pxTaskStatusArray[ uxTask ] ), &xSuspendedTaskList, eSuspended );
                }
                #endif

                /* 若启用运行时间统计且用户提供了非空指针,则读取当前系统的总运行计数器值
                 * 反之,则置0 */
                #if ( configGENERATE_RUN_TIME_STATS == 1 )
                {
                    if( pulTotalRunTime != NULL )
                    {
                        #ifdef portALT_GET_RUN_TIME_COUNTER_VALUE
                            portALT_GET_RUN_TIME_COUNTER_VALUE( ( *pulTotalRunTime ) );
                        #else
                            *pulTotalRunTime = portGET_RUN_TIME_COUNTER_VALUE();
                        #endif
                    }
                }
                #else /* if ( configGENERATE_RUN_TIME_STATS == 1 ) */
                {
                    if( pulTotalRunTime != NULL )
                    {
                        *pulTotalRunTime = 0;
                    }
                }
                #endif /* if ( configGENERATE_RUN_TIME_STATS == 1 ) */
            }
            else
            {
                mtCOVERAGE_TEST_MARKER();       //代码覆盖率测试,实际为空
            }
        }
        /* 解除调度器挂起,恢复正常的任务调度。如果在此期间有高优先级任务被解锁或恢复了,将在此处触发一次上下文切换。 */
        ( void ) xTaskResumeAll();

        return uxTask;
    }

5.2 使用示例

复制代码
TaskStatus_t task_status[10] = 0;
UBaseType_t task_num = 0;
uxTaskGetSystemState(task_status, task_num, NULL);
printf("任务名\t\t任务编号\t\t任务状态\t\t当前优先级\t\t任务原始优先级\r\n");
for (uint8_t i = 0; i < task_num; i++)
{
    printf("%s\t\t%d\t\t%d\t\t%d\t\t%d\r\n",
            task_status[i].pcTaskName,
            task_status[i].xTaskNumber,
            task_status[i].eCurrentState,
            task_status[i].uxCurrentPriority,
            task_status[i].uxBasePriority);
}

6.获取当前任务的任务句柄

  • 功能:返回调用者的任务句柄(即当前任务的控制块指针)

  • 参数:无

  • 返回值 :当前任务的句柄(pxCurrentTCB 的值)

6.1 源码分析

复制代码
    TaskHandle_t xTaskGetCurrentTaskHandle( void )
    {
        TaskHandle_t xReturn;

        /* A critical section is not required as this is not called from
         * an interrupt and the current TCB will always be the same for any
         * individual execution thread. */
        xReturn = pxCurrentTCB;

        return xReturn;
    }

6.2 使用示例

复制代码
    /* 获取当前任务的任务句柄 */
    TaskHandle_t task_handle = 0;
    task_handle = xTaskGetCurrentTaskHandle();
    printf("获取到的当前任务句柄=%p,task2的句柄=%p\r\n", task_handle, task2_handle);

7.通过任务名获取指定任务句柄

  • 功能 :根据任务名称(字符串)在系统中查找对应任务,返回其句柄;若未找到则返回 NULL

  • 参数pcNameToQuery 为要查找的任务名称(C 字符串)。

  • 返回值 :匹配的任务句柄,或 NULL

7.1 源码分析

复制代码
TaskHandle_t xTaskGetHandle( const char * pcNameToQuery ) /*lint !e971 Unqualified char types are allowed for strings and single characters only. */
    {
        UBaseType_t uxQueue = configMAX_PRIORITIES;     //始化为最高优先级,接下来从高到低遍历就绪列表。这是为了优先找到高优先级的任务
        TCB_t * pxTCB;      //用于接收找到的 TCB 指针

        /* Task names will be truncated to configMAX_TASK_NAME_LEN - 1 bytes. */
        /* 断言名称长度,需用户自定义实现 */
        configASSERT( strlen( pcNameToQuery ) < configMAX_TASK_NAME_LEN );

        vTaskSuspendAll();      //挂起任务调度器, 与临界区(taskENTER_CRITICAL)相比,vTaskSuspendAll 允许中断发生,但对任务的上下文切换加锁,适合较长时间遍历
        {
            /* Search the ready lists. */
            /* 循环从 configMAX_PRIORITIES - 1 递减到 tskIDLE_PRIORITY(包含空闲优先级) */
            do
            {
                uxQueue--;
                /* 遍历指定链表,逐个比较任务名称,找到后返回 TCB 指针,否则返回 NULL */
                pxTCB = prvSearchForNameWithinSingleList( ( List_t * ) &( pxReadyTasksLists[ uxQueue ] ), pcNameToQuery );

                if( pxTCB != NULL )
                {
                    /* Found the handle. */
                    break;
                }
            } while( uxQueue > ( UBaseType_t ) tskIDLE_PRIORITY ); /*lint !e961 MISRA exception as the casts are only redundant for some ports. */

            /* 若在就绪列表未找到,任务可能正处于延时或超时等待状态。
             * 依次在pxDelayedTaskList 和 pxOverflowDelayedTaskList 列表中查找 */
            /* Search the delayed lists. */
            if( pxTCB == NULL )
            {
                pxTCB = prvSearchForNameWithinSingleList( ( List_t * ) pxDelayedTaskList, pcNameToQuery );
            }

            if( pxTCB == NULL )
            {
                pxTCB = prvSearchForNameWithinSingleList( ( List_t * ) pxOverflowDelayedTaskList, pcNameToQuery );
            }

            /* 若启用了任务挂起功能,继续在挂起列表 xSuspendedTaskList 中搜索 */
            #if ( INCLUDE_vTaskSuspend == 1 )
            {
                if( pxTCB == NULL )
                {
                    /* Search the suspended list. */
                    pxTCB = prvSearchForNameWithinSingleList( &xSuspendedTaskList, pcNameToQuery );
                }
            }
            #endif

            /* 如果启用了任务删除,且在之前列表中都未找到,任务可能已被删除但尚未被空闲任务回收,在xTasksWaitingTermination 中搜索 */
            #if ( INCLUDE_vTaskDelete == 1 )
            {
                if( pxTCB == NULL )
                {
                    /* Search the deleted list. */
                    pxTCB = prvSearchForNameWithinSingleList( &xTasksWaitingTermination, pcNameToQuery );
                }
            }
            #endif
        }
        ( void ) xTaskResumeAll();      //解除调度器挂起,如果有待处理的上下文切换请求,将在此处执行

        return pxTCB;
    }

7.2 使用示例

复制代码
    task_handle = xTaskGetHandle("task1");
    printf("获取到的名为task1任务句柄=%p,task1的句柄=%p\r\n", task_handle, task1_handle);

8.获取任务栈历史剩余最小值

  • 功能 :返回任务从创建以来未被使用的栈空间的最小值(以字为单位)。数值越低,说明栈使用量越接近极限,越可能发生溢出。

  • 参数xTask 为待查任务句柄;若为 NULL 则查询当前任务自身。

  • 返回值:剩余栈空间的"高水位"值(单位:字,通常 4 字节)。

8.1 源码分析

复制代码
    UBaseType_t uxTaskGetStackHighWaterMark( TaskHandle_t xTask )
    {
        TCB_t * pxTCB;                  //指向目标任务控制块
        uint8_t * pucEndOfStack;        //指向栈的"远端"地址(即栈初始化时未被使用的区域的起点),传递给 prvTaskCheckFreeStackSpace 作为扫描起点
        UBaseType_t uxReturn;           //保存并返回剩余栈空间值

        /* 若 xTask 为 NULL,返回当前任务 TCB;否则根据句柄查找对应 TCB */
        pxTCB = prvGetTCBFromHandle( xTask );

        /* 对于栈向下生长(portSTACK_GROWTH < 0,如 ARM Cortex‑M):
         * 栈底对应最低地址,即 pxTCB->pxStack。
         * 栈从未被使用的"远端"正是栈底,因此将 pucEndOfStack 指向 pxTCB->pxStack。
         * 扫描将从低地址向高地址推进,统计连续保留的魔数(0xA5)的数量。
         * 对于栈向上生长(portSTACK_GROWTH > 0,如部分 MIPS):
         * 栈顶在低地址,栈底在高地址,栈末端为 pxTCB->pxEndOfStack。
         * 未使用的部分位于栈的末端(高地址),因此从此处开始向低地址扫描。 */
        #if portSTACK_GROWTH < 0
        {
            pucEndOfStack = ( uint8_t * ) pxTCB->pxStack;
        }
        #else
        {
            pucEndOfStack = ( uint8_t * ) pxTCB->pxEndOfStack;
        }
        #endif

        /* 计算任务的剩余栈空间(高水位标记法)。
        * 任务栈在初始化时全部填充为魔数 0xA5 (tskSTACK_FILL_BYTE)。
        * 随着任务运行,栈的使用会覆盖这些魔数值。
        * 本函数从栈的"远端"(即最后被使用的区域)开始扫描,
        * 统计仍保持为 0xA5 的连续字节数,从而得到未被使用的最小栈空间。
        *
        * 栈的生长方向由 portSTACK_GROWTH 宏决定,影响扫描的起点和方向:
        *
        * 【栈向下生长 (portSTACK_GROWTH < 0, 如 ARM Cortex-M)】
        *   - 内存布局:高地址为栈顶(起始),低地址为栈底(pxStack)。
        *   - 栈使用方向:从高地址向低地址推进。
        *   - 检查起点:从最低地址的栈底(pxStack)开始。
        *   - 扫描方向:向高地址移动,寻找连续 0xA5 的区域。
        *   - 指针移动:pucStackByte -= portSTACK_GROWTH;
        *     由于 GROWTH 为 -1,实际等同于 pucStackByte += 1(向后移动)。
        *
        * 【栈向上生长 (portSTACK_GROWTH > 0, 如部分 MIPS/PIC32)】
        *   - 内存布局:低地址为栈顶(起始),高地址为栈底。
        *   - 栈使用方向:从低地址向高地址推进。
        *   - 检查起点:从最高地址的栈末端(pxEndOfStack)开始。
        *   - 扫描方向:向低地址移动,寻找连续 0xA5 的区域。
        *   - 指针移动:pucStackByte -= portSTACK_GROWTH;
        *     由于 GROWTH 为 1,等同于 pucStackByte -= 1(向前移动)。
        *
        * 返回值为连续未使用"字(通常4字节)"的数量,而非字节数。*/
        uxReturn = ( UBaseType_t ) prvTaskCheckFreeStackSpace( pucEndOfStack );

        return uxReturn;
    }

8.2 使用示例

复制代码
    UBaseType_t task_stack_remain_min = 0;
    task_stack_remain_min = uxTaskGetStackHighWaterMark(task2_handle);
    printf("task2任务栈历史剩余最小值=%d\r\n",task_stack_remain_min);

9.获取系统中的任务信息(表格形式)

  • 功能:生成一个包含所有任务信息的字符串表格,写入用户提供的缓冲区。

  • 参数pcWriteBuffer 是一个指向字符缓冲区的指针,由调用者分配且需足够大,以容纳所有任务的信息。

  • 注意 :该函数主要用于演示和调试,不建议在产品代码中使用 ,因为它会调用耗时的 sprintf,并且会动态分配内存

9.1 源码分析

复制代码
void vTaskList( char * pcWriteBuffer )
    {
        TaskStatus_t * pxTaskStatusArray;   //指向动态分配的 TaskStatus_t 数组,用于存储每个任务的快照
        UBaseType_t uxArraySize, x;         //数组的大小(任务总数)、循环索引 
        char cStatus;                       // 状态字符,如 'R'(运行)、'B'(阻塞)等

        /*
         * PLEASE NOTE:
         *
         * This function is provided for convenience only, and is used by many
         * of the demo applications.  Do not consider it to be part of the
         * scheduler.
         *
         * vTaskList() calls uxTaskGetSystemState(), then formats part of the
         * uxTaskGetSystemState() output into a human readable table that
         * displays task: names, states, priority, stack usage and task number.
         * Stack usage specified as the number of unused StackType_t words stack can hold
         * on top of stack - not the number of bytes.
         *
         * vTaskList() has a dependency on the sprintf() C library function that
         * might bloat the code size, use a lot of stack, and provide different
         * results on different platforms.  An alternative, tiny, third party,
         * and limited functionality implementation of sprintf() is provided in
         * many of the FreeRTOS/Demo sub-directories in a file called
         * printf-stdarg.c (note printf-stdarg.c does not provide a full
         * snprintf() implementation!).
         *
         * It is recommended that production systems call uxTaskGetSystemState()
         * directly to get access to raw stats data, rather than indirectly
         * through a call to vTaskList().
         */

        /* 仅为便利提供:vTaskList 不是调度器核心部分,而是演示用途。
         * 内部调用 uxTaskGetSystemState:首先获取所有任务的快照数据,再格式化为可读表格。
         * 信息内容:表格列包括任务名、状态、优先级、栈高水位(未使用的栈字数)、任务编号。
         * 依赖 sprintf:可能增加代码体积,不同平台表现可能不同。演示目录中提供了简化版 printf-stdarg.c。
         * 生产系统的建议:直接调用 uxTaskGetSystemState 获取原始数据,而非通过 vTaskList 间接格式化。 */

        /* Make sure the write buffer does not contain a string. */
        *pcWriteBuffer = ( char ) 0x00;         //初始化写入缓冲区

        /* Take a snapshot of the number of tasks in case it changes while this
         * function is executing. */
        uxArraySize = uxCurrentNumberOfTasks;       //记录任务数量快照

        /* Allocate an array index for each task.  NOTE!  if
         * configSUPPORT_DYNAMIC_ALLOCATION is set to 0 then pvPortMalloc() will
         * equate to NULL. */
        /* 使用 FreeRTOS 的内存分配函数(通常指向 pvPortMalloc)分配一个与当前任务数量匹配的 TaskStatus_t 数组 */
        /* 如果 configSUPPORT_DYNAMIC_ALLOCATION 为 0,则 pvPortMalloc 可能返回 NULL */
        pxTaskStatusArray = pvPortMalloc( uxCurrentNumberOfTasks * sizeof( TaskStatus_t ) ); /*lint !e9079 All values returned by pvPortMalloc() have at least the alignment required by the MCU's stack and this allocation allocates a struct that has the alignment requirements of a pointer. */

        if( pxTaskStatusArray != NULL )     //检查分配是否成功
        {
            /* Generate the (binary) data. */
            /* 调用 uxTaskGetSystemState 获取所有任务信息 
             * 填充 pxTaskStatusArray,并返回实际填充的任务数 */
            uxArraySize = uxTaskGetSystemState( pxTaskStatusArray, uxArraySize, NULL );

            /* Create a human readable table from the binary data. */
            /* 遍历所有任务,生成表格字符串 */
            for( x = 0; x < uxArraySize; x++ )
            {
                /* 状态字符转换 */
                switch( pxTaskStatusArray[ x ].eCurrentState )
                {
                    case eRunning:
                        cStatus = tskRUNNING_CHAR;
                        break;

                    case eReady:
                        cStatus = tskREADY_CHAR;
                        break;

                    case eBlocked:
                        cStatus = tskBLOCKED_CHAR;
                        break;

                    case eSuspended:
                        cStatus = tskSUSPENDED_CHAR;
                        break;

                    case eDeleted:
                        cStatus = tskDELETED_CHAR;
                        break;

                    case eInvalid: /* Fall through. */
                    default:       /* Should not get here, but it is included
                                    * to prevent static checking errors. */
                        cStatus = ( char ) 0x00;
                        break;
                }

                /* Write the task name to the string, padding with spaces so it
                 * can be printed in tabular form more easily. */
                /* 写入任务名称(固定宽度,用空格填充) */                
                pcWriteBuffer = prvWriteNameToBuffer( pcWriteBuffer, pxTaskStatusArray[ x ].pcTaskName );

                /* Write the rest of the string. */
                /* 格式化并追加状态、优先级、栈高水位、任务编号 */
                sprintf( pcWriteBuffer, "\t%c\t%u\t%u\t%u\r\n", cStatus, ( unsigned int ) pxTaskStatusArray[ x ].uxCurrentPriority, ( unsigned int ) pxTaskStatusArray[ x ].usStackHighWaterMark, ( unsigned int ) pxTaskStatusArray[ x ].xTaskNumber ); /*lint !e586 sprintf() allowed as this is compiled with many compilers and this is a utility function only - not part of the core kernel implementation. */
                /* 移动缓冲区指针到字符串末尾 */
                pcWriteBuffer += strlen( pcWriteBuffer );                                                                                                                                                                                                /*lint !e9016 Pointer arithmetic ok on char pointers especially as in this case where it best denotes the intent of the code. */
            }

            /* Free the array again.  NOTE!  If configSUPPORT_DYNAMIC_ALLOCATION
             * is 0 then vPortFree() will be #defined to nothing. */
            vPortFree( pxTaskStatusArray );         //释放之前分配的 TaskStatus_t 数组
        }
        else
        {
            mtCOVERAGE_TEST_MARKER();           //代码覆盖率测试,实际为空
        }
    }

9.2 使用示例

复制代码
    /* 以表格形式显示系统中的任务信息 */
    char task_info[500];
    vTaskList(task_info);
    printf("%s\r\n",task_info);

10.获取任务运行时间

  • 功能:生成一个包含所有任务运行时间统计信息的字符串表格,写入用户提供的缓冲区。

  • 参数pcWriteBuffer 是一个指向字符缓冲区的指针,由调用者分配且需足够大,以容纳所有任务的信息。

  • 注意 :该函数与 vTaskList() 类似,主要用于演示和调试,不建议在产品代码中使用 ,因为它会调用耗时的 sprintf,并且会动态分配内存。

10.1 源码解析

复制代码
void vTaskGetRunTimeStats( char * pcWriteBuffer )
    {
        TaskStatus_t * pxTaskStatusArray;       //指向动态分配的 TaskStatus_t 数组,用于存储每个任务的运行时间快照
        UBaseType_t uxArraySize, x;             //数组的容量(任务总数), 循环索引
        configRUN_TIME_COUNTER_TYPE ulTotalTime, ulStatsAsPercentage;   //所有任务消耗的总运行时间(从 uxTaskGetSystemState 获得),前任务消耗的 CPU 时间百分比

        /*
         * PLEASE NOTE:
         *
         * This function is provided for convenience only, and is used by many
         * of the demo applications.  Do not consider it to be part of the
         * scheduler.
         *
         * vTaskGetRunTimeStats() calls uxTaskGetSystemState(), then formats part
         * of the uxTaskGetSystemState() output into a human readable table that
         * displays the amount of time each task has spent in the Running state
         * in both absolute and percentage terms.
         *
         * vTaskGetRunTimeStats() has a dependency on the sprintf() C library
         * function that might bloat the code size, use a lot of stack, and
         * provide different results on different platforms.  An alternative,
         * tiny, third party, and limited functionality implementation of
         * sprintf() is provided in many of the FreeRTOS/Demo sub-directories in
         * a file called printf-stdarg.c (note printf-stdarg.c does not provide
         * a full snprintf() implementation!).
         *
         * It is recommended that production systems call uxTaskGetSystemState()
         * directly to get access to raw stats data, rather than indirectly
         * through a call to vTaskGetRunTimeStats().
         */

         /* 仅为便利提供:vTaskGetRunTimeStats 不是调度器核心部分,而是演示用途。
          * 内部调用 uxTaskGetSystemState:首先获取所有任务的快照数据(包括每个任务消耗的 CPU 时间),再格式化为可读表格。
          * 依赖 sprintf:可能增加代码体积,不同平台表现可能不同。FreeRTOS 演示目录中提供了简化版 printf-stdarg.c。
          * 生产系统建议:直接调用 uxTaskGetSystemState 获取原始统计数据,而非通过 vTaskGetRunTimeStats 间接格式化 */

        /* Make sure the write buffer does not contain a string. */
        /* 缓冲区的第一个字节置为 '\0'(空字符串)。这样即使后续没有写入任何任务数据,缓冲区仍是一个有效的空字符串 */
        *pcWriteBuffer = ( char ) 0x00;

        /* Take a snapshot of the number of tasks in case it changes while this
         * function is executing. */
        /* 读取当前任务总数作为一个快照值。因为在函数执行期间任务数量可能变化(例如其他任务删除或创建),后续操作以此快照为准 */
        uxArraySize = uxCurrentNumberOfTasks;

        /* Allocate an array index for each task.  NOTE!  If
         * configSUPPORT_DYNAMIC_ALLOCATION is set to 0 then pvPortMalloc() will
         * equate to NULL. */
        /* 使用 FreeRTOS 的内存分配函数(通常指向 pvPortMalloc)分配一个与当前任务数量匹配的 TaskStatus_t 数组 */
        /* 如果 configSUPPORT_DYNAMIC_ALLOCATION 为 0,则 pvPortMalloc 可能返回 NULL */
        pxTaskStatusArray = pvPortMalloc( uxCurrentNumberOfTasks * sizeof( TaskStatus_t ) ); /*lint !e9079 All values returned by pvPortMalloc() have at least the alignment required by the MCU's stack and this allocation allocates a struct that has the alignment requirements of a pointer. */

        if( pxTaskStatusArray != NULL )     //检查分配是否成功
        {
            /* Generate the (binary) data. */
            /* 调用 uxTaskGetSystemState 获取所有任务信息 
             * 填充 pxTaskStatusArray,并返回实际填充的任务数
             * 第三个参数 &ulTotalTime:不同于 vTaskList 传 NULL,这里传入了一个有效指针,
             * 以获取所有任务消耗的总 CPU 运行时间(即系统运行的总时间计数)*/
            uxArraySize = uxTaskGetSystemState( pxTaskStatusArray, uxArraySize, &ulTotalTime );

            /* For percentage calculations. */
            /* 将总时间除以 100。这是为了简化后续的百分比计算:直接使用 ulRunTimeCounter / ulTotalTime 即可得到百分比(0~100),而无需再乘以 100 */
            ulTotalTime /= 100UL;

            /* Avoid divide by zero errors. */
            /* 只有总时间大于零时才生成表格,确保后续除法是安全的,避免除0错误*/
            if( ulTotalTime > 0UL )
            {
                /* Create a human readable table from the binary data. */
                /* 遍历所有任务,生成表格字符串 */
                for( x = 0; x < uxArraySize; x++ )
                {
                    /* What percentage of the total run time has the task used?
                     * This will always be rounded down to the nearest integer.
                     * ulTotalRunTime has already been divided by 100. */
                    /* 计算当前任务消耗的 CPU 时间百分比 */
                    ulStatsAsPercentage = pxTaskStatusArray[ x ].ulRunTimeCounter / ulTotalTime;

                    /* Write the task name to the string, padding with
                     * spaces so it can be printed in tabular form more
                     * easily. */
                    /* 写入任务名称(固定宽度,用空格填充) */
                    pcWriteBuffer = prvWriteNameToBuffer( pcWriteBuffer, pxTaskStatusArray[ x ].pcTaskName );

                    /* 百分比大于0,正常输出绝对值和百分比 */
                    if( ulStatsAsPercentage > 0UL )
                    {
                        #ifdef portLU_PRINTF_SPECIFIER_REQUIRED
                        {
                            sprintf( pcWriteBuffer, "\t%lu\t\t%lu%%\r\n", pxTaskStatusArray[ x ].ulRunTimeCounter, ulStatsAsPercentage );
                        }
                        #else
                        {
                            /* sizeof( int ) == sizeof( long ) so a smaller
                             * printf() library can be used. */
                            sprintf( pcWriteBuffer, "\t%u\t\t%u%%\r\n", ( unsigned int ) pxTaskStatusArray[ x ].ulRunTimeCounter, ( unsigned int ) ulStatsAsPercentage ); /*lint !e586 sprintf() allowed as this is compiled with many compilers and this is a utility function only - not part of the core kernel implementation. */
                        }
                        #endif
                    }
                    else        /* 百分比为零,说明任务消耗的时间不足总时间的1%,显示 "<1%" */
                    {
                        /* If the percentage is zero here then the task has
                         * consumed less than 1% of the total run time. */
                        
                        #ifdef portLU_PRINTF_SPECIFIER_REQUIRED
                        {
                            sprintf( pcWriteBuffer, "\t%lu\t\t<1%%\r\n", pxTaskStatusArray[ x ].ulRunTimeCounter );
                        }
                        #else
                        {
                            /* sizeof( int ) == sizeof( long ) so a smaller
                             * printf() library can be used. */
                            sprintf( pcWriteBuffer, "\t%u\t\t<1%%\r\n", ( unsigned int ) pxTaskStatusArray[ x ].ulRunTimeCounter ); /*lint !e586 sprintf() allowed as this is compiled with many compilers and this is a utility function only - not part of the core kernel implementation. */
                        }
                        #endif
                    }

                    /* 移动缓冲区指针到字符串末尾 */
                    pcWriteBuffer += strlen( pcWriteBuffer ); /*lint !e9016 Pointer arithmetic ok on char pointers especially as in this case where it best denotes the intent of the code. */
                }
            }
            else
            {
                mtCOVERAGE_TEST_MARKER();       //代码覆盖率测试,实际为空
            }

            /* Free the array again.  NOTE!  If configSUPPORT_DYNAMIC_ALLOCATION
             * is 0 then vPortFree() will be #defined to nothing. */
            vPortFree( pxTaskStatusArray );         //释放之前分配的 TaskStatus_t 数组
        }
        else
        {
            mtCOVERAGE_TEST_MARKER();               //代码覆盖率测试,实际为空
        }
    }

10.2 使用示例

复制代码
        char task_time_info[500];
        vTaskGetRunTimeStats(task_time_info);
        printf("%s\r\n",task_time_info);

11.声明

(1)Copyright (C) 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved.

(2)文中代码来自FreeRTOS,遵循MIT许可证,许可证可参考:https://opensource.org/licenses/MIT

复制代码
/*
 * FreeRTOS Kernel V10.5.1
 * Copyright (C) 2021 Amazon.com, Inc. or its affiliates.  All Rights Reserved.
 *
 * SPDX-License-Identifier: MIT
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy of
 * this software and associated documentation files (the "Software"), to deal in
 * the Software without restriction, including without limitation the rights to
 * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
 * the Software, and to permit persons to whom the Software is furnished to do so,
 * subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
 * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
 * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
 * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 *
 * https://www.FreeRTOS.org
 * https://github.com/FreeRTOS
 *
 */

【以上内容为个人在学习FreeRTOS过程中的源码解读笔记,欢迎大家在评论区讨论指正。】

【如果本篇内容对你有帮助,不妨点个关注,你的支持是我持续更新的动力!】

相关推荐
杰杰桀桀桀4 小时前
基于stm32ARM库函数的IIR二阶巴特沃斯低通滤波器--附完整代码
arm开发·stm32·嵌入式硬件·数字滤波器·巴特沃斯低通滤波
危桥带雨5 小时前
Freertos——使用队列集优化数据传输
stm32·单片机·嵌入式硬件·freertos
来生硬件工程师5 小时前
【程序库】 MutiButton 按键库
c语言·笔记·stm32·单片机·mcu·嵌入式实时数据库
CodeQingqing5 小时前
反汇编在嵌入式的使用
stm32·嵌入式·反汇编
sweetone5 小时前
好帅(HOST) HS-AF01T电烤炉(空气炸锅)的小修及物联网设备的安全思考
经验分享·单片机·嵌入式硬件·物联网
多看多敲多思考5 小时前
华润微CS32ME10 MCU使用教程(1)---CS32ME10之GPIO使用
c语言·stm32·单片机·嵌入式硬件·mcu
小谦32515 小时前
第十一篇、CubeMX | 可见光谱颜色传感器 AS7341
单片机·嵌入式硬件
黑白园5 小时前
STM32 printf函数重定向到USATR1输出打印
stm32·单片机·嵌入式硬件
踏着七彩祥云的小丑6 小时前
嵌入式——认识电子元器件——温度保险丝系列
单片机·嵌入式硬件