单片机/MCUwilliam hill官网
直播中

yyy71cj

10年用户 731经验值
擅长:测量仪表 EMC/EMI设计 接口/总线/驱动 控制/MCU
私信 关注
[文章]

《单片机编程魔法师之高级裸编程思想》数据驱动程序

        当你做了很多单片机项目的时候,你可能需要这些:
多任务程序
多任务并行程序
多个定时器
多个延时并行
面向对象编程
        但是,你首先需要数据驱动程序……

回帖(78)

阿信509

2014-10-23 22:25:03
这个是一本书吗还是别的啥
举报

yyy71cj

2014-10-24 20:23:52
本帖最后由 yyy71cj 于 2014-10-24 20:54 编辑
引用: 阿信509 发表于 2014-10-23 22:25
这个是一本书吗还是别的啥


带书名号的是书(签名版item.taobao.com/item.htm?id=41499514851),我这里发书的第一章“数据驱动程序”
举报

yyy71cj

2014-10-24 20:34:09
本帖最后由 yyy71cj 于 2014-10-26 17:47 编辑

数据驱动的程序
1.1 什么是数据驱动程序
1.1.1 定义
        数据驱动的程序是什么呢?在这里我先下个定义。
        数据驱动的程序就是通过使用脚本解析器(无数据的代码)对数据脚本(无代码的数据)进行解析而驱动程序按照一定的逻辑进行演绎的程序。
        这个定义告诉我们,编写一个数据驱动的程序要重点做三件事,即编写一个脚本解析器、一个数据脚本、以及脚本解析器与数据脚本之间的控制协议(即演绎逻辑)
        控制协议是一个数据驱动程序得以形成的基础。在实际工作中,我们通常可以通过对项目的共性进行数据分析,从而获得项目被数据提取后的内在运动规律,根据这种规律以及程序控制的要求形成控制协议。
        数据脚本是我们所面临的项目的体貌特征。被数据提取后的项目不再需要任何的文字描述,数据本身、数据量的相对大小、数据与数据的位置关系、数据的存取方式等客观物理属性将全面描述项目的静态与动态特征。
        脚本解析器则是一个控制协议的实施者与一个数据脚本的演播者。脚本解析器必须为演播翻译相应的数据脚本而生,它演播翻译数据脚本中数据的方式必须遵守已经形成的控制协议中的控制精神。脚本解析器通过对数据脚本中的数据按照一定的秩序进行扫描解读,从而再现任务项目中所期望的客观世界的事物的运动过程,并因此而达到完成项目解决问题的目的。
        因此在一个数据驱动的程序中必须有且至少有一批集中存放的被我们称之为数据脚本的数据,必须有且至少有一个身份为脚本解析器的函数(或过程),而控制协议则以一种固有规律的形式在精神层面上隐身于数据脚本与脚本解析器的代码逻辑中。
        数据驱动程序的控制过程如图1.1-1

举报

yyy71cj

2014-10-26 17:05:07
本帖最后由 yyy71cj 于 2014-10-26 17:49 编辑

