FreeRTOS - 单片机程序设计模式

news2024/10/18 11:28:50

在学习FreeRTOS过程中,结合韦东山-FreeRTOS手册和视频、野火-FreeRTOS内核实现与应用开发、及网上查找的其他资源,整理了该篇文章。如有内容理解不正确之处,欢迎大家指出,共同进步。

单片机程序设计模式(第2章)

裸机程序设计模式
裸机程序设计模式:轮询、前后台、定时器驱动、基于状态机

场景:假设一位职场妈妈需要同时解决2个问题:给小孩喂饭、回复工作信息

轮询模式
轮询系统即是在裸机编程的时候,先初始化好相关的硬件,然后让主程序在一个死循环里面不断循环,顺序地做各种事情。

轮询系统是一种非常简单地软件结构,通常只适用于那些只需要顺序执行代码且不需要外部事件来驱动的就能完成的事情。

在main函数中是一个while循环,里面依次调用2个函数,这两个函数相互之间有影响:如果“喂一口饭”太花时间,就会导致迟迟无法“回一个信息”;如果“回一个信息”太花时间,就会导致迟迟无法“喂下一口饭”。

// 经典单片机程序: 轮询
void main()
{
    /* 硬件相关初始化*/
    HardWareInit();
    while (1)
    {
        喂一口饭();
        回一个信息();
    }
}
前后台(使用中断程序)
相比轮询系统,前后台系统是在轮询系统的基础上加入了** 中断** 。外部事件的响应在中断里面完成,事件的处理还是回到轮询系统中完成。

中断:称为前台,main函数里面的无限循环:后台。

在顺序执行后台程序的时候,如果有中断来临,那么中断会打断后台程序的正常执行流,转而去执行中断服务程序,在中断程序里面标记事件,如果事件要处理的事情很简短,则可在中断服务程序里面处理,如果事件要处理的事情比较多,则返回到后台程序里面处理。

假设收到同事发来的信息时,电脑会发出“滴”的一声,这时候妈妈才需要去回复信息。

// 前后台程序
void main()
{
    while (1)
    {
        // 后台程序
        喂一口饭();
    }
}

// 前台程序
void 滴_中断()
{
    回一个信息();
}

main函数里while循环里的代码是后台程序,平时都是while循环在运行;

当同事发来信息,电脑发出“滴”的一声,触发了中断。妈妈暂停喂饭,去执行“滴_中断”给同事回复信息;

在这个场景里,给同事回复信息非常及时:即使正在喂饭也会暂停下来去回复信息。“喂一口饭”无法影响到“回一个信息”。但是,如果“回一个信息”太花时间,就会导致 “喂一口饭”迟迟无法执行。

定时器驱动
定时器驱动模式,是前后台模式的一种,可以按照不用的频率执行各种函数。比如需要每2分钟给小孩喂一口饭,需要每5分钟给同事回复信息。那么就可以启动一个定时器,让它每1分钟产生一次中断,让中断函数在合适的时间调用对应函数。
// 前后台程序: 定时器驱动
void main()
{
    while (1)
    {
        // 后台程序
    }
}

// 前台程序: 每1分钟触发一次中断
void 定时器_中断()
{
    static int cnt = 0;
    cnt++;
    if (cnt % 2 == 0)
    {
        喂一口饭();
    }
    else if (cnt % 5 == 0)
    {
        回一个信息();
    }
}

main函数中的while循环是空的,程序的运行靠定时器中断来驱使。

定时器中断每1分钟发生一次,在中断函数里让cnt变量累加(代码第14行)。

第15行:进行求模运算,如果对2取模为0,就“喂一口饭”。这相当于每发生2次中断就“喂一口饭”。

第19行:进行求模运算,如果对5取模为0,就“回一个信息”。这相当于每发生5次中断就“回一个信息”。

这种模式适合调用周期性的函数,并且每一个函数执行的时间不能超过一个定时器周期。如果“喂一口饭”很花时间,比如长达10分钟,那么就会耽误“回一个信息”;反过来也是一样的,如果“回一个信息”很花时间也会影响到“喂一口饭”;这种场景下程序遭遇到了轮询模式的缺点:函数相互之间有影响。

基于状态机
当“喂一口饭”、“回一个信息”都需要花很长的时间,无论使用前面的哪种设计模式,都会退化到轮询模式的缺点:函数相互之间有影响。可以使用状态机来解决这个缺点,
// 状态机
void main()
{
    while (1)
    {
        喂一口饭();
        回一个信息();
    }
}

