FreeRTOS实时操作系统(七)时间片调度及RTOS的滴答定时器

系列文章

FreeRTOS实时操作系统(一)RTOS的基本概念

FreeRTOS实时操作系统(二)任务创建与任务删除(HAL库)

FreeRTOS实时操作系统(三)任务挂起与恢复

FreeRTOS实时操作系统(四)中断任务管理

FreeRTOS实时操作系统(五)进入临界区、任务调度器挂起与恢复

FreeRTOS实时操作系统(六)列表与列表项

FreeRTOS实时操作系统(七)时间片调度及RTOS的滴答定时器

FreeRTOS实时操作系统(八)任务状态查询及时间统计函数

FreeRTOS实时操作系统(九)时间延时函数及消息队列

FreeRTOS实时操作系统(十)信号量

FreeRTOS实时操作系统(十一)队列集

FreeRTOS实时操作系统(十二)事件标志组

FreeRTOS实时操作系统(十三)任务通知

FreeRTOS实时操作系统(十四)软件定时器

FreeRTOS实时操作系统(十五)Tickless低功耗模式

FreeRTOS实时操作系统(十六)内存管理



前言

在学习正点原子的时间片调度的教程中,突然要改变滴答定时器的中断频率,而我之前对这方面没有一点点了解,所以需要详细补充一下这个知识点。


时间片调度概念

在第一片文章里面提到过时间片调度的概念,这里重复一下:
在这里插入图片描述

同等优先级任务轮流地享有相同的 CPU 时间(可设置), 叫时间片,在FreeRTOS中,一个时间片就等于SysTick 中断周期。

首先每个任务只执行一个时间片,不管任务里面的while循环运行了几次,时间到达之后就要切换到下一个任务。

时间片的大小取决于滴答定时器的中断频率。

像Task3这样被阻塞的任务(系统延时或等待信号等),将直接切换到下一个任务,时间片没执行到也不管,剩下的时间片直接丢掉了。

滴答定时器

像上面的就涉及到了滴答定时器的中断设置,影响了时间片的长度,之前学习HAL库或者蓝桥杯等等,最多是对1ms的滴答定时器利用,从没想过更改,如下图:
在这里插入图片描述
滴答定时器有四个寄存器:

CTRLSysTick控制及状态寄存器
LOADSysTick重装载数值寄存器
VALSysTick当前数值寄存器
CALIBSysTick校准数值寄存器

未带RTOS的HAL库

参考手册中说:RCC通过AHB时钟(HCLK)8分频后作为Cortex系统定时器(SysTick)的外部时钟。通过对SysTick控制与状态寄存器的设置,可选择上述时钟或Cortex(HCLK)时钟作为SysTick时钟。

在这里插入图片描述
也就是是说可以选择AHB时钟8分频或HCLK(内核)时钟。

网上介绍说:HAL库是无法配置这个1分频和8分频,生成的都是一样的,由下图所示,默认选择的是HCLK时钟。
至于解决方案:stm32cubemx配置systick滴答定时器有bug
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
我这里虽然灰了,但是我看了看条件,确实定义了__Vendor_SysTickConfig,同时它的值也是0,这里可能是编译器有问题吧。
在这里插入图片描述

带RTOS的HAL库

在生成的RTOS的HAL库代码中,与标准库有一些区别,在这里重新定义了这个函数。

在这里插入图片描述

