如何判断一段程序是否是裸机程序?

news2024/11/26 20:22:18

在嵌入式MCU领域,一般将不移植操作系统直接烧录运行的程序称为裸机程序。

一般来说,非易失性存储,时钟,图形显示,网络通讯,用户I/O设备…都需要硬件依赖。

基于硬件基础,内存管理、文件系统、多线程调度、UDP、TCP…都需要硬件支持的基础上平台依赖(平台库支持)。

甚至一些数学库,依据底层是否提供硬件支持,其实现效率与精度都可能天差地别。

因此,即使是单片机裸机的程序,因为硬件和平台的不同,大多都不能直接移植。

严格来说,C语言标准库更应该叫“推荐支持的一些函数”,至于最终支不支持,还得看平台实现。底层中没有标准,别说库函数了,就算是写进C标准的一些语法规范,在很多底层环境中也未必能够支持。如果你想写移植性尽可能强的代码,不要写一些奇技淫巧的语法,用较老但不是远古的语言标准,选用绝大多数编译器都支持的编译器拓展,尽可能少写依赖硬件或平台库(哪怕是标准库)的代码。

当然你也可以和我一样走走极端,不依赖三方库标准库从头实现内存管理,数据结构及数学库,渲染器,脚本编译器,虚拟机…

至少只要资源足够,大部分情况代码是可以不需修改直接移植的.(绝大部分情况不推荐这么干)

PainterEngine 一个由C语言编写的完整开源的跨平台图形应用框架www.painterengine.com/

img


“比如c c++中的clock()函数,它在time.h中定义,可是我就不懂它到底能否在裸机下运行,还是必须要有操作系统呢?这个到底要怎么判断呀?”

*什么是裸机程序?*

裸机程序就是指嵌入式系统的软件在没有操作系统的支持下运行,有时也称谓“裸跑”。一般C51/STM32 采用keil开发工具,其本身提供了汇编语言的启动代码。有了基本设置的执行环境。那么你编译出来的就是 裸机程序 。

*答:“c++中的clock()函数,它在time.h中定义,可是我就不懂它到底能否在裸机下运行” 这个是可以运行的,*keil IDE 已经连接生成到 XXX.hex 文件里面了。刷机即可执行代码。


如果你所谓的裸机程序,是指能适用于MCU平台的、不运行于OS的程序。那么,你只要用到了系统资源(时钟、网络、内存、IO口等),那就是依赖于具体平台的。

就说你具体例子:clock(),这个函数是个什么的?

不懂的时候,先man手册查一下。但凡你能查到的函数,基本是依赖于OS的。文档里面也会提到锁遵循的标准(“CONFORMING TO”)。当然,也有一些函数如printf是C库实现的,你用裸机编程,也需要include对应平台的库。

clock()用来取得应用程序从起到到当前,所用占用的CPU时间。很显然,涉及到CPU时间(计时功能),并非裸机程序。

NAME
       clock - determine processor time

SYNOPSIS
       #include <time.h>

       clock_t clock(void);

DESCRIPTION
       The clock() function returns an approximation of processor time used by the program.

RETURN VALUE
       The  value  returned  is  the  CPU time used so far as a clock_t; to get the number of seconds used, divide by
       CLOCKS_PER_SEC.  If the processor time used is not available or its value cannot be represented, the  function
       returns the value (clock_t) -1.
...
CONFORMING TO
       POSIX.1-2001,  POSIX.1-2008, C89, C99.  XSI requires that CLOCKS_PER_SEC equals 1000000 independent of the ac‐
       tual resolution.

纯逻辑的代码,比如算法,流程控制等,不涉及硬件资源,通常是裸机程序。


题主问题问的不是很严谨,但是我理解题主想问的是什么。

题主是想知道那些标准c库里的函数是否有运行依赖,那么很简单,第一步打开编译生成可执行文件对应的map文件,找到程序里你有疑惑的函数看他们是在那个lib文件内定义的,如果这个lib的源码是开源的,例如glibc,newlibc里的,那么去找到对应的源码实现查看他们的源码是否有运行时依赖,如果有syscall之类系统调用,那么就需要操作系统,如果没有,那么就不需要。

如果题主发现你的libc库不是开源方案,那么请联系给你提供编译工具链的供应商,要求他们给出说明或授权源码给你,亦或是查找供应商的文档资料描述确认。

最后,常见的嵌入式裸机开发开源方案一般使用newlibc库,这个库如果适配了所有桩函数后,基本上所有的库函数都可以在裸机运行。


裸机和操作系统的区别是,裸机的应用程序是在特权模式下运行的,而基于操作系统的嵌入式系统的应用层程序是在非特权模式下运行,需要通过操作系统才能访问到一些内存和IO

拿cortex m3处理器为例,cortex m3支持两种模式,一种是线程模式,一种是处理模式,线程模式就是我们普通程序代码执行时所在的模式,当出现异常时会进入处理模式,一开始上电时,cpu在初始化后会默认进入具有特权的线程模式,之后的线程模式将一直具有特权。