在main函数里,还是使用轮询模式依次调用2个函数。

关键在于这2个函数的内部实现:使用状态机,每次只执行一个状态的代码,减少每次执行的时间,代码如下:

void 喂一口饭(void)
{
    static int state = 0;
    switch (state)
    {
        case 0:
        {
            /* 舀饭 */
            /* 进入下一个状态 */
            state++;
            break;
        }
        case 1:
        {
            /* 喂饭 */
            /* 进入下一个状态 */
            state++;
            break;
        }
        case 2:
        {
            /* 舀菜 */
            /* 进入下一个状态 */
            state++;
            break;
        }
        case 3:
        {
            /* 喂菜 */
            /* 恢复到初始状态 */
            state = 0;
            break;
        }
    }
}

void 回一个信息(void)
{
    static int state = 0;

    switch (state)
    {
        case 0:
        {
            /* 查看信息 */
            /* 进入下一个状态 */
            state++;
            break;
        }
        case 1:
        {
            /* 打字 */
            /* 进入下一个状态 */
            state++;
            break;
        }
        case 2:
        {
            /* 发送 */
            /* 恢复到初始状态 */
            state = 0;
            break;
        }
    }
}

使用状态机模式,可以解决裸机程序的难题:假设有A、B两个都很耗时的函数,怎样降低它们相互之间的影响。但是很多场景里,函数A、B并不容易拆分为多个状态,并且这些状态执行的时间并不好控制。

多任务系统
相比于前后台系统, 多任务系统的事件响应也是在中断中完成的,但是事件的处理是在任务中完成的。

在多任务系统中,任务和中断一样,也具有优先级,优先级高的任务会被优先执行。当一个紧急的事件在中断中被标记后,如果事件对应的任务的优先级足够高,就会立马得到响应。

相比于前后台系统,多任务系统的实时性又被提高了。

在多任务系统中,根据程序的功能,将这个程序分割成一个个独立的,无限循环且不能返回的小程序,这个小程序:称为任务。每个任务都是独立的,互补干扰的,且具备自身的优先级,它由操作系统调度管理。

多任务模式
对于裸机程序,无论使用哪种模式进行精心的设计,在最差的情况下都无法解决这个问题:假设有A、B两个都很耗时的函数,无法降低它们相互之间的影响。使用状态机模式时,如果函数拆分得不好,也会导致这个问题。 本质原因是:函数是轮流执行的。假设“喂一口饭”需要t1~t5这5段时间,“回一个信息需要”ta~te这5段时间,轮流执行时:先执行完t1~t5,再执行ta~te,如下图所示:

对于职场妈妈,她怎么解决这个问题呢?她是一个眼明手快的人,可以一心多用,她这样做:

左手拿勺子,给小孩喂饭

右手敲键盘,回复同事

两不耽误,小孩“以为”妈妈在专心喂饭,同事“以为”她在专心聊天

但是脑子只有一个啊,虽然说“一心多用”,但是谁能同时思考两件事?

只是她反应快,上一秒钟在考虑夹哪个菜给小孩,下一秒钟考虑给同事回复什么信息

本质是:交叉执行,t1t5和tate交叉执行,如下图所示:

// RTOS程序
喂饭任务()
{
    while (1)
    {
        喂一口饭();
    }
}

回信息任务()
{
    while (1)
    {
        回一个信息();
    }
}

void main()
{
    // 创建2个任务
    create_task(喂饭任务);
    create_task(回信息任务);

    // 启动调度器
    start_scheduler();
}

多任务系统会依次给这些任务分配时间:你执行一会,我执行一会,如此循环。只要切换的间隔足够短,用户会“感觉这些任务在同时运行”。如下图所示:

互斥操作
多任务系统中,多个任务可能会“同时”访问某些资源,需要增加保护措施以防止混乱。比如任务A、B都要使用串口,能否使用一个全局变量让它们独占地、互斥地使用串口?示例代码如下:
// RTOS程序
int g_canuse = 1;

void uart_print(char *str)
{
    if( g_canuse ){
        g_canuse = 0;  ②
        printf(str);   ③
        g_canuse = 1;}
}

task_A()
{
    while (1)
    {
        uart_print("0123456789\n");
    }
}

task_B()
{
    while (1)
    {
        uart_print("abcdefghij");
    }
}

void main()
{
    // 创建2个任务
    create_task(task_A);
    create_task(task_B);
    // 启动调度器
    start_scheduler();
}

