列表的插入 初始化
插入步骤
在图3-1中我们看到寄存器xPSR被初始为0x01000000,其中bit24被置1,表示使用Thumb指令;寄存器PC被初始化为任务函数指针vTask_A,这样当某次任务切换后,任务A获得CPU控制权,任务函数vTask_A被出栈到PC寄存器,之后会执行任务A的代码;LR寄存器初始化为函数指针prvTaskExitError,这是由移植层提供的一个出错处理函数。当中断发生时,LR被设置成中断要返回的地址,但是每个任务都是一个死循环,正常情况下不应该退出任务函数,所以一旦从任务函数退出,说明那里出错了,这个时候会调用寄存器LR指向的函数来处理这个错误,即prvTaskExitError;根据ATPCS(ARM-Thumb过程调用标准),我们知道子函数调用通过寄存器R0~R3传递参数,在文章的最开始讲xTaskCreate()函数时,提到这个函数有一个空指针类型的参数pvParameters,当任务创建时,它作为一个参数传递给任务,所以这个参数被保存到R0中,用来向任务传递参数。
图3-1:初始化任务堆栈
vApplicationMallocFailedHook()
在很多场合中,某个硬件资源只有一个,当低优先级任务占用该资源的时候,即便高优先级任务也只能乖乖的等待低优先级任务释放资源。这里高优先级任务无法运行而低优先级任务可以运行的现象称为“优先级翻转”。
嵌套的中断
在CM3内核以及NVIC的深处,就已经内建了对中断嵌套的全力支持,根本无需使用用汇编写封皮代码(wrapper code)。事实上,我们要做的就只是为每个中断适当地建立优先级,
不用再操心别的。表现在:
第一、 NVIC和CM3处理器会为我们排出优先级解码的顺序。因此,在某个异常正在响
应时,所有优先级不高于它的异常都不能抢占之,而且它自己也不能抢占自己。
第二、 有了自动入栈和出栈,就不用担心在中断发生嵌套时,会使寄存器的数据损毁,
从而可以放心地执行服务例程。
然而,有一件事情却必须更加一丝不苟地处理了,否则有功能紊乱甚至死机的危险,这
就是计算主堆栈容量的最小安全值。我们已经知道,所有服务例程都只使用主堆栈。所以当
中断嵌套加深时,对主堆栈的压力会增大:每嵌套一级,就至少再需要8个字,即32字节的堆栈空间——而且这还没算上ISR对堆栈的额外需求,并且何时嵌套多少级也是不可预料的。如果主堆栈的容量本来就已经所剩无几了,中断嵌套又突然加深,则主堆栈有被用穿的凶险。这就好像已经表现出了高血压危象的时候,情绪又一激动,就容易导致中风一般。在这里,堆栈溢出同样是很致命的,它会使入栈数据与主堆栈前面的数据区发生混迭,使这些数据被破坏;若服务例程又更改了混迭区的数据,则堆栈内容被破坏。这么一来在执行中断返回后,系统极可能功能紊乱,甚至当场被一击必杀——程序跑飞/死机!另一个要注意的,是相同的异常是不允许重入的。因为每个异常都有自己的优先级,并且在异常处理期间,同级或低优先级的异常是要阻塞的,因此对于同一个异常,只有在上次实例的服务例程执行完毕后,方可继续响应新的请求。由此可知,在SVC服务例程中,就不
得再使用SVC指令,否则将fault伺候
另一个要注意的,是相同的异常是不允许重入的。因为每个异常都有自己的优先级,并
且在异常处理期间,同级或低优先级的异常是要阻塞的,因此对于同一个异常,只有在上次实例的服务例程执行完毕后,方可继续响应新的请求。由此可知,在SVC服务例程中,就不得再使用SVC指令,否则将fault伺候。
这个是对于低优先级的中断,高优先级的中断处理时候,有低优先级的进来时,高优先级完成后,不再执行POP指令,而是直接执行中断指令,指令执行完成,再POP节约中断完成时间,
LR的值被自动更新为特殊的EXC_RETURN,
前面已经讲到,在进入异常服务程序后, LR的值被自动更新为特殊的EXC_RETURN,这是一个高28位全为1的值,只有[3:0]的值有特殊含义,如表9.3所示。当异常服务例程把这个值送往PC时,就会启动处理器的中断返回序列。因为LR的值是由CM3自动设置的,所以只要没有特殊需求
模式分为两种:处理者模式(handler)和线程模式(thread);
权限级别也分为两种:特权级别、用户级别
两种模式为handler模式和线程(thread)模式,这两种模式是为了区别正在执行代码的类型;handler模式为异常处理例程的代码;线程模式为普通应用程序的代码
相对延时:总体时间不是一定的 只是延时部分一定,总体时间会根据上下变动(前边函数的执行时间),更具执行的函数来判断
计算唤醒时间点:进入延时函数开始计算延时时间
绝对延时:保证while()里边的延时是一定的,除了中断打断外,基本是准确的。
一共有两个参数,函数开始时候的时间点和结束时间点,具体的延时函数会变化,
计算唤醒时间点:上一次时间点+延时时间
if( xConstTickCount == ( TickType_t ) 0U )// 发生溢出
{
taskSWITCH_DELAYED_LISTS(); 进行交换
}
else
{
mtCOVERAGE_TEST_MARKER();
}
中断
#define portNVIC_INT_CTRL_REG ( * ( ( volatile uint32_t * ) 0xe000ed04 ) )
任务创建
任务创建过程:先申请任务堆栈,堆栈申请成功后,给任务控制块申请内存。
1:任务堆栈和任务控制块的联系是什么? 先创建一块大的地址(堆栈),然后把新的任务控制块地址指向推栈,即可申请成功
任务控制块:
任务堆栈:
图3-1:初始化任务堆栈
创建两个任务时候的流程
( ( ( ( size_t ) usStackDepth ) * sizeof( StackType_t ) ) )=512 start_tast 128*4
( sizeof( TCB_t )=92
( ( ( ( size_t ) usStackDepth ) * sizeof( StackType_t ) ) )=520 空闲任务 130*4
( sizeof( TCB_t )=92
( ( ( ( size_t ) usStackDepth ) * sizeof( StackType_t ) ) )=1040 时间任务 260*4
( sizeof( TCB_t )=92
( ( ( ( size_t ) usStackDepth ) * sizeof( StackType_t ) ) )=512 task1_task 128*4
( sizeof( TCB_t )=92
( ( ( ( size_t ) usStackDepth ) * sizeof( StackType_t ) ) )=512 task2_task 128*4
( sizeof( TCB_t )=92
添加到就绪列表函数()
初始化后的就绪列表
static void prvAddNewTaskToReadyList( TCB_t *pxNewTCB ){}
for( uxPriority = ( UBaseType_t ) 0U; uxPriority < ( UBaseType_t ) configMAX_PRIORITIES; uxPriority++ )
{
vListInitialise( &( pxReadyTasksLists[ uxPriority ] ) );
}
每一个优先级都用一个列表来表示:
vListInitialise( &xDelayedTaskList1 );
vListInitialise( &xDelayedTaskList2 );
vListInitialise( &xPendingReadyList );
struct xLIST_ITEM
{
listFIRST_LIST_ITEM_INTEGRITY_CHECK_VALUE
configLIST_VOLATILE TickType_t xItemValue;
struct xLIST_ITEM * configLIST_VOLATILE pxNext;
struct xLIST_ITEM * configLIST_VOLATILE pxPrevious;
void * pvOwner;
void * configLIST_VOLATILE pvContainer;
listSECOND_LIST_ITEM_INTEGRITY_CHECK_VALUE
}
void vListInitialise( List_t * const pxList )
{
pxList->pxIndex = ( ListItem_t * ) &( pxList->xListEnd );
pxList->xListEnd.xItemValue = portMAX_DELAY;
pxList->xListEnd.pxNext = ( ListItem_t * ) &( pxList->xListEnd ); pxList->xListEnd.pxPrevious = ( ListItem_t * ) &( pxList->xListEnd );
pxList->uxNumberOfItems = ( UBaseType_t ) 0U;
listSET_LIST_INTEGRITY_CHECK_1_VALUE( pxList );
listSET_LIST_INTEGRITY_CHECK_2_VALUE( pxList );
}
时间片调度
时间片调度:在FreeRTOS中,相同优先级的任务,采用时间片调度,第一个任务用100ms 时间用完后,滴答定时器产生中断,回会检查相同优先级下边是否还有任务,如果有,则任务切换;
#define taskSCHEDULER_SUSPENDED ( ( BaseType_t ) 0 )
#define taskSCHEDULER_NOT_STARTED ( ( BaseType_t ) 1 ) //其他状态处于运行当中
#define taskSCHEDULER_RUNNING ( ( BaseType_t ) 2 )
void SysTick_Handler(void)
{
if(xTaskGetSchedulerState()!=taskSCHEDULER_NOT_STARTED)//系统已经运行
{
xPortSysTickHandler();
}
}
BaseType_t xTaskGetSchedulerState( void )
{
BaseType_t xReturn;
if( xSchedulerRunning == pdFALSE )// 调度器没有运行,则不做任何切换,
{
xReturn = taskSCHEDULER_NOT_STARTED;
}
Else//调度器开始运行,则可以做切换,不是很明白这个逻辑
{
if( uxSchedulerSuspended == ( UBaseType_t ) pdFALSE )
{
xReturn = taskSCHEDULER_RUNNING;
}
else
{
xReturn = taskSCHEDULER_SUSPENDED;
}
}
return xReturn;
}
FreeRTOS内存部分
需要加上结构体的大小
Heap_4
通过首地址 找到对其地址; 设置开始链表指向,设置完成后,地址更新为END地址
END地址减去结构体地址,
队列部分:
pucQueueStorage = ( ( uint8_t * ) pxNewQueue ) + sizeof( Queue_t );
获取的首地址:队列结构体大小+新队列的首地址
相同优先级的内存处理;
任务的切换:滴答定时器:查找延时列表
延时函数,delay_ms(),引起任务切换,delay_us(),不会引起任务切换
if( uxSchedulerSuspended != ( UBaseType_t ) pdFALSE ) 被挂起
{
xYieldPending = pdTRUE;//
}
else
{
//执行任务
}
注意:同优先级的调用
#define NV_DATA ((NvData_stu_t *)EEPROM_BASE_ADDR)
定义为一个结构体指针,调用地址即可;
void EEPROM_WriteMultiBytes(uint32_t addr, void *pData, uint16_t len)
EEPROM_WriteMultiBytes((uint32_t)&(NV_DATA->sysParam),pSysParam,sizeof(NvSystemParam_stu_t));
前导零