HAL_StatusTypeDef HAL_InitTick(uint32_t TickPriority)
{
  RCC_ClkInitTypeDef    clkconfig;
  uint32_t              uwTimclock = 0;
  uint32_t              uwPrescalerValue = 0;
  uint32_t              pFLatency;
  /*Configure the TIM1 IRQ priority */
  HAL_NVIC_SetPriority(TIM1_UP_IRQn, TickPriority ,0);

  /* Enable the TIM1 global Interrupt */
  HAL_NVIC_EnableIRQ(TIM1_UP_IRQn);

  /* Enable TIM1 clock */
  __HAL_RCC_TIM1_CLK_ENABLE();

  /* Get clock configuration */
  HAL_RCC_GetClockConfig(&clkconfig, &pFLatency);

  /* Compute TIM1 clock */
  uwTimclock = HAL_RCC_GetPCLK2Freq();
  /* Compute the prescaler value to have TIM1 counter clock equal to 1MHz */
  uwPrescalerValue = (uint32_t) ((uwTimclock / 1000000U) - 1U);

  /* Initialize TIM1 */
  htim1.Instance = TIM1;

  /* Initialize TIMx peripheral as follow:
  + Period = [(TIM1CLK/1000) - 1]. to have a (1/1000) s time base.
  + Prescaler = (uwTimclock/1000000 - 1) to have a 1MHz counter clock.
  + ClockDivision = 0
  + Counter direction = Up
  */
  htim1.Init.Period = (1000000U / 1000U) - 1U;
  htim1.Init.Prescaler = uwPrescalerValue;
  htim1.Init.ClockDivision = 0;
  htim1.Init.CounterMode = TIM_COUNTERMODE_UP;

  if(HAL_TIM_Base_Init(&htim1) == HAL_OK)
  {
    /* Start the TIM time Base generation in interrupt mode */
    return HAL_TIM_Base_Start_IT(&htim1);
  }

  /* Return function status */
  return HAL_ERROR;
}

这是因为在HAL库中,我把TIM1定时器作为基础时钟源,TIM1就不能在作为其他用途。在STM32CubeMX中不能再对TIM1做任何设置。在NVIC中,TIM1的中断被自动启用,可以修改TIM1的中断优先级,但是不能关闭TIM1的中断。同时,SysTick定时器的中断也还是被自动启用的,且不能关闭,
在这里插入图片描述

在这里插入图片描述
所以在这里,我用TIM1替代了滴答定时器,完全代替了SysTick的功能。

原因是:在使用FreeRTOS时,必须为HAL设置一个非SysTick定时器作为HAL的基础时钟,SysTick将自动作为FreeRTOS的基础时钟。也是就强制滴答定时器作为FreeRTOS的心跳,关系到任务调度和时间片,但是我们之前了解过HAL_Delay,原来其内部实现靠的是滴答定时器,现在自然变成你设置的其他定时器提供了。为啥HAL和FreeRTOS不都使用一个SysTick,是因为内部会发生冲突
参考:FreeRTOS的基础时钟
参考:FreeRTOS的Systick和HAL时基

SysTick定时器:
文件port.c中的函数xPortStartScheduler()和vPortSetupTimerInterrupt()。函数xPortStartScheduler()中设置SysTick和PendSV中断的中断优先级,函数vPortSetupTimerInterrupt()设置SysTick的定时周期,
在这里插入图片描述
portNVIC_SYSTICK_CTRL_REG、portNVIC_SYSTICK_LOAD_REG等宏就是相关寄存器。更改这些能改变滴答定时器的时间。

综合上面这些程序代码,就可以分析出来,改变上图的宏定义就可以实现对时间片大小的调整。

时间片调度实验

设计目标:将设计三个任务:start_task、task1、task2,其中task1和task2优先级相同均为2。为了使现象明显,将滴答定时器的中断频率设置为50ms中断一次,即一个时间片50ms。

将宏configUSE_TIME_SLICING 和 configUSE_PREEMPTION 置1。
抢占式调度器宏:
在这里插入图片描述
时间片调度宏:
在这里插入图片描述

改变时间片大小

/* Configure SysTick to interrupt at the requested rate. */
portNVIC_SYSTICK_LOAD_REG = ( configSYSTICK_CLOCK_HZ / configTICK_RATE_HZ ) - 1UL;
portNVIC_SYSTICK_CTRL_REG = ( portNVIC_SYSTICK_CLK_BIT | portNVIC_SYSTICK_INT_BIT | portNVIC_SYSTICK_ENABLE_BIT );