而当加入操作系统后,在初始化操作系统时,会进行一步操作,那就是通过修改寄存器的值,将线程模式的特权关掉,只允许运行在处理模式下的操作系统具有特权。

所以从最底层的逻辑来讲,通过查看有没有关线程模式特权的汇编可以来进行判断

当然我们还有更简单的一些方法,就是看应用层有没有调用操作系统向上提供的api接口函数。

这就是我对于这一题的认知与解答,希望可以帮到你。

另祝,学习愉快~


你说的这个叫freestanding。能不能裸机运行,主要是看这个功能是否需要与内存以外的东西交流。

你在内存里面瞎写写,排个序,这全套下来都只涉及到内存和立即数,所以是不需要os的。一般来说,要与外界交互的都要os支持。时间是os用定时器硬件提供给你的,文件是os提供的。

new和printf是例外。前者本质上只要在启动前记下空闲内存的位置大小就能实现;后者因为太常用了,所以裸机库一般会专门实现,或者重定向(不知道重定向这个说法是否规范,单片机喜欢这样说)。


这里有个表。这个表只是说裸机环境下有哪些可以使用,但真的能不能使用,还得看有没有提供相关的库,有没有链接进去。像单片机就很少用new,更没见过用exception的。

img


没法判断一段程序代码是不是裸机程序

对于编译好的elf程序,你可以读取elf头判断是不是裸机程序,比如用file命令或者readelf命令

clock()是在time.h中声明,但是一般不在头文件里实现,编译器会链接标准库,标准库提供clock()的具体实现

有些编译器会链接newlib或者mculib这样不完整的需要打桩的标准库,我们称之为裸机编译器


作者:林小浩
链接:https://www.zhihu.com/question/41802793/answer/2160174159
来源:知乎
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

**写博客可能是程序员增加额外收入,体现自身价值的最简单方式了。**本人做程序员6-7年来,通过写博客和文章获得了可观的收入,同时也被一些互联网大佬“翻过牌”,认识了一些更加优秀的人。

下面是我写的一些文章汇总。最让我自豪的是,**我写的《图解算法小册》,解析 150 道高频算法面试题目,**纯原创的内容,曾经在 github 排到过全球榜首,全网下载超过 10w 次。同时拥有一个近 10w 粉丝的公众号。

从0到年薪50w你要学的所有编程知识点!145 赞同 · 12 评论文章img

img

下面说一下我这些年写博客的一个感受。

一、初衷

我写文章(或者说博客)的初衷很简单,就是因为我学习技术比较慢,很多东西看完了,第二天肯定忘。都说好记性不如烂笔头,所以就各种记笔记。

在北京为了图个高性价比的房租,就各种搬家,每次搬家都会丢点东西,之前记得笔记本都丢得差不多了。还有就是,随着技术越学越多,感觉之前比较简单的笔记就没什么用了,扔掉又觉得可惜。

为了留作纪念,就想着如何永久保存。那么博客或者云笔记就是一个非常好的方式。于是就开始了我的文章创作之路。

创作前期,由于笔记记得非常烂,很多东西都写得非常潦草,很多自己都看不懂,总感觉这样没什么意义,于是开始不断的对内容进行整理、完善。

慢慢的我的技术文章(博客)创作之路步入正轨。

二、创作历程

创作前期经常把一些文章写在CSDN、博客园之类的平台上,写一篇技术文章之后,就各种搬运。记得当时每天光复制粘贴就耗费好长时间,复制的平台有很多,比如CSDN、博客园、企鹅号、天涯社区、大鱼号、百家号等等。我的想法就是,管他有没有人看,先占个地。

创作前期主要写技术文章,简单梳理个逻辑,然后就各种拼凑,文章也质量要求很简单,只要自己能看懂就行,不管别人能不能看懂。

当我看见第一条鼓励的评论时,彻底的改变了我对待写文章的一个态度。也体会到“我的文章可能在影响很多人”。从此开始认真的写好每一篇文章,同时也将我的写作战场从各大平台转到了“公众号”。

随着时间推移,我的**《图解算法小册》诞生了。**这也是我比较自豪的一个技术系列,这个系列让我获得了很多粉丝。这时我对写作的体会也慢慢加深。

于是在创作风格上也慢慢开始转变,从写一些技术文章到写一些程序员热点以及程序员生活等。详细创作方式之后单独写一篇文章介绍一下。

三、创作收获

创作收获有很多,可能大部分人都比较关心,到底赚了多少钱。详细内容大家可以看这里。

月薪 4 万人民币是一种怎样的感受?367 赞同 · 37 评论回答img

我重点想说的是一些其他的收获。

首先,创作是一个痛苦的过程,比较消耗人的耐性,很多人都很难坚持下来,当然,我也放弃了很多次,幸亏的我的创作初衷比较简单,能记录东西就好,反正学技术也得记录内容。就这样不抱着太大期望去做这件事,反而更容易成。

因此,这也是最大的收获,之前做所有事都是三分热血,这件事让我清楚,我也是可以坚持一件事。