1.1.2 简介
    我们通常是用代码来表达程序逻辑的,特别是在数据量并不大的时候。但是事实上有很多的逻辑,却隐藏在一些数据中。
    数据是如何反映客观事物的生存逻辑的呢?
    根据现行科学界的普遍认识,我们可以得到这样一个观点,那就是“数据是宇宙的语言”。也许我们与外星人之间没有共同的口头言语,但是对于宇宙的描述,智慧生物一定会使用相同的语法——数学,一定会使用相同的词义——数据。也正是这个观点,为我们的数据驱动的程序提供了一个理论上的支持。
    首先,数据的大小本身就是对客观事物的一种描述。例如这个物体体积是3立方米,质量是5千克。这里的数据“3”与数据“5”便是对该物体的一种准确的客观数据描述。准确的客观数据描述几乎毫无悬念地被人类所接受,对于这类数据的解释我们只需要用直白的方式就可以了。如果不遵循这种描述方式,我们将称之为扭曲的描述方式。扭曲的描述方式也会经常被我们使用在项目实践中,被扭曲过的数据将不再有直观的含义,解释它们当然也不能使用直白的方式,而必须要遵循一定的解释协议,这个解释协议也正是控制协议中的一部分。
    例如物体1的体积是7立方米,物体2的体积是8立方米,那么我们用数据“7”与“8”可以描述它们的体积,我们也可以用“700”与“800”来对它们的体积进行描述(如图1.1-2中的直白描述),……。很显然,这些描述我们只是使用了不同的单位,所有的物体之间的体积遵循着固有的比例关系,这看起来很直白。但有时候,我们会打破这种成规,例如为了节约内存而在有限的数据空间中描述一组对象,我们完全可以用扭曲的方式来描述它们。假如我们的数据都集中在500之上,那么我们可以以500为基准,所有的数都去掉一个500,将得到的以5000点的增量作为该物体的体积,我们将得到一组增量描述的数据(如图1.1-2中的增量描述)。假如这些数据更优的集中点在1000,那么我们就可以以1000为基准减去该物体的体积,将因此得到的补量作为该物体的体积,我们将得到一组补量描述的数据(如图1.1-2中的补量描述)。很显然,增量描述与补量描述都是一种扭曲描述。

    其次,数据与数据之间的相对大小也是对客观事物的一种描述。无论我们对于项目中的待描述成员采用了直白描述还是扭曲描述,数据与数据之间的相对关系都能成为我们所要关注的事物的本质规律(如图1.1-3)

    第三,数据的顺序决定了客观事物运行的时序,时序与数据的相对大小就决定了客观事物的变化规律,常见的时间曲线正是这一规律的描述者。
    数据中隐藏的规律远远还不只这么多,但是我们这里不一一絮说,因为上面典型的几个方面就已经足以能够告诉我们,数据具有足够的资格成为客观事物生存规律的描述语言。
    虽然我们通过代码逻辑也能实现任务的要求,但是这会让代码十分冗长,而且分支众多,程序会非常难以理解、记忆与维护。而如果我们将这些隐藏这奥秘的数据按照它们固有的规律整理出来并按一定的格式摆放,局面将会发生逆变,逻辑控制的代码将会十分简单而且易于理解、记忆与维护。
    举个例子来说明数据驱动的程序的结构特点。
    例如我们需要听歌,我们不能为每首歌编制一段代码来播放,所以我们就计划着设计了一种叫MP3的播放器,然后我们要求所有的歌都必须按照MP3的格式来存放。这样每首歌不再带有特定的代码,只要送入MP3中就能播放,而且当要听另一首歌时,播放器不会有任何改动的需要,只要换首MP3格式的歌就行。而当我们的播放器需要做升级换代时,原来的MP3格式的歌也同样没有任何改动的需要,还能与原来一样继续使用。这样,MP3播放器就相当于我们这里所说的解析器,而MP3格式的歌曲就相当于我们这里所说的数据脚本。
    这看或许起来很深奥!然而如果等你明白了其中的奥妙,你就会发现数据驱动的程序离我们并不是遥不可及。

举报

yyy71cj

2014-10-28 10:07:43
本帖最后由 yyy71cj 于 2014-10-28 10:10 编辑

1.2 四支方波问题与测试模型
1.2.1 问题1与分析
        为了探究数据驱动的奥秘,我们还是先从问题中寻求发现。
        能使用数据驱动程序的任务首先得有数据,而且数据的数量还得有一定的规模,理论上是越多越好。那么什么样的任务中会有很多数据呢?
        有大量数据的任务其实很多,例如存储器中就全是数据,很显然,如果我们要编写一个存储器操作系统(Memory Operation SystemMOS),我们就必须要把操作命令与存储器数据完全分开,否则我们得做大量的定制命令,这显然是编程者的噩梦。再例如上面提到的歌曲,还有常说的视频,它们都被描述成全数据的文件,如果我们要编写播放器,很显然我们不能把某首歌的数据混杂在播放器中。
        很多问题的数据显而易见,但是也有很多问题看起来似乎与数据无关。但由于计算机领域中的单片机技术完全是一门数字技术,所以看似与数据无关的问题,我们最终也得将它们进行数字化。例如一个动态过程的描述问题。
        下面我们就来先提出问题1。
        我们要解决一组方波的同步实现问题。这组方波包括4支单波,每支单波的波峰与波谷的宽度没有确定关系,但是4支波之间存在着一种时序同步关系。
        问题1的四支波相互关系如图1.2-1
    解决这个问题的处理芯片是80C52单片机。这是我们的初衷。无论这个问题对于51系列的单片机来说有多不容易,用它来实现是我们讨论的基础。
    图中所示的四支波显然非常有规律,从波1到波4频率为半频倍减,高电平脉冲宽度都相同,低电平的宽度不同使各支波的频率不同。
    问题中标记脉冲宽度的数值是以延时循环次数来标记的,这样做的目的只是为了简化与问题无关的参数,我们这里没有必要以秒或毫秒为单位。在问题1中,低脉冲的宽度为749个循环,高脉冲的宽度为251个循环,我们为什么在这里用这样的两个数据呢?这里暂时不做讨论。
    四支波的脉冲在脉冲翻转(如果翻转发生)的时候是同步的。也就是说,第四支波在由低电平向高电平翻转的同时,上面三支波也发生翻转,第三支波在由低向高电平翻转的同时,上面两支波也发生翻转,依此类推;这个规律同时也适合于高电平向低电平翻转的情况。