基于多任务系统编写程序时,访问公用的资源的时候要考虑“互斥操作”。任何一种多任务系统都会提供相应的函数。

同步操作
如果任务之间有依赖关系,比如任务A执行了某个操作之后,需要任务B进行后续的处理。如果代码如下编写的话,任务B大部分时间做的都是无用功。
// RTOS程序
int flag = 0;

void task_A()
{
    while (1)
    {
        // 做某些复杂的事情
        // 完成后把flag设置为1
        flag = 1;
    }
}

void task_B()
{
    while (1)
    {
        if (flag)
        {
            // 做后续的操作
        }
    }
}

void main()
{
    // 创建2个任务
    create_task(task_A);
    create_task(task_B);
    // 启动调度器
    start_scheduler();
}

上述代码中,在任务A没有设置flag为1之前,任务B的代码都只是去判断flag。而任务A、B的函数是依次轮流运行的,假设系统运行了100秒,其中任务A总共运行了50秒,任务B总共运行了50秒,任务A在努力处理复杂的运算,任务B仅仅是浪费CPU资源。

如果可以让任务B阻塞,即让任务B不参与调度,那么任务A就可以独占CPU资源加快处理复杂的事情。当任务A处理完事情后,再唤醒任务B。示例代码如下:

// RTOS程序
void task_A()
{
    while (1)
    {
        // 做某些复杂的事情
        // 释放信号量,会唤醒任务B;
    }
}

void task_B()
{
    while (1)
    {
        // 等待信号量, 会让任务B阻塞
        // 做后续的操作
    }
}

void main()
{
    // 创建2个任务
    create_task(task_A);
    create_task(task_B);
    // 启动调度器
    start_scheduler();
}
  • 第15行:任务B运行时,等待信号量,不成功时就会阻塞,不在参与任务调度。
  • 第7行:任务A处理完复杂的事情后,释放信号量会唤醒任务B。
  • 第16行:任务B被唤醒后,从这里继续运行。

在这个过程中,任务A处理复杂事情的时候可以独占CPU资源,加快处理速度。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2217735.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

10月17日

输入一个数组,循环输出数组的元素,以及各个元素的长度 arr("apple" "banana" "date")# 初始化索引 index0# 获取数组长度 arr_len${#arr[]}# 使用 while 循环遍历数组 while [ $index -lt $arr_len ]; doelement${arr[$in…

“人工智能+中职”:VR虚拟仿真实训室的发展前景

随着人工智能技术的飞速发展,中职教育也迎来了新的发展机遇。特别是虚拟现实(VR)技术在中职教育领域的应用,结合人工智能的加持,为中职教育提供了全新的教学模式和实训体验。其中,VR虚拟仿真实训室的发展前…

不使用U盘重装windows系统

优点:可以不使用U盘,重装速度快,可以保留系统的激活状态! 然后自己设置就可以重装系统了~~~

【重学 MySQL】七十、揭秘数据库对象,打造高效数据管理之旅