其次,就是对个人职业的帮助,有了网络曝光度后,让更多的人看见了我,了解了我这个人。其中有很多互联网公司的大佬,甚至有很多人给我开出高薪。这是作为一个普通程序员很难遇到的。也非常感谢各位大佬的赏识。

最后,就是认识了各种各样优秀的人,同时,也和很多人有了深入的合作,和大佬们言语之间扩展了自己的思维高度,对待个人成长和人生的态度都有了新的转变。

当然还有就是个人的技术、职业发展都有了质的进步。

四、建议

最后给各位程序员同仁一个建议,要想被更多人看见,就得学会曝光自己,让更多人了解你。


我不知道其它程序员写博客的原因,我写技术博客的动机如下。

1 通过持续输出,让自己有个更好的工作和挣钱的状态,不至于躺平。

2 为我出书和挣钱做准备,同时通过写博客,推广我的书和各种挣钱渠道。

3 通过写博客,更看到更多大牛的文章和博客,从而能更好地探索其它的挣钱渠道。

再说下我写博客的收益。

1 通过写博客的锻炼,我出了不少技术书,我在找工作时,这些我出的技术书很好地帮到了我。

2 让我能不断找到了提升流量的方法,从而能更好地给我带来收益。

3 同时给我带来了直接的现金的收益。

接下来我就详细展开写下我写技术博客的历程。

我是17年开始在博客园里写博客的,如下的几篇博客,或者是点击量众多,或者是被博客园选做编辑推荐,放在博客园网站的顶端位置。

如何在面试中介绍自己的项目经验 - hsm_computer - 博客园www.cnblogs.com/JavaArchitect/p/7586949.html

进大厂也就这回事,工作后2到3年进大厂操作指南 - hsm_computer - 博客园www.cnblogs.com/JavaArchitect/p/12665483.html

借着谈转正感想的时机,再聊聊外企和互联网公司的工作体验 - hsm_computer - 博客园www.cnblogs.com/JavaArchitect/p/11732544.html

用象棋的思维趣说IT人的职业发展和钱途 - hsm_computer - 博客园www.cnblogs.com/JavaArchitect/p/11065287.html

从国际象棋与象棋的走法差异,再趣说IT人提升能力和增收方式 - hsm_computer - 博客园www.cnblogs.com/JavaArchitect/p/11246162.html

当时写这篇文章的时候,不像现在一样为了推广和为了钱,所以感觉写的时候有种“灵气加持”的神奇感受,写到关键处,感觉脑海里自然就出现了后面该出现的最好的用辞。

那个时候,有很多公众号的运营者要求来转载我的文章,有时一篇文章出来,来的晚的人,还无法转载成。说是转载,但不少人其实是以“原创”的方式放在他们的公众号里的,顶多最后用段文字来给明出处,到现在我自己公众号要用我自己的文章还不成。

其实那些肯标明转载的已经算比较良心了,更多的是就直接抄袭。但是我还是得说句比较矫情的话,我写这些文章时产生的灵气加身的感受,其实是我最大的收获,我后面的职业发展,以及到出版社去出书,还真靠了这些灵气和灵感。

后面我就出书了,也开始出书和做公众号了。也到知乎来写文章了。在知乎里,我输出的博文给我带来的流量更为客观,现在一天能给我带来2万多的流量,最近比较爆款的是如下两篇。

HR如何筛掉千锋、达内、黑马出来的学员?2190 赞同 · 442 评论回答

计算机专业曾经有哪些方向非常火后来却凉了?884 赞同 · 238 评论回答img

其实再说句矫情的话,写文章,出书和做公众号,我目前靠这得到的收益并不多,但通过干这个,让我找到了能模仿和学习的对象。

比如我在刚写博客时,认为点击量过万就不错了,通过写博客过程中的找资料,我顺带揣摩出了写高流量文章的技巧。再如,本来我认为干兼职就是出视频和出书,但现在我通过实践,也积累了不少变现的技巧,与之对应的,我公众号的广告收益,以及在知乎上的付费咨询收益也在与日俱增,同时我更打算开辟在线视频等营收渠道。

所以说,我非常支持程序员持续写技术博客,出文章,出书犹在其次,靠出文章出书在面试中更好地证明自己,也只是附带的收益。通过持续写博客,一方面能让自己处在积极向上的状态,而不会在空闲时经常去消遣,而且通过写文章写多了,灵气和灵感自然也就来了。

更为重要的时,程序员通过持续写博客,更能很好地开拓自己的眼界,比如能知道该怎么进大厂,当前有什么比较值钱的技术以及怎么提升,以及知道当前自己做努力的领域有哪些大牛,同时能模仿这些大牛的操作来进一步提升自己。

当然,当程序员积累到一定程度,或许可以通过其他更高效的方式提升自己和挣钱,但在积累阶段,持续写技术博客,确实是一个高效提升自己的方法。


作者:啊距离具体
链接:https://www.zhihu.com/question/438340661/answer/2618494025
来源:知乎
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

谢邀,路过顺便说几句。

一、程序必需元素分析

首先,所有的程序必然是周期性运行的死循环,哪怕中断程序。中断程序可以看成以单片机时钟频率或者外设时钟频率不停查询标志位,然后根据标志位状态运行的特殊程序。

