FreeRTOS任务调度原理

2023-05-16

1.FreeRTOS的列表和列表项

列表和列表项是FreeRTOS中一个非常重要的数据结构,是FreeRTOS的基石。要想看懂FreeRTOS的源码并学习其中的原理,我们必须先了解一下这个数据结构。这个数据结构也是和任务调度息息相关的。
/* 列表项*/
struct xLIST_ITEM
{
    
    configLIST_VOLATILE TickType_t xItemValue;              /*< 列表项值 */
    struct xLIST_ITEM * configLIST_VOLATILE pxNext;         /*< 列表项后向指针 */
    struct xLIST_ITEM * configLIST_VOLATILE pxPrevious;     /*< 列表项前向指针 */
    void * pvOwner;                                         /*< 当前列表项属于控制块(TCB) */
    struct xLIST * configLIST_VOLATILE pxContainer;         /*< 当前列表项所属队列(列表)*/
    
};
typedef struct xLIST_ITEM ListItem_t; 

/* mini列表项(可以减少存储空间)*/
struct xMINI_LIST_ITEM
{
   
    configLIST_VOLATILE TickType_t xItemValue;				/*< 列表项值 */
    struct xLIST_ITEM * configLIST_VOLATILE pxNext;			/*< 列表项后向指针 */
    struct xLIST_ITEM * configLIST_VOLATILE pxPrevious;		/*< 列表项前向指针 */
};
typedef struct xMINI_LIST_ITEM MiniListItem_t;
/* 列表(本质上和双向链表一样)*/
typedef struct xLIST
{
    
    volatile UBaseType_t uxNumberOfItems;		  /*< 当前队列(列表)总共有多少项 */
    ListItem_t * configLIST_VOLATILE pxIndex;     /*< 当前队列(列表项)指针*/
    MiniListItem_t xListEnd;                      /*< 队列(列表)尾部,使用的是mini列表项表示*/
    
} List_t;

1.1有关函数

有关列表操作的相关函数都在list.c文件中。下面介绍一下相关函数和作用。

/*对列表的初始化*/
void vListInitialise( List_t * const pxList );
/*初始化结果:列表当前列表项为0,当前pxIndex指针指向xListEnd*/

 

 

/*列表项初始化*/
void vListInitialiseItem( ListItem_t * const pxItem )
{
	/* 只需要将当前列表项所属的列表初始化为NULL,表示当前列表项不属于任何列表 */
    pxItem->pxContainer = NULL;

}
/*列表的插入*/
void vListInsert( List_t * const pxList,
                  ListItem_t * const pxNewListItem );
/*
函数 vListInsert()的参数 pxList 决定了列表项要插入到哪个列表中,
pxNewListItem决定了要插入的列表项,要插入的位置由列表项中的成员变量 xltemValue 来决定。
列表项的插入根据xltemValue 的值按照升序的方式排列。
*/

 

/*列表尾部插入函数,需要注意的是插入的位置是pxIndex指向的列表项的前面*/
void vListInsertEnd( List_t * const pxList,
                     ListItem_t * const pxNewListItem )
  {
  	/*获得当前pxIndex所指向的列表项,当前列表项即为列表头*/
    ListItem_t * const pxIndex = pxList->pxIndex;

  	/*插入新的列表项到列表头前面,因为此列表是一个双向列表即环形链表,头和尾相连*/
    pxNewListItem->pxNext = pxIndex;
    pxNewListItem->pxPrevious = pxIndex->pxPrevious;

    pxIndex->pxPrevious->pxNext = pxNewListItem;
    pxIndex->pxPrevious = pxNewListItem;

    /* Remember which list the item is in. */
    pxNewListItem->pxContainer = pxList;

  	/*列表成员数加一*/
    ( pxList->uxNumberOfItems )++;
}
/*列表项的移除*/
UBaseType_t uxListRemove( ListItem_t * const pxItemToRemove )
{
	/*获取当前列表项所属哪个列表*/
    List_t * const pxList = pxItemToRemove->pxContainer;

  	/*将此列表项的前后列表项指针指向移位*/
    pxItemToRemove->pxNext->pxPrevious = pxItemToRemove->pxPrevious;
    pxItemToRemove->pxPrevious->pxNext = pxItemToRemove->pxNext;

   

    /* Make sure the index is left pointing to a valid item. */
    if( pxList->pxIndex == pxItemToRemove )
    {
        pxList->pxIndex = pxItemToRemove->pxPrevious;
    }
    else
    {
        mtCOVERAGE_TEST_MARKER();
    }

    pxItemToRemove->pxContainer = NULL;
    ( pxList->uxNumberOfItems )--;

    return pxList->uxNumberOfItems;
}
/*列表的遍历,是一个宏,在list.h中*/
#define listGET_OWNER_OF_NEXT_ENTRY( pxTCB, pxList )                                           \
    {                                                                                          \
        List_t * const pxConstList = ( pxList );                                               \
        /* Increment the index to the next item and return the item, ensuring */               \
        /* we don't return the marker used at the end of the list.  */                         \
        ( pxConstList )->pxIndex = ( pxConstList )->pxIndex->pxNext;    \
        /*如果当前列表到了列表末尾,则重新指向列表头*/
        if( ( void * ) ( pxConstList )->pxIndex == ( void * ) &( ( pxConstList )->xListEnd ) ) \
        {                                                                                      \
            ( pxConstList )->pxIndex = ( pxConstList )->pxIndex->pxNext;                       \
        }                                                                                      \
        ( pxTCB ) = ( pxConstList )->pxIndex->pvOwner;                                         \
    }
    