举报

李永林

2014-10-29 13:41:44
下载看看,学习一下。
举报

yyy71cj

2014-10-29 20:16:48
1.2.2 测试模型
        为了简化解决问题的途径,我们采用计算机仿真的方式来检验我们的试验结果。仿真软件工具我们使用Proteus软件的仿真功能模块(关于Proteus的用法读者可以参照相关的参考书,本书不做专门介绍)
        我们先来建立一个测试模型。
        在ProteusISIS中放置一个80C52,我们计划用P1.0~P1.3这四个口线来对应输出问题1中的第一到第四支波。为了观察程序的效果,我们再加上一个OSCILLOSCOPE示波器,然后将示波器的四个通道分别连到80C52P1.0~P1.3(如图1.2-2)。经过这番简单的准备后,一个测试模型就建立好了。
1.2-2 测试模型
    这个问题看起来并不是很难,然而真正实现它也许并不是那么容易的,而如果要很简洁地实现它,可能还算是有点纠结了。如果不信,可以根据自己的思路先动手试一试,然后再看下一节。
举报

yyy71cj

2014-10-30 20:06:10
本帖最后由 yyy71cj 于 2014-10-30 20:10 编辑

1.3 一支峰谷等宽方波的实现1.3.1 问题与分析
        为了做一个编程魔法师,我们一定要具有化腐朽为神奇的能力,把一个看似简单的问题透析到底。
        有了图1.2-2的测试模型之后,我们得首先考虑如何来实现问题1中的问题,这就需要一个草稿性的方案。
        面对这种周期性的题目,用循环延时来实现很显然是一种不错的选择,这也正是我们题目所要求的方法。
        也许有人会有反对意见,因为定时器实现延时是很好的方法。但是这里我们只用循环来解决问题。理由是,硬件定时器是单片机中很宝贵的资源,我们尽量不使用它,是因为有些问题几乎必须要有硬件定时器的支持,例如串口通信等。另外,也许在定时器里编程不一定是个好习惯,应为定时器与我们的主程序之间很难同步,中断会发生在主程序执行时的任何一个不确定的位置,这让延时可能会难于控制或者打乱我们的计划,这种情况在延时处理复杂、代码数量庞大的情况下尤为糟糕。
        总之,有时候我们不得不使用循环来编程,所以根据题目要求,这里我们不使用那些稀有资源。
        为了解决一个有点难度的问题,直接去对付问题也许不是最恰当的,我们可以采取一些迂回的策略来避其锋芒。
        问题1看起来太复杂,我们暂时不去实现它,我们先来做个简单点的。先看下面问题1-1的方波(如图1.3-1)


    我们这个迂回极大的简化了问题。首先四支波变成了一支波,这让我们编程时所受的约束得到了缓解,我们在实现时不用考虑与其他波的同步约束问题。其次波峰与波谷的宽度也统一了,这让这支波由原本的没规律变到现在的很有规律。
    在这个问题中,数据量看起来不多,但是包含了一个周期性的循环过程,它是一个动态的过程,也许现在我们还看不出这种动态过程该如何用数据来描述。但是好在这个问题很简单,我们不必对它的实现加上太多华丽的形式,也就是说对于这么简单的问题,我们完全不必去考虑数据分离的事情,我们在这里只需要运用常规思维来分析它就可以了,对于这个问题的分析与处理将会成为数据分离程序编制方法的起点。
    既然我们可以不在乎形式的处理这个问题,那一切都会变得熟悉而简单了。问题的简单就会衬托出我们的强大,现在我们无所畏惧地来编程实现它。