**第二,**不同功能的程序运行周期不一致,但都可以归纳为几个常用的周期,例如100us,1ms.10ms.100ms和1s,没有必要自己跟自己过不去,新增一些奇怪的周期。

**第三,**不同功能程序对周期的准确性不一样,例如蜂鸣器io口驱动,对周期精度要求很高,如果翻转频率不一样会导致音色变得奇怪。电机驱动的ad中断,显示扫描的驱动都属于这类。这类一般靠中断和外设硬件完成。另外一类是对周期精度不敏感的,例如按键响应,误差个几十毫秒也没啥问题。

**第四,**程序是用来解决问题的,解决问题的基本方法就是将大化小,所以一个程序由不同功能程序协同完成,为了协同,不同子程序之间需要通讯。

综上,我们的程序架构要反映以上的四点,(而且建议大家要积累自己的框架,完善框架,这样每次出问题了,能根据框架很快定位到问题点。)

二、程序架构分析

  1. 对于硬周期程序,使用中断、或者外设执行,例如100us定时程序。PWM生成等。

2)对于软周期程序,使用主循环提供的定时标志运行。

3)每个任务都必需尽快完成自己的工作,然后结束。每个任务都使用查询的方法,查询到自己需要的结果就运行,查询不到就跳过。(对于阻塞,等上了RTOS或者使用PT_THREAD宏实现,有兴趣的话,可以新增这个话题)

4)部分任务需要硬周期与软周期配合完成任务,例如,串口通讯,串口先将数据缓存下来,再由软周期任务进行处理,等会再举例。

5)任务间的通讯,通常使用生产者消费者模式(每一个任务不是用来产生消息,就是用来处理消息,所以任务可以根据消息来进行划分),并且保证消费者消费速度快于生产速度,减少使用队列缓存等技术。例如按键扫描与按键处理程序,按键每10ms检测一次按键,有按键按下后产生按键标志。按键处理程序也10ms运行一次,确定按键标志,因此任务之间的通讯使用全局变量进行。

综上,基本程序框架如下,每一个程序必然包含如下的代码。

#include "global.h" /*global.h文件包含所有模块的子头文件*/
static uint8_t timeFlag100us,timeFlag1ms,timeFlag10ms,timeFlag100ms,timeFlag1s
/*100us中断程序*/
void  isr_100usr() {
     static uint8_t timeCnt1ms,timeCnt10ms,uint16_t timeCnt100ms;
     timeCnt1ms++;timeCnt10ms++;timeCnt10ms++;
     timeFlag100us = ~0;
     if(timeCnt1ms>=10)  {timeCnt1ms   = 0; timeFlag1ms   = ~0;}
     if(timeCnt10ms>=100) {timeCnt10ms  = 0; timeFlag10ms  = ~0;}
     if(timeCnt100ms>=1000){timeCnt100ms = 0; timeFlag100ms = ~0;}
}
/*需要用到1S的通常都是需要时间较准确的场合,所以使用单独时钟中断          */
/*若不需要太准确,可以放在100us_isr上,很多程序也用不到1S的周期,所以很少用*/
void isr_1s(){
    timeFlag1s = ~0;
}

void main(void){
     clk_init();/*系统时间初始化*/
     hw1_init(); /*一般硬件初始化,一般每个硬件的初始化都单独一个初始化函数,*/
     hw2_init(); /*  那么我们就可以通过Main程序来进行索引查找我们的程序*/
     /* hw...._init*/
     sw1_init();/*软件初始化,跟上面一样,每个软件模块单独一个初始化函数*/
     sw2_init();/*软件初始化,跟上面一样,每个软件模块单独一个初始化函数*/
     /* sw....._init  */ 
     while(1){
          if(timeFlag100us == 0)continue; timeFlag100us = 0;
          sw100us();/*100us软周期程序*/
          if(timeFlag1ms == 0)  continue; timeFlag1ms   = 0;
          sw1ms();/*1ms软周期程序*/
          if(timeFlag10ms == 0) continue; timeFlag10ms = 0;
          sw10ms();/*10ms软周期程序*/
          if(timeFlag100ms == 0)continue; timeFlag100ms = 0;
          sw100ms();/*100ms软周期程序*/
     }
}

三、基本程序结构-普通程序

二上面说的是整体的软件框架,现在说的是每个子程序框架是怎么样,这小节说的是普通程序。刚才说到每个程序应该使用查询而不是死等的方法进行。现在以按键程序为例。

#include "global.h" /*global.h文件包含所有模块的子头文件*/

/*按键扫描程序,在主循环每10ms执行一次*/
static uint16_t keyBuf[KEY_MAX];
uint8_t key_cmd;
void KeyIint()
{
     key_cmd = KEY_CMD_NULL;
}
void  keyRun(void){
     if(IO_KEY1 == 1){
             if(keyBuf[0] < 300)keyBuf[0]++;
             if(keyBuf[0] == 5)key_cmd = KEY_0_DOWN;     /*有按键按下并且进行5次滤波*/
             if(keyBuf[0] == 100)key_cmd = KEY_0_1S_LNG;/*1S长按*/  
      }
      else{
            if(keyBuf[0] >=5)) key_cmd = KEY_0_SHORT; /*有部分程序是按键按下时处理,有部分是松开时处理*/
            keyBuf[0] = 0;
      }
      if(IO_KEY2 == 1){
           /* just like up*/
       }else{
          /*........*/
       }
      if(IO_KEY3 == 1){
           /* just like up*/
       }else{
          /*........*/
       }
}

