当前位置: 首页 > news >正文

裸机编程的几种模式、架构与缺陷。

大多数嵌入式的初学者都是从单片机裸机编程开始的,对于初学者来说,裸机编程更加直观、简单,代码所见及所得,调试也非常方便,区别于使用操作系统需要先了解大量的操作系统基础知识,调度的基本常识,还需要注意各种资源的共享与竞争等概念,并且调试也没有那么直观等等。裸机编程在一些比较简单的项目上还是具有一定的优势的。

接下来我们来看看裸机编程的常见模式和架构。

1.主循环轮询模式

主循环轮询模式就是在主函数中使用一个永不退出的 while(1) 来承载所有的应用逻辑,如下:

int main(void) {while(1){do_a();do_b();do_c();}
}

do_a、do_b、do_c 三个函数依次执行,全部执行完毕后再次从 do_a 逻辑开始,以此不断循环。

这种模式是最简单也是最初级的模式,但其也存在很多问题。由于上述三个逻辑会依次执行,那么就会相互影响,do_b 必须要等 do_a 执行完后再执行,do_c 必须要等 do_a 和 do_b 都执行完后才执行,一旦前置逻辑中存在大量的延时,后续逻辑就无法得到及时的运行。

比如后续逻辑中存在一些交互行为,do_b 会判断一个按键的按下状态并做出响应,而此时还在 do_a 中执行延时指令,那么整体运行就会显得非常卡顿,甚至还会因为错过用户按键的时机而导致即使按下了按键,也没有执行对应的反馈。

2.中断执行模式

针对于上面的问题,很多人就会使用中断来解决。对于一些需要立即响应的操作,将其放在中断中,从而避免其被主程序中的其他逻辑所影响,此时代码可能如下所示:

//按键中断
void key_isr(void){do_b(); //按键按下的操作
}int main(void) {while(1){do_a();do_c();}
}

主循环中还是正常执行非交互式的逻辑,而对于上例中按键交互的逻辑 do_b,则放到对应的按键信号捕获中断中(如 GPIO 外部中断)。此时即使在执行主循环中的其他逻辑,由于中断会打断主循环立即运行,所以按键信号会被立刻检测到并响应。

无法及时得到响应的问题解决了,对于一些非常简单的逻辑,这种模式就足够了,但如果主循环中的逻辑有一定的周期性要求,如 do_a 需要每隔 100 毫秒执行一次, do_c 需要 50 毫秒执行一次,于是 do_a 和 do_c 下就会存在 delay(100) 和 delay(50) 的代码:

// 按键中断
void key_isr(void) {do_b();  // 按键按下的操作
}void do_a(void) {delay(100);  // 延时100ms// do_a 逻辑
}void do_c(void) {delay(50);  // 延时50ms// do_c 逻辑
}int main(void) {while (1) {do_a();do_c();}
}

此时无论 do_a 和 do_c 谁前谁后,他们的执行周期都会拉长到至少 150 毫秒!因为顺序执行的原因,你必须等待上一个逻辑执行完才能执行下一个逻辑。

这种情况下 do_a 和 do_c 任何一个逻辑的周期都无法被满足,这种模式的缺陷也就显现出来了。

3.中断+定时器+主循环的前后台架构

上例的一个最大问题就是主循环的每次执行都要完整地将所有逻辑都执行一遍,而每个逻辑中为了控制自身的周期又用了延时。各个延时就不可避免地影响到其他逻辑的执行,再由于顺序执行的逻辑,其他逻辑的执行又影响到了自身,产生恶性循环,最终没有一个逻辑是符合其自身的周期的。

既然如此,我们可以使用定时器产生一个时间标志,这个标志代表了当前系统运行的时间,主循环中的逻辑再检测这个时间,如果满足自身执行的时间,那么就执行自身逻辑,如果不满足则直接跳出,让其他逻辑执行,中断逻辑仍然不变。这种情况下前台就是中断,后台就是主循环,其代码形式如下:

// 按键中断
void key_isr(void) {do_b();  // 按键按下的操作
}// 定时器中断 1ms 进一次
unsigned int tick = 0;
void timer_isr(void) {tick++;if (tick > 10000) tick = 0;
}void do_a(void) {if (tick % 100 == 0) {// do_a 逻辑} else {return;}
}void do_c(void) {if (tick % 50 == 0) {// do c 逻辑} else {return;}
}int main(void) {while (1) {do_a();do_c();}
}

由上述代码可以看到定时器中断为 1 毫秒,每进一次中断 tick 加 1,在主循环中的 do_a 和 do_c 会首先判断 tick 的值,一旦发现与自己的运行周期相同,则执行自身逻辑,否则退出。此时理想的运行图如下:

由于去掉了每个逻辑中的延时,取而代之的是标志位的判断,其执行速度是非常快的,如上图所示 ,灰色的块表示在运行判断逻辑并且没有满足运行要求。这种情况下每个逻辑都能在其指定的周期内得到执行。

这种架构在裸机编程中可以算得上一种中高级的架构,能够满足大多数不是特别复杂的需求。当然,在上图中我们可以看到 do_a 和 do_b 一个为 100 毫秒,一个为 50 毫秒,存在公倍数情况,也就是说在某一时刻,如这里的 0 毫秒和 100 毫秒,就会出现两个逻辑同时运行的场景。实际在项目中如果要求比较严格,会对这个周期进行一个控制和计算,尽量减少各逻辑同时执行的概率,避免由于同时执行的逻辑过多且过于频繁,执行时间的总和仍然会太长,从而影响整体运行稳定性的问题。

到这里请思考一下,假如 do_a 逻辑本身的执行时间就很长,比如进行一个非常复杂的运算,或者需要读取一个 G 级别的文件,导致单一逻辑的执行时间就超过了最小周期(如例子中的 50 毫秒),那即使 50 毫秒的周期到了,由于 do_a 还没运行完,do_c 也无法得到运行,这时候时间标志已经形同虚设,甚至由于此处是取余判断,假如 do_a 运行了 51 毫秒结束,do_b 在判断的时候已经是 52 毫秒,52%50 不为零,do_b 直接无法执行,时间标志甚至产生了负面影响!

虽说将 “通过取余运算判断是否可以执行的逻辑” 修改为 “设置多个时间标志(如 50ms_flag、100ms_flag等),在中断中判断满足时间就将这些标志置位,主循环中直接对这些标志进行判断的逻辑” 可以避免由于时间后延导致的无法触发逻辑执行问题,但仍然无法解决周期被影响的本质。

怎么办?

4.前后台 + 状态机架构 

既然上面的问题是由于主循环中单个应用逻辑自身执行时间太长导致,那么我们就将其拆分,原本一个逻辑只能一次执行完,现在就拆分成多个步骤,每次执行只运行一个步骤而不是完整的逻辑,再用一个变量去记录当前执行到了哪个步骤,下次进入就执行下一个步骤。

这就是状态机编程(以 do_a 为例,其他主循环逻辑同 do_a ):

void do_a(void) {static unsigned char step = 0;if (tick % 100 == 0) {switch (step) {case 0:// 执行第一步step++;break;case 1:// 执行第二步step++;break;case 2:// 执行第三步step = 0;break;default://  未知步骤,归零重来step = 0;break;}} else {return;}
}

可以看到原本 do_a 我们将它看作一个完整不可分割的逻辑,执行完整个 do_a 才会退出,而现在我们将其拆分成了3个步骤,每执行完一个步骤就会退出 do_a 函数,直到下一次进入才会执行下一个步骤,这样一来就能有效缩短一次 do_a 执行的时间,从而大大降低其一次执行时间会超过所有逻辑中最小周期的可能性。主循环中其他应用逻辑也和 do_a 一样,利用更加细分的状态机模式来加快主循环的响应效率,进一步提高了裸机编程的稳定性和时间可控性。

状态机的加入也使得裸机编程走向了其终极形态,使其能够处理更加复杂的逻辑与应用,与此同时,其代码量和复杂度也极速上升,尤其是当你的主循环中有十几个甚至几十个任务逻辑,此时你就会面临地狱级的编程难度。

当然,即使你能够接受地狱级挑战,最终也仍然会遇到一个问题 —— 随着应用逻辑的增多,同一时间执行了大量的状态机分支步骤,这些步骤仅凭人工已经很难再进行拆分了,并且很不幸,它们执行时间的总和超过了预定的周期,最终导致了各种各样的问题。

此时恭喜你,已经走到了裸机编程的巅峰,同时也是裸机编程的尽头。是时候迈开脚步,走向操作系统编程这条路了!

相关文章:

裸机编程的几种模式、架构与缺陷。

大多数嵌入式的初学者都是从单片机裸机编程开始的,对于初学者来说,裸机编程更加直观、简单,代码所见及所得,调试也非常方便,区别于使用操作系统需要先了解大量的操作系统基础知识,调度的基本常识&#xff0…...

TSINGSEE青犀视频AI方案:数据+算力+算法,人工智能的三大基石

背景分析 随着信息技术的迅猛发展,人工智能(AI)已经逐渐渗透到我们生活的各个领域,从智能家居到自动驾驶,从医疗诊断到金融风控,AI的应用正在改变着我们的生活方式。而数据、算法和算力,正是构成…...

Linux认识与学习BASH

Linux认识与学习BASH 认识BASH这个Shellshell是什么系统的合法shell与/etc/shells功能Bash Shell的功能查询命令是否为Bash shell 的内置命令(type)命令的执行与快速编辑按钮 shell的变量功能什么是变量?变量的使用与设置:echo、变量设置规则、unset环境…...

Python JSON 序列化以及反序列化 文件读写

Python JSON 序列化以及反序列化 JSON (JavaScript Object Notation) 是一种轻量级的文本数据存储格式。JSON 数据通常存储在字符串中,即JSON字符串,其实就是一字符串,只是带有一定的格式,可以被解析。本文使用的 Python 版本为3…...

Spring MVC 返回JSON数据

1. 前置准备 1.1 导入jackson依赖 <dependency><groupId>com.fasterxml.jackson.core</groupId><artifactId>jackson-databind</artifactId><version>2.15.0</version> </dependency>1.2 添加json数据转化器 EnableWebMvc …...

前端基础——HTML傻瓜式入门(1)

该文章Github地址&#xff1a;https://github.com/AntonyCheng/html-notes 在此介绍一下作者开源的SpringBoot项目初始化模板&#xff08;Github仓库地址&#xff1a;https://github.com/AntonyCheng/spring-boot-init-template & CSDN文章地址&#xff1a;https://blog.c…...

【AI】如何创建自己的自定义ChatGPT

如何创建自己的自定义ChatGPT 目录 如何创建自己的自定义ChatGPT大型语言模型(LLM)GPT模型ChatGPTOpenAI APILlamaIndexLangChain参考推荐超级课程: Docker快速入门到精通Kubernetes入门到大师通关课本文将记录如何使用OpenAI GPT-3.5模型、LlamaIndex和LangChain创建自己的…...

电子科技大学链时代工作室招新题C语言部分---题号E

1. 题目 这道题大概的意思是说&#xff0c;一座城市中被埋了许多雷&#xff08;用一个只含0和1的字符串表示城市&#xff0c;1代表有雷&#xff0c;0代表无雷&#xff09;。 你作为一个排雷兵&#xff0c;需要花最少的钱引爆所有的雷来使城市中不再有雷&#xff08;太逆天了&a…...

K8S CNI

OCI概念 OCI&#xff0c;Open Container Initiative&#xff0c;开放容器标准&#xff0c;是一个轻量级&#xff0c;开放的治理结构&#xff08;项目&#xff09;&#xff0c;在 Linux 基金会的支持下成立&#xff0c;致力于围绕容器格式和运行时创建开放的行业标准。 OCI 项目…...

Python数据分析实验一:Python数据采集与存储

目录 一、实验目的与要求二、实验过程三、主要程序清单和运行结果1、爬取 “中国南海网” 站点上的相关信息2、爬取天气网站上的北京的历史天气信息 四、程序运行结果五、实验体会 一、实验目的与要求 1、目的&#xff1a; 理解抓取网页数据的一般处理过程&#xff1b;熟悉应用…...

丘一丘正则表达式

正则表达式(regular expression,regex,RE) 正则表达式是一种用来简洁表达一组字符串的表达式正则表达式是一种通用的字符串表达框架正则表达式是一种针对字符串表达“简洁”和“特征”思想的工具正则表达式可以用来判断某字符串的特征归属 正则表达式常用操作符 操作符说明实…...

工业物联网平台在水务环保、暖通制冷、电力能源等行业的应用

随着科技的不断发展&#xff0c;工业物联网平台作为连接物理世界与数字世界的桥梁&#xff0c;正逐渐成为推动各行业智能化转型的关键力量。在水务环保、暖通制冷、电力能源等行业&#xff0c;工业物联网平台的应用尤为广泛&#xff0c;对于提升运营效率、降低能耗、优化管理等…...

【研发日记】Matlab/Simulink技能解锁(二)——在Matlab Function编辑窗口Debug

文章目录 前言 行断点 条件断点 按行步进 Watch Value 分析和应用 总结 前言 见《【研发日记】Matlab/Simulink技能解锁(一)——在Simulink编辑窗口Debug》 行断点 当Matlab Function出现异常时&#xff0c;如果能确定大致的代码段&#xff0c;就可以在相应的行上设置一…...

从键盘输入两个数,求它们的和并输出 从键盘输入三个数到a,b,c中,按公式值输出

别急别急&#xff0c;先看完 (从初学者出发&#xff09; 从键盘输入两个数&#xff0c;求它们的和并输出 作者 陈春晖 单位 浙江大学 本题目要求读入2个整数A和B&#xff0c;然后输出它们的和。 输入格式: 在一行中给出一个被加数 在另一行中给出一个加数 输出格式: 在…...

密码解密 C卷(100%用例)(JavaPythonC++Node.jsC语言)

给定一段“密文“字符串s,其中字符都是经过"密码本”映射的,现需要将"密文"解密并且输出 映射的规则(a-i)分别用(1-9)表示;(j-z")分别用(10-"26”)表示 约束:映射始终唯一 输入描述: “密文”字符串 输出描述: 明文字符串 补充说明: 翻译后的文本…...

因为manifest.json文件引起的 android-chrome-192x192.png 404 (Not Found)

H5项目打包之后&#xff0c;总是有这个报错&#xff0c;有时候还有别的icon也找不见 一通调查之后&#xff0c;发现是因为引入了一个vue插件 这个插件引入之后&#xff0c;webpack打包的时候就会自动在dist文件夹中产生一个manifest.json文件这个文件里面主要就是一些icon地址的…...

『 Linux 』进程替换( Process replacement ) 及 简单Shell的实现(万字)

文章目录 &#x1f984; 进程替换&#x1f9a9; execl()函数&#x1f9a9; execlp()函数&#x1f9a9; execle()函数&#x1f9a9; execv()函数&#x1f9a9; execvp()函数&#x1f9a9; execvpe()函数&#x1f9a9; execve()函数 &#x1f984; 简单Shell命令行解释器的实现&a…...

【Linux】从零开始认识进程 — 前篇

我从来不相信什么懒洋洋的自由。我向往的自由是通过勤奋和努力实现的更广阔的人生。。——山本耀司 从零开始认识进程 1 认识冯诺依曼体系2 操作系统3 进程3.1 什么是进程&#xff1f;&#xff1f;&#xff1f;3.2 进程管理PCB 3.3 Linux中的进程深入理解 3.4 进程创建总结 送给…...

公众号留言功能恢复了,你的开通了吗?

了解公众号的人都知道&#xff0c;腾讯在2018年3月宣布暂停新注册公众号的留言功能&#xff0c;这之后注册的公众号都不具备留言功能。 这成了很多号主运营人的一块心病&#xff0c;也包括我。 没有留言&#xff0c;就好似一个人玩单机游戏&#xff0c;无法与读者互动&#xff…...

C语言葵花宝典之——文件操作

前言&#xff1a; 在之前的学习中&#xff0c;我们所写的C语言程序总是在运行结束之后&#xff0c;就会自动销毁&#xff0c;那如果我们想将一个结果进行长期存储应该如何操作呢&#xff1f;这时候就需要我们用文件来操作。 目录 1、什么是文件&#xff1f; 1.1 程序文件 1.2…...

网络编程(Modbus进阶)

思维导图 Modbus RTU&#xff08;先学一点理论&#xff09; 概念 Modbus RTU 是工业自动化领域 最广泛应用的串行通信协议&#xff0c;由 Modicon 公司&#xff08;现施耐德电气&#xff09;于 1979 年推出。它以 高效率、强健性、易实现的特点成为工业控制系统的通信标准。 包…...

web vue 项目 Docker化部署

Web 项目 Docker 化部署详细教程 目录 Web 项目 Docker 化部署概述Dockerfile 详解 构建阶段生产阶段 构建和运行 Docker 镜像 1. Web 项目 Docker 化部署概述 Docker 化部署的主要步骤分为以下几个阶段&#xff1a; 构建阶段&#xff08;Build Stage&#xff09;&#xff1a…...

深入浅出Asp.Net Core MVC应用开发系列-AspNetCore中的日志记录

ASP.NET Core 是一个跨平台的开源框架&#xff0c;用于在 Windows、macOS 或 Linux 上生成基于云的新式 Web 应用。 ASP.NET Core 中的日志记录 .NET 通过 ILogger API 支持高性能结构化日志记录&#xff0c;以帮助监视应用程序行为和诊断问题。 可以通过配置不同的记录提供程…...

pam_env.so模块配置解析

在PAM&#xff08;Pluggable Authentication Modules&#xff09;配置中&#xff0c; /etc/pam.d/su 文件相关配置含义如下&#xff1a; 配置解析 auth required pam_env.so1. 字段分解 字段值说明模块类型auth认证类模块&#xff0c;负责验证用户身份&am…...

【大模型RAG】Docker 一键部署 Milvus 完整攻略

本文概要 Milvus 2.5 Stand-alone 版可通过 Docker 在几分钟内完成安装&#xff1b;只需暴露 19530&#xff08;gRPC&#xff09;与 9091&#xff08;HTTP/WebUI&#xff09;两个端口&#xff0c;即可让本地电脑通过 PyMilvus 或浏览器访问远程 Linux 服务器上的 Milvus。下面…...

MMaDA: Multimodal Large Diffusion Language Models

CODE &#xff1a; https://github.com/Gen-Verse/MMaDA Abstract 我们介绍了一种新型的多模态扩散基础模型MMaDA&#xff0c;它被设计用于在文本推理、多模态理解和文本到图像生成等不同领域实现卓越的性能。该方法的特点是三个关键创新:(i) MMaDA采用统一的扩散架构&#xf…...

python如何将word的doc另存为docx

将 DOCX 文件另存为 DOCX 格式&#xff08;Python 实现&#xff09; 在 Python 中&#xff0c;你可以使用 python-docx 库来操作 Word 文档。不过需要注意的是&#xff0c;.doc 是旧的 Word 格式&#xff0c;而 .docx 是新的基于 XML 的格式。python-docx 只能处理 .docx 格式…...

根据万维钢·精英日课6的内容,使用AI(2025)可以参考以下方法:

根据万维钢精英日课6的内容&#xff0c;使用AI&#xff08;2025&#xff09;可以参考以下方法&#xff1a; 四个洞见 模型已经比人聪明&#xff1a;以ChatGPT o3为代表的AI非常强大&#xff0c;能运用高级理论解释道理、引用最新学术论文&#xff0c;生成对顶尖科学家都有用的…...

网络编程(UDP编程)

思维导图 UDP基础编程&#xff08;单播&#xff09; 1.流程图 服务器&#xff1a;短信的接收方 创建套接字 (socket)-----------------------------------------》有手机指定网络信息-----------------------------------------------》有号码绑定套接字 (bind)--------------…...

selenium学习实战【Python爬虫】

selenium学习实战【Python爬虫】 文章目录 selenium学习实战【Python爬虫】一、声明二、学习目标三、安装依赖3.1 安装selenium库3.2 安装浏览器驱动3.2.1 查看Edge版本3.2.2 驱动安装 四、代码讲解4.1 配置浏览器4.2 加载更多4.3 寻找内容4.4 完整代码 五、报告文件爬取5.1 提…...