举报

2872890731

2014-10-31 11:32:11
万能的单片机波形发生器?
举报

电子发烧学习者

2014-10-31 15:01:25
谢谢楼主的分享,希望俄能够学习
举报

yyy71cj

2014-10-31 20:39:01
引用: 2872890731 发表于 2014-10-31 11:32
万能的单片机波形发生器?

主旨不在波,在数据驱动,波只是一个典型例子,由于时间匆忙,一次不能发很多
举报

yyy71cj

2014-10-31 20:41:52
引用: 电子发烧学习者 发表于 2014-10-31 15:01
谢谢楼主的分享,希望俄能够学习

一切皆有可能,欢迎参与
1 举报

yyy71cj

2014-11-16 10:00:07
1.3.2 实现
对于这种规则的方波,要实现起来就非常方便了,稍微有点编程经验的人编实现这样方波的程序便会新手拈来。
建立一个项目,添加如图1.3-2的.c文件到项目中。
文档:.. 1.c.. @Project:1.uvproj && Output: 1.hex
#include
***it P10=P1^0;
void delay(unsigned int t)
{
       unsignedint i;
       for(i=0;i
}
main(void)
{
       while(1)
       {
              delay(500);    // 延时500个循环单位
              P10 = ~P10;
       }
}
图1.3-2 实现问题1-1的程序

一支等宽方波的问题就被我们解决了,编写这段代码几乎不费吹灰之力。程序的对错如何我们可以通过仿真来验证。

yyy71cj

2014-11-16 10:04:15
1.3.3 仿真
将项目进行编译,产生十六进制代码文件1.hex,然后将它作为图1.2-2测试模型中U1(80C52)的Program File(如图1.3-3)。
数据驱动(007).jpg
1.3-3 80C52单击鼠标右键进入编辑元件设置ProgramFile
   
这样我们的测试模型就可以开始仿真了。点击ISIS左下角的“仿真开始”按钮,我们将毫不意外地得到我们想要的波形(如图1.3-4)。
数据驱动(008).jpg
1.3-4 测试模型仿真出问题1-1所示的方波
至此,也许我们并没有看出什么奥妙所在,而且更多的只是平淡无奇。但是这里却实实在在地隐藏了一个规律,也许很多人不会注意到,在此我们先不说到底隐藏了一个什么规律。我们再来看下一节的问题,或许会对我们有所启发。


举报

adauan

2014-11-19 09:48:14
真是好文章,支持
举报

yyy71cj

2014-11-21 21:02:16
1.4一支峰谷不等宽方波的实现1.4.1 问题与分析
上节我们做了一件十分平凡的任务,我们编写了一支峰谷等宽的方波实现程序,但那显然不是我们想要的结果。
简单的实践会让我们没有成就感,但我们不能因之而气馁。我们一定不会白做的,不是吗?因为神奇经常就隐藏在平凡之中,没有平凡往往就会让我们看不到神奇的所在。我们可以以之为起点,一步一步向目标靠近。
现在我们将问题1-1中的方波稍作一点修改,我们将等宽的方波变成不等宽的,波谷的宽度我们设定为749个循环单位,而波峰宽度设定为251个循环单位,这样我们就得到了问题1-2,一支新的方波了(如图1.4-1)。
数据驱动(009).jpg
这支方波较问题1-1中的方波在实现上难度略微有点增加,这会让我们嗜好挑战的心理稍稍多了一些满足,然而这不是主要的。主要的是,这只方波正好是我们问题1中所描述的四支方波中的一支。
这样我们就已经看到真正问题的身影了,实现它,也就是向解决最终的问题靠近了一步。

这支波只是在峰谷的宽度上虽然有所不同,但是实质上与问题1-1中的方波并没什么两样,实现的总体思路也不用做多大修改。下面我们编程来实现这只波。

1.4.2 实现
现在我们继续采用上面的思维方式来解决这个问题,相信这样的问题对于有丰富编程经验的人来说一定是易如反掌。
因此这里我们不需要为问题1-2中的方波实现程序做出什么特别的说明,直接给出代码就可以了(如图1.4-2)。
文档:.. 2.c.. @Project:2.uvproj && Output: 2.hex
#include
***it P10=P1^0;
void delay(unsigned int t)
{
       unsignedint i;
       for(i=0;i
}
main(void)
{
       while(1)
       {
              P10 = 0;
              delay(749);
              P10 = 1;
              delay(251);
       }
}
图1.4-2 实现问题1-2的程序



yyy71cj

2014-11-29 17:45:32
本帖最后由 yyy71cj 于 2014-11-30 09:13 编辑

1.4.3 仿真
    将编译出的十六进制文件2.hex指给80C52后,我们的测试模型就能得到如图1.4-3的仿真结果。
数据驱动(010).jpg
图1.4-3测试模型仿真出问题1-2所示的方波
    仿真结果并未出乎我们的意料,结果显示我们想要的我们做到了。



1.4.4 亮点分析
        从上小节的仿真结果看出,我们毫无悬念地得到了我们想要的波形。但是这不是我们应该关心的,因为到这里我们还什么都没有发现。
        为了获得我们所要关注的焦点,我们必须要分析一下图1.3-2与图1.4-2这两段代码的特点。然而每段代码都只有寥寥数句,有什么好分析的呢?如果那样想,那么我们将在这个事件上失去发现规律的机会。
        既然这些代码看起来似乎没什么亮点,那我们就关心一下这两段程序发生了什么变化吧(如图1.4-4),也许这里暗藏了什么东西。
数据驱动(011).jpg
图1.4-4  两段程序比较
        从图1.4-4中我们看出了什么?循环体的内容发生了变化。Code 1循环体中的代码只有两句代码,而Code 2的却有四句代码。P10的赋值方式也发生了变化,不过此处我们不必关心,因为我们更关心的是关乎程序结构上的差异,具体的语句变化将被证明那只是一个次要差异,两句代码与四句代码的不同正表示的是一种编程策略的变化。

        Code 1的两句代码是一种什么策略呢?仔细观察我们不难发现它其实只是一个半周期循环,相应的,而Code 2的代码策略却是一个全周期的循环。正是这种编程策略的不同,所以造成了前者只有两句代码,而后者却有四句代码。这个规律也许是我们很容易在一不经意中就不在意的规律,也就是前面没有说破的那个潜在规律。也许这个规律看起来其貌不扬,但是却正是这个规律将成为我们如何实现问题1所示方波的关键。

举报

yyy71cj

2014-11-30 09:08:46
本帖最后由 yyy71cj 于 2014-11-30 09:12 编辑

1.5两支波的实现1.5.1 问题与分析
        在上节中我们实现了问题1中的第一支波,这个很容易。但是容易的事情结束了,问题1中实际上有四支波,我们终于要正视它们了。
        很显然解决这个问题的难点就在于如何同时同步显示这四支波。
        通常,悍然杀入问题的垓心是要吃苦头的,我们不能爱逞匹夫之勇。但是我们的迂回策略在外围已经做了充分的准备,我们把容易的事情都做了,可是我们还是没有切入到要点,甚至获得的启示也很平常。
        白做的阴霾再次笼罩着我们的思维。我们是不是再没有简化的办法了呢?如果没有简化的办法,我们前面的准备工作注定失去意义。但是幸运的是,我们还有简化问题的策略,而且正因为这个策略,才把我们前面的工作与未来的工作联系到了一起。
        为了简化思路,我们依旧没必要一下子把四支波都做出来,我们还能迂回,我们可以尝试着先来做问题1-3,即同时同步实现第一支波和第二支波(如图1.5-1)。
数据驱动(012).jpg
图1.5-1  频率倍减的两支波

        在逻辑上,往往是一与多难度差异很大,而二与多的难度系数则是相同的,我们这里的情况正是这样的。所以我们这里从一支波的实现到两支波的实现将是为解决问题而跨出的最有战略意义的实质性的一大步。尽管二与多的难度系数相同,但是二与多的工作量不同,这就是我们为什么要把两支波的实现单独列出来的原因。至少问题1-3中的需求看起来比问题1要简单些。
        实现波,我们有一个致胜的法宝,那就是循环。很显然,两支波如果用两个循环,那么同步的问题就无法解决,所以我们只能使用一个循环。
        为了能通过循环来解决问题,我们必须要找到两支波能同时用一种格式的循环体来实现的途径。这里,如何安排延时就成了我们解决问题的关键。我们先需要分析一下这两支波的延时规律(如图1.5-2)。
数据驱动(013).jpg
图1.5-2  频率倍减的两支波
        如图1.5-2所示,第一支波一个周期只经历了1和2两个阶段,而第二支波则经历了1、2、3、4四个阶段,正好是第一支波的两倍,只是在不同的阶段,第一支波与第二支波所呈现的电平状态不一定一样,如阶段2中第一支波为高电平,而第二支波则正好相反。这种倍数关系对我们解决问题非常有利,就如数学里的求最小公倍数一样,我们只要采用循环体扩周期法就能很容易地解决我们所要解决的问题,也就是我们对循环体采用了求最小公倍数的方法,使用那个对阶段要求最多的循环体来作为我们公共的循环体。


举报

yyy71cj

2014-12-2 18:56:39
本帖最后由 yyy71cj 于 2014-12-2 18:58 编辑

1.5两支波的实现1.5.1 问题与分析
        在上节中我们实现了问题1中的第一支波,这个很容易。但是容易的事情结束了,问题1中实际上有四支波,我们终于要正视它们了。
        很显然解决这个问题的难点就在于如何同时同步显示这四支波。
        通常,悍然杀入问题的垓心是要吃苦头的,我们不能爱逞匹夫之勇。但是我们的迂回策略在外围已经做了充分的准备,我们把容易的事情都做了,可是我们还是没有切入到要点,甚至获得的启示也很平常。
        白做的阴霾再次笼罩着我们的思维。我们是不是再没有简化的办法了呢?如果没有简化的办法,我们前面的准备工作注定失去意义。但是幸运的是,我们还有简化问题的策略,而且正因为这个策略,才把我们前面的工作与未来的工作联系到了一起。
        为了简化思路,我们依旧没必要一下子把四支波都做出来,我们还能迂回,我们可以尝试着先来做问题1-3,即同时同步实现第一支波和第二支波(如图1.5-1)。

数据驱动(013).jpg
图1.5-1  频率倍减的两支波

        在逻辑上,往往是一与多难度差异很大,而二与多的难度系数则是相同的,我们这里的情况正是这样的。所以我们这里从一支波的实现到两支波的实现将是为解决问题而跨出的最有战略意义的实质性的一大步。尽管二与多的难度系数相同,但是二与多的工作量不同,这就是我们为什么要把两支波的实现单独列出来的原因。至少问题1-3中的需求看起来比问题1要简单些。
        实现波,我们有一个致胜的法宝,那就是循环。很显然,两支波如果用两个循环,那么同步的问题就无法解决,所以我们只能使用一个循环。
        为了能通过循环来解决问题,我们必须要找到两支波能同时用一种格式的循环体来实现的途径。这里,如何安排延时就成了我们解决问题的关键。我们先需要分析一下这两支波的延时规律(如图1.5-2)。

数据驱动(014).jpg
图1.5-2  频率倍减的两支波

        如图1.5-2所示,第一支波一个周期只经历了1和2两个阶段,而第二支波则经历了1、2、3、4四个阶段,正好是第一支波的两倍,只是在不同的阶段,第一支波与第二支波所呈现的电平状态不一定一样,如阶段2中第一支波为高电平,而第二支波则正好相反。这种倍数关系对我们解决问题非常有利,就如数学里的求最小公倍数一样,我们只要采用循环体扩周期法就能很容易地解决我们所要解决的问题,也就是我们对循环体采用了求最小公倍数的方法,使用那个对阶段要求最多的循环体来作为我们公共的循环体。



举报

更多回帖

×
20
完善资料,
赚取积分