然后到key.h文件,该问题将生产内容提供给消费者,至于谁是消费者,key不关心。

/*  key.h文件 ,该文件包含在global.h上 */
#ifndef __KEY_H__
#define __KEY_H__
/*需要的头文件*/
void KeyIint(); /*在main文件里初始化调用*/
void  KeyRun(void);/*这样main函数才能调用*/
extern uint8_t key_cmd;
enum{
    KEY_CMD_NULL = 0,
    KEY_0_DOWN     ,
    KEY_0_1S_LNG,
    KEY_0_SHORT,
   /* ........others */
}
#endif

有生产者必然有消费者,按键扫描负责产生按键消息,那么必然有一个按键处理程序进行处理

#include "global.h"

void KeyProcInit()  /*按键处理初始化函数*/
{
   /*do something*/
}

void KeyProcRun(void){/*按键处理主程序,负责消费key_cmd,主程序每10ms运行一次*/
    if(key_cmd == KEY_0_DOWN){
         buzz_on_cmd  = BUZZ_SHORT; /*蜂鸣器短响一下,由峰鸣器程序提供*/
     }
      if(key_cmd == KEY_0_1S_ONG){
         buzz_on_cmd  = BUZZ_LONG; /*蜂鸣器长响一下,由峰鸣器程序提供*/
     }
     key_cmd = KEY_CMD_NULL;
}

keyproc.h的文件就不写了,麻烦。。。。。

三、基本程序结构-软、硬周期程序配合

刚才的蜂鸣器程序就是最好的软、硬件周期配合的例子,因为蜂鸣器IO口翻时间需要比较精准,所以需要使用硬件定时器完成,但对于命令的接收只需要保证足够快就行,而且也不一一定要比生产快,因为对于大部分应用来说,蜂鸣器响声不是致使的,偶然丢弃也没问题。所以为了CPU资源,有此东西可以做取舍的。当然现在只是举例而已。

#include "gloal.h"

uint8_t buzz_on_cmd ;
static uint8_t buzz_on_cnt;

/*蜂鸣器中断程序,由100us中断程序调用*/
void BuzzIsr(){
     if(buzz_on_cnt >0){
         IO_BUZZ^=1;
     }
}
void Buzzinit(){
buzz_on_cmd = 0;
}

/* 在主程序10ms周期运行*/
void BuzzRun(){
    
    if(buzz_on_cmd == BUZZ_SHORT){
         buzz_on_cnt = 5;
     }
     if(buzz_on_cmd == BUZZ_LONG){
         buzz_on_cnt = 20;
     } 
     buzz_on_cmd  = 0;

     if(buzz_on_cnt >0) buzz_on_cnt--;
     else               IO_BUZZ = IO_OFF;

}

/*头文件的东西就不写了。。。。。。。。*/

四、基本程序结构-软周期硬件查询程序

在使用串口通讯的时候,我们可以使用中断,也可以使用查询的方式,但我个人一般是尽量减少中断数量,所以串口通讯波特率不高的时候会尽量用查询。这里主要是为了演示如何减少程序堵塞。