现在详细介绍一下这个portNVIC_SYSTICK_LOAD_REG :

configSYSTICK_CLOCK_HZ :
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
一步一步追踪:configSYSTICK_CLOCK_HZ=16M;

configTICK_RATE_HZ=1000;
在这里插入图片描述
所以计算可得:portNVIC_SYSTICK_LOAD_REG=16k-1;

在16M的时钟下,每16k次计数就中断一次,所以滴答定时器的中断时间就是1ms。
所以一般我们改变这个configTICK_RATE_HZ值就可以改变滴答定时器的时间。

要改成50ms的滴答定时器,就把这个configTICK_RATE_HZ值改为20即可。

之后就可以进行时间片调度的测试:

TaskHandle_t    task1_handler;

#define TASK1_PRIO         2
#define TASK1_STACK_SIZE   128
TaskHandle_t    task1_handler;
void task1( void * pvParameters );

/* TASK2 任务 配置
 * 包括: 任务句柄 任务优先级 堆栈大小 创建任务
 */
#define TASK2_PRIO         2
#define TASK2_STACK_SIZE   128
TaskHandle_t    task2_handler;
void task2( void * pvParameters );

void vTaskCode( void * pvParameters )
 {	 
    taskENTER_CRITICAL();               /* 进入临界区 */
    xTaskCreate((TaskFunction_t         )   task1,
                (char *                 )   "task1",
                (configSTACK_DEPTH_TYPE )   TASK1_STACK_SIZE,
                (void *                 )   NULL,
                (UBaseType_t            )   TASK1_PRIO,
                (TaskHandle_t *         )   &task1_handler );
                
    xTaskCreate((TaskFunction_t         )   task2,
                (char *                 )   "task2",
                (configSTACK_DEPTH_TYPE )   TASK2_STACK_SIZE,
                (void *                 )   NULL,
                (UBaseType_t            )   TASK2_PRIO,
                (TaskHandle_t *         )   &task2_handler );
    vTaskDelete(NULL);
    taskEXIT_CRITICAL();                /* 退出临界区 */
 }

 // Function that creates a task.
 void vOtherFunction( void )
 {
	xTaskCreate( vTaskCode, "tak1", 128, NULL, 1, &task1_handler );
	vTaskStartScheduler();
 }
 
void task1( void * pvParameters )
{
    uint32_t task1_num = 0;
    while(1)
    {
        taskENTER_CRITICAL();               /* 进入临界区 */
        printf("task1运行次数:%d\r\n",++task1_num);
        taskEXIT_CRITICAL();                /* 退出临界区 */
        HAL_Delay(10);
    }
}

void task2( void * pvParameters )
{
    uint32_t task2_num = 0;
    while(1)
    {
        taskENTER_CRITICAL();               /* 进入临界区 */
        printf("task2运行次数:%d\r\n",++task2_num);
        taskEXIT_CRITICAL();                /* 退出临界区 */
        HAL_Delay(10);
    }
}

设置两个串口发送任务的优先级一样,之后就可以测试结果:
在这里插入图片描述
可以看到最后在50ms的时间片内,每个任务的while循环大概执行5次后进行了任务的切换。

在上面代码中,我们在串口发送函数上下加入了临界区代码保护,主要是防止数据发送了一半,切换了任务,数据没发完。

如果上面代码的两个任务优先级不同,就会出现一直执行优先级高的任务的现象,因为它不会出现阻塞了,就一直是就绪态。

这里能使用HAL_Delay()函数,但不能使用vTaskDelay(),都相当于阻塞任务,会出现时间片还没用完就进行了任务切换,而且因为滴答定时器的时间变化了,vTaskDelay的单位就不再是1ms了,是50ms单位了
在这里插入图片描述

  • 6
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值