【重学 MySQL】七十、揭秘数据库对象,打造高效数据管理之旅 数据库(Database)数据表(Table)视图(View)存储过程(Stored Procedure)触发器(Trigger&#xff09…

初识git · 有关模型

目录 前言: 有关开发模型 前言: 其实文章更新到这里的时候,我们已经学习了可以满足我们日常生活中的基本需求的指令了,但是为什么要更新本篇文章呢?是因为实际生活中我们对于开发工作,运维工作&#xff…

每日OJ题_牛客_非对称之美_最长非回文字符串_C++_Java

目录 牛客_非对称之美_最长非回文字符串 题目解析 C代码 Java代码 牛客_非对称之美_最长非回文字符串 非对称之美 (nowcoder.com) 题目解析 找到规律就是最长非回文字符串(判断是否全同->0,否则是n-1(回文减去1)或n&…

架构设计笔记-17-通信系统架构设计理论及实践

目录 知识要点 案例分析 1.数据中心架构的技术 论文 1.论网络安全体系设计 知识要点 开放系统的存储方式主要有: 1. 直连式存储(Direct-Attached Storage,DAS):在服务器上外挂一组大容量硬盘,存储设…

Maven--简略

简介 Apache旗下的一款开源项目,用来进行项目构建,帮助开发者管理项目中的jar及jar包之间的依赖,还拥有项目编译、测试、打包的功能。 管理方式 统一建立一个jar仓库,把jar上传至统一的仓库,使用时,配置…

生产工单系统的功能是什么?有哪些应用实践?

前段时间我们公司忙得不可开交:订单像雪花一样飞来,可生产现场却一片混乱。客户催单的电话不断,大家都急得像热锅上的蚂蚁。我也整天为了协调各个环节而焦头烂额。有一天路过生产车间,看到大家手忙脚乱地找单子、问进度&#xff0…

“vue : 无法加载文件 D:\nodejs\node_global\vue.ps1,因为在此系统上禁止运行脚本”的解决方法

用VS Code来直接创建vue项目时,出现了以下错误,导致创建失败: 于是按照错误提示去查看了下出错原因:是因为PowerShell的执行政策阻止了该操作。用 Get-ExecutionPolicy 查看发现执行策略为受限状态: 解决方法如下&am…

PDF编辑功能是灰色的,什么原因?

PDF文件打开之后,发现编辑功能都是灰色的,无法使用,无法编辑PDF文件,遇到这种情况,因为PDF文件设置了限制编辑导致的。一般情况下,我们只需要输入PDF密码,将限制编辑取消就可以正常编辑文件了&a…

5.计算机网络_抓包工具wireshark

安装 Linux中安装wireshark: sudo apt-get install wireshark Linux中执行wireshark: sudo wireshark 使用 注意:只有与外网交互的数据才可以被wireshark抓到,本机回环的数据不会被抓到 实验内容: 使用nc命令…

技术速递|推出适用于 .NET 的官方 OpenAI 库的稳定版本

作者:.NET 团队 排版:Alan Wang 早在 6 月份,我们就推出了适用于 .NET 的 OpenAI 库的第一个测试版,使开发人员能够将高级 AI 模型集成到他们的应用程序中。今天,我们很高兴地宣布,适用于 .NET 的官方 Open…

请问:ESModule 与 CommonJS 的异同点是什么?

前言 本篇文章不会介绍模块的详细用法,因为核心是重新认识和理解模块的本质内容是什么,直奔主题,下面先给出最后结论,接下来在逐个进行分析。 ECMAScript Module 和 CommonJS 的相同点: 都拥有自己的缓存机制&#…

FreeRTOS - 软件定时器

在学习FreeRTOS过程中,结合韦东山-FreeRTOS手册和视频、野火-FreeRTOS内核实现与应用开发、及网上查找的其他资源,整理了该篇文章。如有内容理解不正确之处,欢迎大家指出,共同进步。 1. 软件定时器 软件定时器也可以完成两类事情…

安卓流式布局实现记录

效果图&#xff1a; 1、导入第三方控件 implementation com.google.android:flexbox:1.1.0 2、布局中使用 <com.google.android.flexbox.FlexboxLayoutandroid:id"id/baggageFl"android:layout_width"match_parent"android:layout_height"wrap_co…

在Linux操作系统上安装NVM教程——CentOS 8/VMware 17版本

目录 一、查看网络配置 二、配置阿里云镜像 三、下载NVM 四、给虚拟机共享本机文件&#xff08;补充&#xff09; 一、查看网络配置是否能上网 1.查看文件&#xff1a;cat /etc/sysconfig/network-scripts/ifcfg-ens160&#xff08;注意&#xff1a;ONBOOT"yes"…

Kibana可视化Dashboard如何基于字段是否包含某关键词进行过滤

kinana是一个功能强大、可对Elasticsearch数据进行可视化的开源工具。 我们在dashboard创建可视化时&#xff0c;有时需要将某个index里数据的某个字段根据是否包含某些特定关键词进行过滤&#xff0c;这个时候就可以用到lens里的filter功能很方便地进行操作。 如上图所示&…

汽车与航空领域的功能安全对比:ISO 26262-6 与 DO-178C 的差异浅析

ISO 26262-6 和 DO-178C &#xff08;航空系统与设备认证中的软件考量&#xff09;。是汽车和航空领域分别广泛应用的软件安全标准。它们的共同目标是确保系统软件可靠性&#xff0c;减少系统软件故障对生命安全的威胁&#xff0c;但在具体的软件安全方案和规范实施上存在明显的…

python的两个路径

xxx/python.exe&#xff08;解释器位置&#xff09; sdsd/xx/xx.py&#xff08;文件位置&#xff09; 在命令行中运行python的时候&#xff0c;命令行所在位置是os.getcwd()&#xff0c;bash是操作系统相关组件 假如脚本中执行fopen(a.txt)&#xff0c;这里的相对路径a.txt也…