/*
	每调用一次这个函数,则列表项的pxIndex则会指向下一个列表项,并返回这个列表项的pxOwner值
    即列表项所属的任务控制块(TCB)
*/

2.FreeRTOS任务结构

FreeRTOS 的任务组成结构是由:任务控制块(TCB),任务栈,和任务函数三部分组成:
任务控制块(TCB):任务的数据结构,记录任务的各种属性描述
任务栈:在RAM中为任务分配的一片内存,维持着任务的正常运行,用于存储运行地址,函数参数等
任务函数:任务具体的执行过程,由用户定义
/*任务控制块(去除了条件编译选项)*/
typedef struct tskTaskControlBlock       
{
    volatile StackType_t * pxTopOfStack; 		/*< 任务控制块栈顶指针 */
    ListItem_t xStateListItem;                  /*< 列表项*/
    ListItem_t xEventListItem;                  /*< 列表项 */
    UBaseType_t uxPriority;                     /*< 任务优先级 */
    StackType_t * pxStack;                      /*< 任务控制块栈底指针*/
    char pcTaskName[ configMAX_TASK_NAME_LEN ]; /*< 任务名 */
}

 三种结构之间对应的关系如下图所示:

 3.FreeRTOS任务状态

FreeRTOS 是一个实时多任务操作系统,但不是多线程的,也就是说同一时刻只能有一个任务占用cpu 运行。
FreeRTOS 的任务状态有四种:运行状态(running),就绪状态(ready),挂起状态(suspended),阻塞状态(blocked),任务在同一时刻只能处于这四种任务状态中的一种。
运行状态(running):任务正占用了CPU。
就绪状态(ready):任务一旦创建成功就会处于就绪状态。若该任务优先级大于处于运行任务的优先级,则该任务将会立马运行;若不大于,则该任务将处于就绪态直到状态转换条件满足。
挂起状态(suspended):任务无限期的不能被调度器调度运行,只能通过vTaskSuspend 和 vTaskResume 函数使任务在挂起态与其他状态间切换。挂起态可以理解为一种特殊的阻塞状态,阻塞态对应的事件event 在这里对应着调用 vTaskSuspend 和 vTaskResume 函数。 处于挂起态的任务被加入到挂起队列里。
阻塞状态(blocked):当处于运行态的任务调用 vTaskDelay()相关的阻塞函数后,任务就处于了阻塞态。运行态转为阻塞态的过程对应着任务从就绪列表转移到阻塞列表里,暂时无法被调度器调度运行。当阻塞条件满足时,也就是 Event事件到来后,阻塞态就会转移到就绪态,准备调度运行。
四种状态对应的关系如下图所示:

4.FreeRTOS中的调度链表

 

/*就绪任务状态列表*/
static List_t pxReadyTasksLists[ configMAX_PRIORITIES ];
/*可以看到每个优先级的任务都有一个就绪链表,当任务调度时就会从最高优先级的列表依次查询*/
/*
/*阻塞任务状态列表*/
static List_t * volatile pxDelayedTaskList;
/* 所有的阻塞任务都将会放入此链表中直到有事件到达解除了阻塞状态才会把任务重新放到就绪链表中*/
/*挂起任务状态列表*/ 
static List_t xPendingReadyList;
任务控制块与就绪链表之间的关系:
由于 FreeRTOS 的任务控制块是动态创建的,就绪队列结构是动态链表结构,因此需要将任务控制块插入到就绪链表中,这里在任务控制块里加入了 xListItem数据结构,实际上插入到就绪队列的是该数据结构成员xStateListItem。根据上述 xListItem数据结构可以看出该数据结构是联系队列与控制块的“桥梁“。下图很好的说明了任务控制块是如何和列表建立起联系的,他们之间的联系就是后面任务调度的基础。

 xList 数据结构可以看做链表的头,该数据结构成员 pxIndex 用于指向当前任务主要是为轮状调度机制服务的,初始时 pxIndex 指向队列头,每当切换到下一个同优先级的任务时,该指针指向下一个队列成员,从而保证了同优先级下的任务都能公平的分享 cpu 处理的时间。

 

