略
1,名字:滴答定时器,系统节拍,SYSTICK。
2,特性
- 属于Coretx-M内核
- 24位,向下计数。
- 触发系统定时器中断为系统提供服务的。
3,4个寄存器
- 系统定时器控制及状态寄存器(地址:0xE000_E010)
位段 | 名称 | 类型 | 复位值 | 描述 |
16 | COUNTFLAG | [R | 0 | 如果在上次读取本寄存器后,系统定时器已经数到了0,则该位为1,如果读取该位,该位将自动清零 |
2 | CLKSOURCE | R / W. | 0 | 0 =外部时钟源(STCLK) 1 =内核时钟(FCLK) |
1 | TICKINT | R / W. | 0 | 1 = SysTick倒数到0时产生SysTick异常请求 0 =数到0时无动作 |
0 | ENABLE | R / W. | 0 | 系统定时器定时器的使能位 |
- 系统定时器重装载数值寄存器(地址:0xE000_E014)
位段 | 名称 | 类型 | 复位值 | 描述 |
23:0 | RELOAD | R / W. | 0 | 当倒数至零时,将被重装载的值 |
- 系统定时器当前数值寄存器(地址:0xE000_E018)
位段 | 名称 | 类型 | 复位值 | 描述 |
23:0 | 当前 | R / Wc中 | 0 | 读取时返回当前倒计数的值,写它则使之清零,同时还会清除在系统定时器控制及状态寄存器中的 COUNTFLAG标志 |
- 系统定时器的校准数值寄存器(地址:0xE000_E01C)
位段 | 名称 | 类型 | 复位值 | 描述 |
31 | NOREF | [R | - | 1 =没有外部参考时钟(STCLK不可用) 0 =外部参考时钟可用 |
三十 | SKEW | [R | - | 1 =校准值不是准确的10ms 0 =校准值是准确的10ms |
23:0 | TENMS | R / W. | 0 | 芯片设计者应该通过的Cortex-M3的输入信号提供该数值。该若读值 回零,则表示无法使用校准功能 |
typedef struct { __IOM uint32_t CTRL; / *!<偏移量:0x000(R / W)SysTick控制和状态 寄存器* / __IOM uint32_t LOAD; / *!<Offset:0x004(R / W)SysTick重载值 寄存器* / __IOM uint32_t VAL; / *!<Offset:0x008(R / W)SysTick当前值 寄存器* / __IM uint32_t CALIB; / *!<偏移量:0x00C(R /)SysTick校准寄存器* / } SysTick_Type; |
4,FreeRTOS的操作系统如何配置的?
SystemClock_Config() - “
1 | / **配置Systick中断时间HAL_RCC_GetHCLKFreq()= 操作:SysTick-> LOAD,SysTick-> VAL,SysTick-> CTRL(内核时钟,产生异常,工作使能) |
2 | / **配置Systick * / 操作:SysTick-> CTRL(内核时钟) |
3 | / * SysTick_IRQn中断配置* / HAL_NVIC_SetPriority(SysTick_IRQn,15,0); |
5,中断服务函数
SysTick_Handler()---“osSystickHandler()---”
void osSystickHandler(void)
{
的#if(INCLUDE_xTaskGetSchedulerState == 1) /* 若未开启了系统调度,直接返回 */
如果(xTaskGetSchedulerState()!= taskSCHEDULER_NOT_STARTED)/*系统调度器未启动,也直接返回*/
{
#ENDIF / * * INCLUDE_xTaskGetSchedulerState /
xPortSysTickHandler(); /* 调度处理*/
#if(INCLUDE_xTaskGetSchedulerState == 1)
}
#endif / * INCLUDE_xTaskGetSchedulerState * /
}
void xPortSysTickHandler(void)
{
portDISABLE_INTERRUPTS(); /* 关闭中断 */
{
/ *增加RTOS tick。* /
if(xTaskIncrementTick()!= pdFALSE){ /*递增系统 时钟 */
portNVIC_INT_CTRL_REG = portNVIC_PENDSVSET_BIT; /* 触发pendSv中断,进行任务切换 */
}
}
portENABLE_INTERRUPTS();
}
BaseType_t xTaskIncrementTick(void)详解 如图1所示,若调度器挂起 1.1全局的无符号长 uxPendedTicks 递增1.记录调度器被挂起期间的系统时钟数。不使用xTickCount。在xTaskResumeAll中恢复xTickCount。 1.2若使能了钩子函数,则回调滴答定时器的钩子函数vApplicationTickHook 2,若调度器未挂起 2.1全局的无符号长 xTickCount 递增1。 2.2判断xTickCount 值是否溢出,若溢出使用taskSWITCH_DELAYED_LISTS()切换链表pxDelayedTaskList和pxOverflowDelayedTaskList。 2.3判断下一个阻塞延时任务的唤醒时间xNextTaskUnblockTime是否过期,若过期,遍历延时任务pxDelayedTaskList链表,依次获取相应的TCB_t和唤醒时间点值xItemValue。 2.3.1若本任务唤醒时间点值xItemValue大于当前的时间点xConstTickCount,表示过期应当唤醒了,将xNextTaskUnblockTime = xItemValue;否则继续2.3.2。 2.3.2将本任务从Ready,Blocked,Suspended态之一的链表中移除本任务的列表项xStateListItem ; 2.3.3若任务也在等待事件,从事件列表(队列,信号量等)中删除它的列表项xEventListItem。 2.3.4若阻塞的任务,则使用prvAddTaskToReadyList()放入适当的就绪列表中。 2.3.5如果抢占开启,但是只有当未未阻塞任务的优先级等于或高于当前执行的任务时,才应该执行上下文切换。标记xSwitchRequired = pdTRUE,用于返回; 2.4如果抢占是打开的,并且应用程序编写器没有显式地关闭时间切片,那么与当前运行任务具有同等优先级的任务将共享处理时间(时间片)。 2.5当被解除挂起的滴答计数时(当调度器被解除锁定时).tick钩子函数不应当被调用。 3,若采用抢占式调度器,configUSE_PREEMPTION,且有触发一次任务切换的需求(xYieldPending!= pdFALSE)则使xSwitchRequired = pdTRUE; |
疑问:
- xStateListItem,xEventListItem是如何移除的uxListRemove()?
- pxDelayedTaskList和pxOverflowDelayedTaskList是如何通过taskSWITCH_DELAYED_LISTS()交换的?
- xTaskResumeAll()是如何处理恢复xTickCount的?
- xYieldPending的作用?
- xSwitchRequired = pdTRUE意义何在?
- prvAddTaskToReadyList()是如何加入就绪链表实现的?
- 这里PENDSV触发条件是什么,触发后会发生什么?
6,vTaskDelay函数实现分析
void vTaskDelay( const TickType_t xTicksToDelay )
{
BaseType_t xAlreadyYielded = pdFALSE;
/* A delay time of zero just forces a reschedule. */
if( xTicksToDelay > ( TickType_t ) 0U )
{
configASSERT( uxSchedulerSuspended == 0 );
vTaskSuspendAll();
{
traceTASK_DELAY();
/* 在调度程序被挂起时从事件列表中删除的任务将不会被放在就绪列表中或从阻塞列表中删除,直到重新启动调度程序为止。此任务不能出现在事件列表中,因为它是当前正在执行的任务。 */
prvAddCurrentTaskToDelayedList( xTicksToDelay, pdFALSE );
}
xAlreadyYielded = xTaskResumeAll();
}
else
{
mtCOVERAGE_TEST_MARKER();
}
/* 果xTaskResumeAll还没有这样做,我们可能已经让自己睡着了。 */
if( xAlreadyYielded == pdFALSE )
{
portYIELD_WITHIN_API();
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}