#include "global.h"
/*下的是提供给其他程序使用的,其他程序将_u8SendCnt,_u8SendSize_u8SendBuf与uart程序通讯*/
uint8_t  _u8SendBuf[UART_SEND_BUF_MAX,_u8SendCnt,_u8SendSize;
void UartInit()
{

}

/*主程序100usr执行一次*/
/*所以有时候串口发送字节会有一两百us的延时,但一般不会影响使用*/
/*下面假设发送波特率是9600,一个字节的发送时间就是1ms左右*/
void UartSendRun()
{
      if(_u8SendCnt < _u8SendSize){
          if(TI == 1){/*51单片机典型的串口发送
              TI = 0;
              SBUF = _u8SendBuf[_u8SendCnt ++];
           }
      }
}

串口发送的解释程序,从该例子可以看出,我们可以看到uartSend程序不关心谁启动了他,谁填充了数据。所以以后如果我们要增加发送协议,可以单独新增一个程序用来填充该数据。但这样的话,会导致程序间争夺uasrSend,当然可以增加同步锁来解决,具体的可以参考RTOS。但你也可以自己想想办法看怎么解决,对于你学RTOS挺有好处的。

#include "global.h"

void UartComIint(){

}

/*每10ms执行一次*/
void UartComRun(){
  static uint8_t timeCnt;
  if(timeCnt++<= 5)return;/*就是程序每50ms才执行一次啦。
  if(_u8SendCnt < _u8SendSize) return;/*查询是否已经发送完*/
  timeCnt = 0;
/*填写发送数据帧*/
 _u8SendBuf[0]= 0xxx;
_u8SendBuf[1] = 0xxx;
 ......
  _u8SendBuf[10] = 0xxxx;
 /* 通过这些变量通知uartSend程序开始发送*/
 _u8SendCnt = 0;
 _u8SendSize = 11;
 TI= 1;

}

五、基本程序结构-状态机

状态机说难不难,说复杂不复杂,也不在这里写了,有空会新增这个话题。

六、最终程序结构

#include "global.h" /*global.h文件包含所有模块的子头文件*/
static uint8_t timeFlag100us,timeFlag1ms,timeFlag10ms,timeFlag100ms,timeFlag1s
/*100us中断程序*/
void  isr_100usr() {
     static uint8_t timeCnt1ms,timeCnt10ms,uint16_t timeCnt100ms;
     BuzzIsr();/*优先执行,尽量减少IO抖动*/
     timeCnt1ms++;timeCnt10ms++;timeCnt10ms++;
     timeFlag100us = ~0;
     if(timeCnt1ms>=10)  {timeCnt1ms   = 0; timeFlag1ms   = ~0;}
     if(timeCnt10ms>=100) {timeCnt10ms  = 0; timeFlag10ms  = ~0;}
     if(timeCnt100ms>=1000){timeCnt100ms = 0; timeFlag100ms = ~0;}
}
/*需要用到1S的通常都是需要时间较准确的场合,所以使用单独时钟中断          */
/*若不需要太准确,可以放在100us_isr上,很多程序也用不到1S的周期,所以很少用*/
void isr_1s(){
    timeFlag1s = ~0;
}

void main(void){
     clk_init();/*系统时间初始化*/
     hw1_init(); /*一般硬件初始化,一般每个硬件的初始化都单独一个初始化函数,*/
     hw2_init(); /*  那么我们就可以通过Main程序来进行索引查找我们的程序*/
     /* hw...._init*/
     sw1_init();/*软件初始化,跟上面一样,每个软件模块单独一个初始化函数*/
     sw2_init();/*软件初始化,跟上面一样,每个软件模块单独一个初始化函数*/
KeyIint();
KeyProcInit();
Buzzinit();
 UartInit();
     /* sw....._init  */ 
     while(1){
          if(timeFlag100us == 0)continue; timeFlag100us = 0;
          sw100us();/*100us软周期程序*/
            UartSendRun();
          if(timeFlag1ms == 0)  continue; timeFlag1ms   = 0;
          sw1ms();/*1ms软周期程序*/
          if(timeFlag10ms == 0) continue; timeFlag10ms = 0;
          sw10ms();/*10ms软周期程序*/
         KeyProcRun();
         BuzzRun();
        UartComRun();
      keyRun();
          if(timeFlag100ms == 0)continue; timeFlag100ms = 0;
          sw100ms();/*100ms软周期程序*/
     }
}

我个人比较喜欢10ms程序,所以一般都放在10ms运行。

七、程序运行时间分析

可以看到每个时间,程序执行数量都不一样,那么必然会导致每个时间标签出现抖动。对于我们来说,最重要的是抖动是否在接受范围内,不能接受那么该放在中断执行。而且每个程序都使用的是查询的或者状态机的方法,执行时间都很短。实际使用的时候,需要放个IO口在100us的程序时进行翻转,用示波器观测IO频率,评估抖动是否能接受,不能就让任务进行划分,或者选一个更好的单片机。

八、文件结构

建议每个功能模块使用一个文件,方便定位源代码和进行单元测试。

九、总结

哈,很多人会问,为什么100us的查询不直接在while1那里一直查询?因为在100us查询的话,我们可以相对准确的设定我们的查询时间和次数。

第二、程序想到哪写到哪,每个程序都能单独的进行运行和测试 。如果能使用指针函数,全局变量都不需要使用,要定位问题时,可以先一些程序注释掉,先不运行,也妨碍其他程序运行。

第三、为以后使用RTOS打下基础。开始学会怎么划分任务,任务怎么通讯。以后用上了RTOS后,每个程序变成RTOS的任务就行了。对于学习RTOS我认为有好处。而且随着这个框架使用多了,使能发现它的不足,开始懂得为啥需要RTOS了。

第四、因为是随手而写(手机码字,头疼),篇幅也有限,很多知识点也没写上,例如不使用RTOS怎么写堵塞程序(就是刚才说的PT-thread),全局变量怎么管理,状态机,生产者消费者模型等。不足之处请原谅哈。

第五、单片机编程思路应该能拓展到计算机上,计算机也只是一个大循环不停的套小循环。


看了下别的回答,推荐的《时间触发嵌入式系统设计》这本书我看过,值得一看,我现在的程序基本上受这个影响出来的。

程序上把应用层和底层分开,底层调用顶层可以用函数回调,比如说按键的处理底层扫描按键和顶层处理具体按键消息要分开。应用层把不同的状态逻辑分开,比如说待机和运行做的事情肯定是不一样的。

我现在的程序应用层是事件驱动的,底层是模块化的,接口是统一的格式。一定要抽象出这个模块的特性,比如说蜂鸣器有源无源模块给出去的接口是一致的,底层改有源无源不要影响顶层使用蜂鸣器驱动。又比如说按键抽象出来统一的事件和键值,底层按键是扫描的还是独立的不要暴露给顶层去处理。

粗颗粒度的时间用一个统一的时间切片管理,细颗粒度的延时在不影响别的任务执行时用delay也没关系。一些模块比如红外需要用IO中断+定时器的直接在模块内部处理,中断任务短小是不影响主循环任务执行的。

任务以状态来驱动,每次进任务处理完就退出去,不要死等下一个状态。比如说按键处理的过程可以大概分为空闲、消抖、长按判断、重复长按判断,这些状态中间时间间隔很长,但处理起来其实很简单,用状态来解决掉状态中间的延时,让出CPU给别的任务。


看了很多回答,我感觉大部分没有介绍的一点是:

对于一个需求明确的项目,如果你使用裸机实现比较困难的话,那么往往使用各种框架或RTOS来实现,也是依然困难的。


无论是裸机还是现成框架或者RTOS,哪种方法来实现你的项目,你都要对项目进行

1.功能划分;

2.各个功能之间如果有联动,就会有信号传递;

3.各种耦合的解耦;

4.资源的占用和释放(比如串口收发);

5.输入输出的合理采集、控制;

6.各个功能模块之间的“调度”(借用RTOS的概念来总结。也就是意味着哪怕是裸机也会有简单的调度)

仓促这么总结,一般项目大体如是,有遗漏也有可能,可以留言提醒。


当这些事情做好,你会发现,无论是裸机还是RTOS,都要遵循你的这些顶层设计。

其中裸机就需要自己来做调度,自己做资源管理,自己做信号量的收发控制,特点是灵活完全可控,代价是容易自己搞出各种坑;

而RTOS提供给性能优秀的调度、资源管理、信号收发机制,代价就是你必须按照RTOS的规范来使用他们,如果使用方式不合理,也会出各种坑。

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

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

相关文章

【API部署】fastapi与nuitka打包py项目

提示&#xff1a;分两部分&#xff1a;fastapi接口调用&#xff0c;与nuitka快速打包 功能&#xff1a;作为一名算法工程师&#xff0c;训练机器学习模型只是为客户提供解决方案的一部分。 除了生成和清理数据、选择和调整算法之外&#xff0c;还需交付和部署结果&#xff0c;…

130道基础OJ编程题之: 29 ~ 38 道

130道基础OJ编程题之: 29 ~ 38 道 文章目录130道基础OJ编程题之: 29 ~ 38 道0. 昔日OJ编程题:29. BC23 时间转换30. BC24 总成绩和平均分计算31. BC30 KiKi和酸奶32. BC31 发布信息33. BC3 输出学生信息34. BC33 计算平均成绩35. BC34 进制AB36. BC37 网购37.BC39 争夺前五名38…

【谷粒商城】

一、项目介绍 1.微服务架构图 2.微服务划分图 二、环境搭建 1.虚拟机搭建环境 这里我买了华为云&#xff0c;没用虚拟机 华为云配置 2.Linux 安装docker docker文档&#xff1a;https://docs.docker.com/engine/install/centos/ # 1. 卸载之前的dockersudo yum remove d…

[MySql]初识数据库与常见基本操作

专栏简介 :MySql数据库从入门到进阶. 题目来源:leetcode,牛客,剑指offer. 创作目标:记录学习MySql学习历程 希望在提升自己的同时,帮助他人,,与大家一起共同进步,互相成长. 学历代表过去,能力代表现在,学习能力代表未来! 文章目录 前言 1.初识数据库 1.1 数据库概述 1.2 数据库…

mysql隔离级别RR下的行锁、临键锁、间隙锁详解及运用

一&#xff1a;mysql 锁的基本概念 锁&#xff1a;悲观锁、乐观锁 悲观锁&#xff1a;写锁 for update、读锁for share 写锁&#xff1a;只允许当前事务读写&#xff0c;其它事务全部等待&#xff0c;包括读取数据&#xff0c;锁的数据范围需要具体分析 读锁&#xff1a;允…

【前端】Vue+Element UI案例:通用后台管理系统-Echarts图表:折线图、柱状图、饼状图

文章目录目标代码数据改写为动态Echarts引入与html结构折线图&#xff1a;orderData柱状图&#xff1a;userData饼状图&#xff1a;videoData总效果总代码:Home.vue上一篇&#xff1a;【前端】VueElement UI案例&#xff1a;通用后台管理系统-Echarts图表准备&#xff1a;axios…

公司缺人自己搞了vue又搞koa,熬夜把架子搭起来

如果有一天&#xff0c;人手紧缺&#xff0c;自己搞了前端还要搞服务端&#xff0c;今天我们把这个项目架子搭起来&#xff0c;让前端同学也可以轻松全栈开火。 技多不压身&#xff0c;活儿多了可压身啊 目录 一、上午写VUE 1、 新建一个我们的伟大项目文件夹 2、用vscode打…

程序中断方式

中断的基本概念 程序中断是指在计算机执行现行程序的过程中&#xff0c;出现某些急需处理的异常情况或特殊请求&#xff0c;CPU暂时中止现行程序&#xff0c;而转去对这些异常情况或特殊请求进行处理&#xff0c;在处理完毕后CPU又自动返回到现行程序的断点处&#xff0c;继续…

c语言之“数组”初级篇

前言 牛牛又和大家见面了&#xff0c;本篇牛牛要讲的内容是c语言中有关数组的内容。 欢迎大家一起学习&#xff0c;共同进步。 目录前言数组一、一维数组1.1 一维数组的创建1.2 一维数组的初始化1.3 一维数组的应用1.4 一维数组的存储二、二维数组2.1 二维数组创建2.2 二维数…

MySQL的select语句

SQL概述 SQL背景知识 1946 年&#xff0c;世界上第一台电脑诞生&#xff0c;如今&#xff0c;借由这台电脑发展起来的互联网已经自成江湖。在这几十年里&#xff0c;无数的技术、产业在这片江湖里沉浮&#xff0c;有的方兴未艾&#xff0c;有的已经几幕兴衰。但在这片浩荡的波…

基于android的车辆违章停放执法移动APP(ssm+uinapp+Mysql)-计算机毕业设计

车辆违章停放执法移动APP的功能已基本实现&#xff0c;主要实现首页&#xff0c;个人中心&#xff0c;市民管理&#xff0c;警察管理&#xff0c;罚单信息管理&#xff0c;缴费通知管理&#xff0c;系统管理等功能的操作系统。 论文主要从系统的分析与设计、数据库设计和系统的…

【机器学习】回归的原理学习与葡萄酒数据集的最小二乘法线性回归实例

文章目录一&#xff0c;回归1.1回归分析的基本概念1.2线性回归1.3最小二乘法1.4一元(简单)线性回归模型1.4.1随机误差项(线性回归模型)的假定条件1.4.2参数的普通最小二乘估计(0LS)1.5葡萄酒数据集的最小二乘法线性回归实例一&#xff0c;回归 1.1回归分析的基本概念 回归分析…

前端一面经典vue面试题总结

一般在哪个生命周期请求异步数据 我们可以在钩子函数 created、beforeMount、mounted 中进行调用&#xff0c;因为在这三个钩子函数中&#xff0c;data 已经创建&#xff0c;可以将服务端端返回的数据进行赋值。 ​ 推荐在 created 钩子函数中调用异步请求&#xff0c;因为在…

受激拉曼散射计量【Stimulated-Raman-Scattering Metrology】(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️❤️&#x1f4a5;&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑…

单元测试的时候读不到resources.test中配置

背景 接手了几个老工程&#xff0c;跑单元测试的时候&#xff0c;发现数据库的配置总是走了dev环境&#xff0c; 原因是工程中分环境进行了db的配置 历史经验 指定本地环境 ActiveProfiles(“test”) 没有生效 解决 在pom文件中 新加如下配置 <build><!--单元测…

Java—类加载机制

类加载机制 我们多次提到了类加载器ClassLoader&#xff0c;本章就来详细讨论Java中的类加载机制与ClassLoader。 类加载器ClassLoader就是加载其他类的类&#xff0c;它负责将字节码文件加载到内存&#xff0c;创建Class对象。与之前介绍的反射、注解和动态代理一样&#xf…

奶制品数据可视化,去年全国奶制品产量高达3778万吨,同比增长7.1%

奶制品是生活中很常见的一种补充人体所需维生素和矿物质元素的重要食品&#xff0c;在生活中奶制品也是很常见的&#xff0c;食用最多的是牛奶。牛奶中含有非常丰富的钙质&#xff0c;睡前适当给孩子食用&#xff0c;可以补充孩子所需的钙质从而达到长高的效果。 很多小伙伴经常…

C++ 类和对象以及内存管理 练习错题总结

作者&#xff1a;小萌新 专栏&#xff1a;C初阶作业 简介&#xff1a;大二学生 希望能和大家一起进步 本篇博客介绍&#xff1a;对于我们上一周学的知识做一个总结 查缺补漏 C 类和对象以及内存管理练习类和对象 (上)拷贝构造函数类和对象&#xff08;中&#xff09;重载函数运…

MySQL 主从复制与读写分离

一 概念 什么是读写分离 读写分离&#xff0c;基本的原理是让主数据库处理事务性增、改、删操作( INSERT、UPDATE、DELETE) &#xff0c;而从数据库处理SELECT查询操作。数据库复制被用来把事务性操作导致的变更同步到集群中的从数据库。 为什么要读写分离 因为数据库的“写…

刷题笔记之九(查找输入整数二进制中1的个数+完全数计算+杨辉三角的变形+计算某字符出现次数)

目录 1. 聚合查询是进行行与行的运算合并&#xff0c;是不能和别的列放在一块查询 2. PHP数据库查询语句 3. 有group by时先执行where&#xff0c;后执行having 4. join常用两张表内连接和外连接&#xff0c;用一张表可以自连接 5. 删除数据用delete&#xff0c;删除表时…