5.任务调度原理

上面已经说明了FreeRTOS中和任务调度相关的几种数据结构以及一个全局的就绪链表,任务的调度就是和这个就绪链表息息相关。FreeRTOS操作系统可以被配置为可剥夺形和不可剥夺形内核,这里主要介绍可剥夺性的调度原理。

FreeRTOS 的可剥夺性调度算法属于静态优先级调度机制,根据事先用户安排的任务优先级决定调度次序,每次总是选择就绪态任务中优先级最高的任务占用 cpu,创建任务时可以赋予每个任务不同数值大小的优先级,当然也可以相同。优先级的大小随同数值大小的增大而增大,优先级数值大小的范围限定在[0,configMAX_PRIORITIES]。系统选择调度任务总是当前就绪队列中优先级最高的那个任务,如果被选中的任务比当前处于运行状态的任务优先级要高高,则会发生抢占现象。其实,同优先级间也会抢占,目的是任务调度的公平性,同优先级的任务使用的是时间片轮转的方法,任务轮流执行。

FreeRTOS的调度器在每个可能引起上下文切换的点(高优先级任务结束阻塞状态或同优先级任务轮流执行等等),检查就绪队列,将当前就绪队列中最高优先级的任务与当前任务作比较,如果优先级高于当前任务的优先级,则切换上下文,当前任务优先级转换为就绪态,相反该任务转换为运行态。FreeRTOS 调度器是通过相应的任务控制块里的优先级及上下文信息来实现调度管理的。FreeRTOS 是通过任务控制块来进行任务的管理,任务调度器取得相应任务的控制块,进而分析相关控制块的优先级信息并作出比较,最后实现相关任务的切换与保护。

6.任务切换原理

6.1PendSV异常

FreeRTOS会利用 PendSV异常来处理上下文切换,实际上不止FreeRTOS,其他OS也是使用PendSV异常来处理上下文切换。在多任务环境下,内核每次切换任务时,都会进入PendSV中断服务函数里,进行切换任务栈操作。

上下文切换被触发的场合可以是:

1.执行一个系统调用

