• 【FreeRTOS】两个Delay函数


    0 前言

    • 在我们实际开发过程中,一般都用事件开发

    • 不要使用死循环


    1 Delay函数

    1.1 两个Delay函数

    FreeRTOS中有两个Delay函数

    • vTaskDelay至少等待指定个数的Tick Interrupt才能变为就绪状态。
    • vTaskDelayUntil等待到指定的绝对时刻,才能变为就绪态。

    这2个函数原型如下:

    void vTaskDelay( const TickType_t xTicksToDelay ); /* xTicksToDelay: 等待多少给Tick */
    
    /* pxPreviousWakeTime: 上一次被唤醒的时间
     * xTimeIncrement: 要阻塞到(pxPreviousWakeTime + xTimeIncrement)
     * 单位都是Tick Count
     */
    BaseType_t xTaskDelayUntil( TickType_t * const pxPreviousWakeTime,
                                const TickType_t xTimeIncrement );
    

    下面画图说明:

    Tick中断如下:这里使用vTaskDelay(5)的话,就会等待5个Tick中断,任务再次变成就绪态
    在这里插入图片描述
    如果等到了5个Tick中断,并且查看现在有没有同级别的或者更高优先级的任务,如果没有的话,就会立刻执行这个任务。


    现在假设我们运行的任务是这样的

    while(1)
    {
    	do_sth();		//这段代码执行的时间长度每次都不相同,假设是1ms、10ms、5ms
    	vTaskDelay(5);	//延时5ms
    }
    

    每次执行do_sth();这段代码执行的时间长度每次都不相同,假设是1ms、10ms、5ms

    在这里插入图片描述
    实际的运行流程:

    • 先运行1ms的do_sth();,再允许5ms的延时,再运行10ms的do_sth();,再运行5ms的延时,再运行5ms的do_sth();

    流程如下图:
    在这里插入图片描述

    • vTaskDelay(5);这个函数运行时间大于等于5个Tick

    如果想让这个函数周期运行的话,他们的间隔都是同样一段时间,如下图:

    在这里插入图片描述
    这时候就需要使用领一个函数

    BaseType_t xTaskDelayUntil( TickType_t * const pxPreviousWakeTime, const TickType_t xTimeIncrement );
    

    现在的运行流程就是,先记录Tick的时刻preTime=T1,然后运行do_sth(),直到运行到T1+15=T2这个时刻

    运行到T2时刻,需要做什么呢?

    1. 更新preTime = T2
    2. 让任务进入Ready状态

    在这里插入图片描述
    T2和T3之间也是15个Tick,这样就能使得do_sth 周期性的运行~


    1.2 总结

    • 使用vTaskDelay(n)时,进入、退出vTaskDelay的时间间隔至少是n个Tick中断
    • 使用xTaskDelayUntil(&Pre, n)时,前后两次退出xTaskDelayUntil的时间至少是n个Tick中断
      • 退出xTaskDelayUntil时任务就进入的就绪状态,一般都能得到执行机会
      • 所以可以使用xTaskDelayUntil来让任务周期性地运行

    2 程序

    在06_create_task_use_params的基础上修改,得到11_task_delay

    2.1 函数修改

    void LCDPrintfTask(void *params)
    {
        //将传入的参数,转换成 struct TaskPrintInfo 这个结构体
        struct TaskPrintInfo *pInfo = params;
        uint32_t count = 0; //定义一个计数值
        uint8_t length;     //长度
        
        BaseType_t preTime; //long
        uint64_t t1, t2;    //时间变量
        
        LCD_Init();
    	LCD_Clear();	// 清屏
        
        preTime = xTaskGetTickCount();  // 获取时间,这个时间后面会自动更新
        
        while(1)
        {
            /* 打印信息 */
            if (g_LCDCanUse)        //g_LCDCanUse == 1 能使用LCD
            {
                g_LCDCanUse = 0;    // 在这里禁止其他任务使用LCD
                length  = LCD_PrintString(pInfo->x, pInfo->y, pInfo->name); //在(x,y)处打印name里的内容,返回打印了多少个字符
                length += LCD_PrintString(length, pInfo->y, ":");           //从返回的那个长度开始打印一个冒号
                LCD_PrintSignedVal(length, pInfo->y, count++);              //在冒号的下一个位置开始打印计数值count,并且count++
                g_LCDCanUse = 1;        //用完再恢复成1,表示可以继续使用LCD
                mdelay(count & 0x3);    //取出低两位,来添加一个随机的死循环
            }
            
            //获取系统时间
            t1 = system_get_ns();
            
            vTaskDelay(500);    //500ms
            
            //xTaskDelayUntil(&preTime, 500);        
            t2 = system_get_ns();
            
            //打印 显示
            LCD_ClearLine(pInfo->x, pInfo->y+2);
            LCD_PrintSignedVal(pInfo->x, pInfo->y+2, (t2-t1)/1e6);  //ms为单位
    
        }
    }
    

    烧录程序,OLED的第二行显示499,是固定的

    修改代码,改成vTaskDelayUntil

    void LCDPrintfTask(void *params)
    {
        //将传入的参数,转换成 struct TaskPrintInfo 这个结构体
        struct TaskPrintInfo *pInfo = params;
        uint32_t count = 0; //定义一个计数值
        uint8_t length;     //长度
        
        BaseType_t preTime; //long
        uint64_t t1, t2;    //时间变量
        
        LCD_Init();
    	LCD_Clear();	// 清屏
        
        preTime = xTaskGetTickCount();  // 获取时间,这个时间后面会自动更新
        
        while(1)
        {
            /* 打印信息 */
            if (g_LCDCanUse)        //g_LCDCanUse == 1 能使用LCD
            {
                g_LCDCanUse = 0;    // 在这里禁止其他任务使用LCD
                length  = LCD_PrintString(pInfo->x, pInfo->y, pInfo->name); //在(x,y)处打印name里的内容,返回打印了多少个字符
                length += LCD_PrintString(length, pInfo->y, ":");           //从返回的那个长度开始打印一个冒号
                LCD_PrintSignedVal(length, pInfo->y, count++);              //在冒号的下一个位置开始打印计数值count,并且count++
                g_LCDCanUse = 1;        //用完再恢复成1,表示可以继续使用LCD
                mdelay(count & 0x3);    //取出低两位,来添加一个随机的死循环
            }
            
            //获取系统时间
            t1 = system_get_ns();
            //vTaskDelay(500);    //500ms 
            vTaskDelayUntil(&preTime, 500);
            t2 = system_get_ns();
            
            //打印 显示
            LCD_ClearLine(pInfo->x, pInfo->y+2);
            LCD_PrintSignedVal(pInfo->x, pInfo->y+2, (t2-t1)/1e6);  //ms为单位
        }
    }
    

    烧写运行

    mdelay(count & 0x3); //取出低两位,来添加一个随机的死循环
    这行代码,导致程序会随机delay一阵子,那么OLED上显示的内容每次都是不一样的!

    随机显示398 397 396 399 404 473 406 405


    2.2 总结

    • vTaskDelay:函数执行完延时 **ms
    • vTaskDelayUntil:整个函数运行**ms(加上Delay)

    学习视频:【FreeRTOS入门与工程实践 --由浅入深带你学习FreeRTOS(FreeRTOS教程 基于STM32,以实际项目为导向)】 【精准空降到 01:50】 https://www.bilibili.com/video/BV1Jw411i7Fz/?p=24&share_source=copy_web&vd_source=8af85e60c2df9af1f0fd23935753a933&t=110

    学习视频传送门
    在这里插入图片描述

  • 相关阅读:
    C++ 如何把string转为int,如何把int转为string(字符串转为数字,数字转为字符串)
    SpringMVC进阶:常用注解、参数传递和请求响应以及页面跳转
    【STM32/FreeRTOS】SysTick定时器及FreeRTOS系统节拍
    容易理解的归并排序(C语言)
    【学习挑战赛】经典算法之直接选择排序
    【T+】余额表联查明细账,提示未将对象引用设置到对象的实例;参数格式错误,solutionID不能为空。
    “滑动窗口”算法专项训练
    C/C++ __builtin 超实用位运算函数总结
    虚拟数字人的商业价值
    C语言学习之路(基础篇)—— 数据类型 02
  • 原文地址:https://blog.csdn.net/weixin_63135906/article/details/140043687