2.系统滴答定时器(Sys'Tick)中断

6.2任务切换场合

6.2.1 执行系统调用

执行系统调用就是直接调用可以引起任务切换的相关API函数,比如任务切换函数taskYIELD()。

#define taskYIELD()                        portYIELD()
/* Scheduler utilities. */
#define portYIELD()                                 \
    {                                                   \
        /* Set a PendSV to request a context switch. */ \
        portNVIC_INT_CTRL_REG = portNVIC_PENDSVSET_BIT; \
                                                        \
        /* Barriers are normally not required but do ensure the code is completely \
         * within the specified behaviour for the architecture. */ \
        __dsb( portSY_FULL_READ_WRITE );                           \
        __isb( portSY_FULL_READ_WRITE );                           \
    }   //启动PendSV中断进行任务切换

6.2.2 系统滴答定时器中断

在FreeRTOS中,系统滴答定时器(SysTick)中断服务函数中也会进行任务切换:

void xPortSysTickHandler( void )
{
    /* The SysTick runs at the lowest interrupt priority, so when this interrupt
     * executes all interrupts must be unmasked.  There is therefore no need to
     * save and then restore the interrupt mask value as its value is already
     * known - therefore the slightly faster vPortRaiseBASEPRI() function is used
     * in place of portSET_INTERRUPT_MASK_FROM_ISR(). */
    vPortRaiseBASEPRI();
    {
        /* Increment the RTOS tick. */
        if( xTaskIncrementTick() != pdFALSE )/*增加时钟计数器xTickCount的值*/
        {
            /* A context switch is required.  Context switching is performed in
             * the PendSV interrupt.  Pend the PendSV interrupt. */
            portNVIC_INT_CTRL_REG = portNVIC_PENDSVSET_BIT;/*满足条件启动PendSV中断*/
        }
    }

    vPortClearBASEPRIFromISR();
}

6.3PendSV中断服务函数

PendSV中断服务程序:

__asm void xPortPendSVHandler( void )
{
    extern uxCriticalNesting;
    extern pxCurrentTCB;/*一个重要的全局变量,指向当前任务的TCB*/
    extern vTaskSwitchContext;

/* *INDENT-OFF* */
    PRESERVE8	"8字节对齐"

    "下面这一句是将当前psp堆栈指针值寄存在r0中,因为psp等下会变"
    mrs r0, psp
    "强制指令清空"
    isb
	"下面这一句,是将pxCurrentTCB变量的地址寄存在r3中"
    ldr r3, =pxCurrentTCB /* Get the location of the current TCB. */
    "下面这一句,是将pxCurrentTCB指向内存区域的第一个元素地址寄存在r2中"
	"而pxCurrentTCB指向内存区域的第一个元素值,"
	"其实就是记录当前TCB的栈顶地址变量(前面已经介绍了TCB结构体)."
	"执行完下一句后,r2中的值就是指向当前TCB栈顶地址的变量"
    ldr r2, [ r3 ]
	"下面这一句是将寄存器r4-r11的值依次压入当前栈中,r0中的值会减少32(4x8)"
    stmdb r0 !, { r4 - r11 } /* Save the remaining registers. */
  	"此时的r0中的值就是当前任务压栈后的栈顶地址,执行完下一句后,"
	"就是将当前TCB栈顶地址的变量重新指向更新后的栈顶地址"
    "将新的栈顶地址保存在任务控制块的第一个字段中"
    str r0, [ r2 ] /* Save the new top of stack into the first member of the TCB. */
	"因为该函数是中断服务程序,由于CM3的双堆栈特性,所以,"
	"此处的sp表示的是msp(主堆栈)。下面这一句,是依次将r14、r3存入msp中"
	"存放r14的原因是,后面需要使用到这个lr值;存放r3的原因是,后面要"
	"使用r3获取pxCurrentTCB,r3保存了当前任务的任务控制块,其实使用pxCurrentTCB重新加载到寄存器中也可以"
    stmdb sp !, { r3, r14 }
  	"下面这一句,是将系统调用的最大中断优先级值寄存到r0中,为了屏蔽一部分的中断"
    mov r0, #configMAX_SYSCALL_INTERRUPT_PRIORITY
    msr basepri, r0
    "强制同步数据"
    dsb
    "强制清除指令"
    isb
    "跳转到vTaskSwitchContext函数,此函数的目的是为了更新pxCurrentTCB"
	"指向的地址,获取下一个需要运行的任务"
     "同时也可以看出为什么上面要压栈r14、r3了,因为存在子函数"
    bl vTaskSwitchContext
    "在更新pxCurrentTCB之后,下面这2句,是将所有的中断打开"
    mov r0, #0
    msr basepri, r0
    "下面这一句,是将当初压栈的r3、r14出栈"
    ldmia sp !, { r3, r14 }
	"下面这两句,是将r0存入更新后TCB的栈顶变量"
    "结果刚刚vTaskSwitchContext函数,pxCurrentTCB已经指向了新的任务,所以读取r3处保存的地址的值发生了变化"
    ldr r1, [ r3 ]
    ldr r0, [ r1 ] /* The first item in pxCurrentTCB is the task top of stack. */
    "下面这一句,是将新任务栈中的前8个值依次出栈到r4-r11,也就是新的现场"
    ldmia r0 !, { r4 - r11 } /* Pop the registers and the critical nesting count. */
  	"更新进程栈PSP的值"
    msr psp, r0
    "清除指令"
    isb
    "跳转指令,这个会对r14的值进行判断,如果后4位是0x0d,"
	"会根据psp的值,出栈到pc、r1等寄存器"
    "之后硬件自动恢复寄存器 R0~R3、R12、LR、PC和 xPSR 的值,"
    "确定异常返回以后应该进入处理器模式还是进程模式、使用主栈指针(MSP)还是进程栈指针(PSP)。"
    bx r14
    nop
/* *INDENT-ON* */
}

上面的汇编程序就说明了内核如何调度了,每次进入pendsv中断函数内:

●压栈所需要的寄存器值,比如r14、r3

●进入子程序,更新当前任务指针pxCurrentTCB,即寻找需要运行的任务

●退出子程序,出栈新的任务现场,结束跳转

●在跳转指令中,指令系统会计算出pc应该指向的值

6.4查找下一个要运行的任务

PendSV中断服务程序中调用函数 vTaskSwitchContext()来获取下一个要运行的任务,也就是查找已经就绪了的优先级最高的任务,缩减后(去掉条件编译)函数源码如下∶

void vTaskSwitchContext( void )
{
    if( uxSchedulerSuspended != ( UBaseType_t ) pdFALSE )//如果调度器挂起则不进行任务调度
    {
        xYieldPending = pdTRUE;
    }
    else
    {
        xYieldPending = pdFALSE;
        traceTASK_SWITCHED_OUT();
        taskCHECK_FOR_STACK_OVERFLOW();
        taskSELECT_HIGHEST_PRIORITY_TASK(); //获取下一个要运行的任务
        traceTASK_SWITCHED_IN();

    }
}

FreeRTOS中查找下一个要运行的任务有两种方法;一个是通用的方法,另外一个就是使用硬件的方法,这个在前面讲解 FreeRTOSCofnig.h 文件的时候提到过了,至于选择哪种方法是通过宏 configUSE_PORT_OPTIMISED_TASK_SELECTION 来决定的。当这个宏为1的时候,则使用硬件的方法;否则,就是使用通用的方法。我们来看一下这两个方法的区别。

/*通用方法*/
#define taskSELECT_HIGHEST_PRIORITY_TASK()                                \
    {                                                                         \
        UBaseType_t uxTopPriority = uxTopReadyPriority;                       \
                                                                              \
        /* 找到包含就绪任务的最高优先级队列 */      \
        /*pxReadyTasksLists[ uxTopPriority ]之前提到的就绪链表*/
        while( listLIST_IS_EMPTY( &( pxReadyTasksLists[ uxTopPriority ] ) ) ) \
        {                                                                     \
            configASSERT( uxTopPriority );                                    \
            --uxTopPriority;                                                  \
        }                                                                     \
                                                                              \
        /* 已经找到了有就绪任务的优先级了,接下来就是从对应的列表中找出下一个要运行的任务,
        查找方法就是使用函数 listGET_OWNER_OF_NEXT_ENTRY ()来获取列表中的下一个列表项,
        然后将获取到的列表项所对应的任务控制块赋值给 pxCurrentTCB,这样就确定了下一个要运行的任务 */                    \
        listGET_OWNER_OF_NEXT_ENTRY( pxCurrentTCB, &( pxReadyTasksLists[ uxTopPriority ] ) ); \
        uxTopReadyPriority = uxTopPriority;                                                   \
    } /* taskSELECT_HIGHEST_PRIORITY_TASK */
/*硬件方法*/
#define taskSELECT_HIGHEST_PRIORITY_TASK()                                                  \
    {                                                                                           \
        UBaseType_t uxTopPriority;                                                              \
                                                                                                \
        /* 找到包含就绪任务的最高优先级队列 */                         \
        portGET_HIGHEST_PRIORITY( uxTopPriority, uxTopReadyPriority );                          \
        configASSERT( listCURRENT_LIST_LENGTH( &( pxReadyTasksLists[ uxTopPriority ] ) ) > 0 ); \
        /*从对应的就绪链表中找出下一个需要运行的任务*/
        listGET_OWNER_OF_NEXT_ENTRY( pxCurrentTCB, &( pxReadyTasksLists[ uxTopPriority ] ) );   \
    } /* taskSELECT_HIGHEST_PRIORITY_TASK() */
 
/*寻找存在就绪任务的最高优先级队列使用的是硬件指令clz*/
#define portGET_HIGHEST_PRIORITY( uxTopPriority, uxReadyPriorities )    uxTopPriority = ( 31UL - ( uint32_t ) __clz( ( uxReadyPriorities ) ) )
/*
使用硬件方法的时候 uxTopReadyPriority 不代表处于就绪态的最高优先级,
而是使用每个bit 代表一个优先级,bit0 代表优先级0,bit31就代表优先级 31,
当某个优先级有就绪任务时,则将其对应的 bit 置1。从这里就可以看出,如果使用硬件方法,
则最多只能有32 个优先级。__clz(uxReadyPriorities)就是计算 uxReadyPriorities 的
前导零个数。前导零个数就是指从最高位开始(bit31)到第一个为1 的 bit,其间0的个数,例子如下∶
二进制数1000000000000000的前导零个数就为0。
二进制数000010011110001的前导零个数就是4。

得到 uxTopReadyPriority的前导零个数以后,
再用31 减去这个前导零个数得到的就是处于就绪态的最高优先级了,
比如优先级 30 时处于就绪态的最高优先级,30的前导零个数为1,那么31-1=30,
得到处于就绪态的最高优先级为30。
*/

6.5FreeRTOS时间片调度

前面提到,FreeRTOS支持多个任务同时拥有一个优先级,这些任务的调度是一个值得考虑的问题。FreeRTOS中允许一个任务运行一个时间片(一个时钟节拍的长度)后让出 CPU的使用权,让拥有同优先级的下一个任务运行。至于下一个要运行哪个任务,这在6.4节分析过了。FreeRTOS中的这种调度方法就是时间片调度。时间片调度发生在系统滴答定时器的中断服务函数中,前面已经给出了SysTick的中断服务函数。

void xPortSysTickHandler( void )
{
    {
      	/*调度条件*/
        if( xTaskIncrementTick() != pdFALSE )/*增加时钟计数器xTickCount的值*/
        {
            portNVIC_INT_CTRL_REG = portNVIC_PENDSVSET_BIT;/*满足条件启动PendSV中断*/
        }
    }

    vPortClearBASEPRIFromISR();
}

BaseType_t xTaskIncrementTick( void )
{
    TCB_t * pxTCB;
    TickType_t xItemValue;
    BaseType_t xSwitchRequired = pdFALSE;

    /* Called by the portable layer each time a tick interrupt occurs.
     * Increments the tick then checks to see if the new tick value will cause any
     * tasks to be unblocked. */
    traceTASK_INCREMENT_TICK( xTickCount );

    if( uxSchedulerSuspended == ( UBaseType_t ) pdFALSE )
    {
      ......
		/*需要配置可抢占调度和时间片轮转*/
        #if ( ( configUSE_PREEMPTION == 1 ) && ( configUSE_TIME_SLICING == 1 ) )
            {
        		/*查找当前任务对应的就绪链表下是否还有其他任务*/
                if( listCURRENT_LIST_LENGTH( &( pxReadyTasksLists[ pxCurrentTCB->uxPriority ] ) ) > ( UBaseType_t ) 1 )
                {
                  	/*如果还有其他任务则发生一次调度*/
                    xSwitchRequired = pdTRUE;
                }
                else
                {
                    mtCOVERAGE_TEST_MARKER();
                }
            }
        #endif /* ( ( configUSE_PREEMPTION == 1 ) && ( configUSE_TIME_SLICING == 1 ) ) */
       
    }
    return xSwitchRequired;
}

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

FreeRTOS任务调度原理 的相关文章

  • FreeRTOS系列

    本文主要介绍如何在任务或中断中向队列发送消息或者从队列中接收消息 使用STM32CubeMX将FreeRTOS移植到工程中 创建两个任务以及两个消息队列 并开启两个中断 两个任务 Keyscan Task 读取按键的键值 并将键值发送到队列
  • Freertos中vTaskDelay()是怎么用的

    1 常见的使用场景 void vLED Task void pvParameters while 1 Heartbeat LED vTaskDelay 1000 portTICK RATE MS 说明 上面这段代码的意思是 led翻转后经过
  • freertos---软定时器

    一 软件定时器介绍 freeRTOS软件定时器的时基是基于系统时钟节拍实现的 可以创建很多个 在硬件定时器资源不充足的情况下非常有用 软件定时器一般用作周期性地执行函数 在创建软件定时器时指定软件定时器的回调函数 在回调函数中实现相应的功能
  • freeRTOS手册 第六章 . 中断管理

    如果我对本翻译内容享有所有权 允许任何人复制使用本文章 不会收取任何费用 如有平台向你收取费用与本人无任何关系 第六章 中断管理 章节介绍和范围 事件 嵌入式实时系统必需对环境中的事件做出响应 比如 外部网络设备收到一个发送给TCP IP栈
  • 基于HAL库的FREERTOS----------一.任务

    FreeROTS 就是一个免费的 RTOS 类系统 这里要注意 RTOS 不是指某一个确定的系统 而是指一类系统 比如 UCOS FreeRTOS RTX RT Thread 等这些都是 RTOS 类操作系统 FreeRTOS 是 RTOS
  • FreeRTOS记录(九、一个裸机工程转FreeRTOS的实例)

    记录一下一个实际项目由裸机程序改成FreeRTOS 以前产品的平台还是C8051单片机上面的程序 硬件平台改成了STM32L051 同时使用STM32CubeMX生成的工程 使用FreeRTOS系统 EEPROM数据存储读取函数修改更新 2
  • Error: L6218E: Undefined symbol vApplicationGetIdleTaskMemory (referred from tasks.o).

    我用的是F103ZET6的板子 移植成功后 编译出现两个错误是关于stm32f10x it c 里 void SVC Handler void void PendSV Handler void 两个函数的占用问题 随后编译出现以下两个问题
  • FreeRTOS,串口中断接收中使用xQueueOverwriteFromISR()函数,程序卡死在configASSERT

    原因 UART的中断优先级设置的太高 高于了configMAX SYSCALL INTERRUPT PRIORITY宏定义的安全中断等级 UART的中断等级小于等于宏定义的优先等级即可
  • 啊哈C的简单使用

    打开啊哈C 新建一个程序输出hello world include
  • 基于STM32的FreeRTOS学习之中断测试实验(五)

    记录一下 方便以后翻阅 本章内容是接着上一章节进行的实际演练 1 实验目的 FreeRTOS可以屏蔽优先级低于configMAX SYSCALL INTERRUPT PRIORITY的中断 不会屏蔽高于其的中断 本次实验就是验证这个说法 本
  • Arduino IDE将FreeRTOS用于STM32

    介绍 适用于STM32F103C8的FreeRTOS STM32F103C是一种能够使用FreeRTOS的ARM Cortex M3处理器 我们直接在Arduino IDE中开始使用STM32F103C8的FreeRTOS 我们也可以使用K
  • [FreeRTOS入门学习笔记]定时器

    定时器的使用步骤 1 定义一个handle xTimerCreate创建 2 启动定时器 在Task1中调用 通过队列通知守护任务来执行定时器任务 要再config头文件中定义守护任务相关配置 虽然定时器是在task1中启动 但是定时器的任
  • FreeRTOS学习---“定时器”篇

    总目录 FreeRTOS学习 任务 篇 FreeRTOS学习 消息队列 篇 FreeRTOS学习 信号量 篇 FreeRTOS学习 事件组 篇 FreeRTOS学习 定时器 篇 FreeRTOS提供了一种软件定时器 用来快速实现一些周期性的
  • RT-Thread记录(五、RT-Thread 临界区保护与FreeRTOS的比较)

    本文聊聊临界区 以及RT Thread对临界区的处理 通过源码分析一下 RT Thread 对临界区保护的实现以及与 FreeRTOS 处理的不同 目录 前言 一 临界区 1 1 什么是临界区 1 2 RTOS中的临界区 二 RT Thre
  • FreeRTOS轻量级同步--任务通知

    1 简介 在FreeRTOS的配置参数中的configUSE TASK NOTIFICATIONS宏打开 一般RTOS会默认打开 如图1所示 图1 notify宏开关 RTOS在创建任务时 会创建一个32位的通知值ulNotifiedVal
  • FreeRTOS笔记(二)

    FreeRTOS笔记 二 静态任务 文章目录 FreeRTOS笔记 二 静态任务 一 任务定义 二 任务创建 2 1 定义任务栈 2 2 定义任务函数 2 3 定义任务控制块 2 4 实现任务创建函数 三 实现就绪列表 3 1 定义就绪列表
  • FreeRTOS临界段

    1 临界段 在访问共享资源时不希望被其他任务或者中断打断的代码 这段要执行的代码称为临界段代码 2 设置临界段的目的 保护共享资源 例如 全局变量 公共函数 不可重入函数 函数里面使用 了一些静态全局变量 malloc 等 保护外设的实时性
  • 13-FreeRTOS任务创建与删除

    任务创建和删除API函数位于文件task c中 需要包含task h头文件 task h里面包函数任务的类型函数 例如 对xTaskCreate的调用 通过指针方式 返回一个TaskHandle t 变量 然后可将该变量用vTaskDele
  • FreeRTOS之系统配置

    1 FreeRTOS的系统配置文件为FreeRTOSConfig h 在此配置文件中可以完成FreeRTOS的裁剪和配置 在官方的demo中 每个工程都有一个该文件 2 先说一下 INCLUDE 开始的宏 使用 INCLUDE 开头的宏用来
  • 如何将 void* 转换为函数指针?

    我在 FreeRTOS 中使用 xTaskCreate 其第四个参数 void const 是传递给新线程调用的函数的参数 void connect to foo void const task params void on connect

随机推荐

  • Unity用UGUI实现省份选择

    参考 Unity3D中使用UGUI实现省市选择器 YOLO TO GAME的博客 CSDN博客
  • Unity3D中Resources动态加载图片

    问题 xff1a unity的Resources动态加载就不必多说了 xff0c 这里出现的问题是当我把图片放入Resources文件夹后 xff0c 使用Resources Load xff08 datapath xff09 却并没有出现
  • Navicat报错:1045-Access denied for user root@localhost(using password:YES)

    解决 xff1a Navicat报错 xff1a 1045 Access denied for user root 64 localhost using password YES SET PASSWORD FOR 39 root 39 64
  • UE4添加音乐、音效

    目录 一 目的 xff1a 1 想 xff1a UE4添加音乐 音效 二 参考 1 三 操作 xff1a 完成 1 前述 xff1a 1 导入音乐 1 创建Cue 1 Cue进行设置 1 创建音乐 1 播放和暂停音乐 一 目的 xff1a
  • 《嵌入式C语言自我修养》书评

    首先 xff0c 介绍本书的内容 笔者从嵌入式工程师的视角出发 xff0c 先为我们初学者补上硬件相关基础 xff0c 如计算机工作原理和系统结构 xff08 理解程序编译 链接 安装和运行机制 xff09 CPU的工作原理 xff08 理
  • SLAM14讲之第五讲--像素坐标系、畸变、双目相机深度求解

    像素坐标系 由相似三角形的定义可得 xff1a 这是相对于成像平面的坐标变化 xff0c 我们实际上所得到的图片还要经历一层成像平面到像素平面的变换 xff0c 实际上就是相差了一个缩放和原点的平移 如此我们定义 xff1a u为横轴向右与
  • python webkit 异步抓取页面数据

    usr bin python from ghost import Ghost class FetcherCartoon def getCartoonUrl self url if url is None return false todo
  • 树莓派 Ubuntu mate 16.04使用VNC开启远程桌面

    1 安装 vncserver sudo apt span class token operator span get span class token operator span y install vnc4server 2 启动 vncs
  • 数学建模(四)-----最优化问题-----Simulate Anneal Arithmetic

    模拟退火算法的应用很广泛 xff0c 可以较高的效率求解最大截问题 Max Cut Problem 0 1背包问题 Zero One Knapsack Problem 图着色问题 Graph Colouring Problem 调度问题 S
  • 路径规划的优化

    因为这个求N个点的最短路径是将把所有可能的走法都可能尝试一遍 这样的话 如果计算十几个点之间的最短路径是没有问题的 但是问题就在如果超过二十个点位 那么最坏的情况就是需要计算20的阶乘个 这个计算是相当高的 可能会把线上的服务器打爆 或者计
  • SPL06电容式压力传感器数据读取与处理(基于STM32)

    该例程使用的开发板为正点原子的精英板F103 xff0c 相关资料请大家自行去正点原子论坛下载 首先来看一下SPL06的简介 xff0c SPL06的压强测量范围30kPa 110kPa xff0c 最大供电电压为3 6V xff0c 相对
  • 企业微信开发实战(六、自建应用-审批流程引擎之配置可信任域名、创建审批模版、发起审批)

    文章目录 4 自建应用审批状态变化通知回调4 1概述4 2代码实战 5 查询自建应用审批单当前状态5 1概述5 2代码实战 源码赞赏 4 自建应用审批状态变化通知回调 4 1概述 1 企业可以在管理后台 自建应用 设置API接收中 xff0
  • FreeRTOS内存管理之heap_4.c

    FreeRTOS内存管理之heap 4 c源码解析 每当创建任务 队列 互斥量 软件定时器 信号量或事件组时 xff0c RTOS内核会为它们分配RAM 标准函数库中的malloc 和free 函数有些时候能够用于完成这个任务 xff0c
  • git图形化管理工具

    一 独立客户端工具 1 GitHub for Desktop 全球开发人员交友俱乐部提供的强大工具 xff0c 功能完善 xff0c 使用方便 对于使用GitHub的开发人员来说是非常便捷的工具 界面干净 xff0c 用起来非常顺手 xff
  • ROS功能包

    ROS package介绍 package是什么呢 xff1f 指的是一种特定的文件结构和文件夹组合 通常将实现同一个具体功能的程序代码放到一个package中 xff0c 比如实现相机数据采集这一功能 文件结构 CMakeLists tx
  • 坐标转换tf

    tf介绍 坐标转换 TransForm 位置和姿态 坐标变换是空间实体的位置描述 xff0c 是从一种坐标系统变换到另一种坐标系统的过程 通过建立两个坐标系统之间一一对应关系来实现下图为机器人几个部件之间的坐标关系 tf概念 tf是一个用户
  • 经典Windows编程书单

    说好的这次写一个图形编程书单 但是看起来不是很好整理 xff0c 这类书散落的家里到处都是 先把经典Windows编程的书整理一下吧 xff0c 不过Windows的也到处都是很多都找不到了 xff0c 只能把找到的拍个照 xff0c 可能
  • vscode设置C++代码格式化(Clang-Format)

    vscode中只要安装了C C 43 43 扩展后 xff0c 在C C 43 43 源文件中右键就能看到格式化文档的选项 xff0c 这样就能通过该选项或者其快捷键 xff08 Shift 43 Alt 43 F xff09 来实现快速格
  • git pull覆盖了本地未push的代码解决方案

    一 问题背景 情况 xff1a 本地代码写完后 xff0c git push上去github xff0c 然后报错 xff0c 提示要先git pull pull之后 xff0c 失败了或者覆盖了本地未push的代码 二 解决方案 2 1
  • FreeRTOS任务调度原理

    1 FreeRTOS的列表和列表项 列表和列表项是FreeRTOS中一个非常重要的数据结构 xff0c 是FreeRTOS的基石 要想看懂FreeRTOS的源码并学习其中的原理 xff0c 我们必须先了解一下这个数据结构 这个数据结构也是和