背景

  之前在学习 Zigbee 时,曾发表了一篇博文嵌入式项目代码结构的分层——HAL、FML、APL,其中谈到了我在编写的一些经验总结,并在之后的一篇博文中应用了这种分层思想(Zigbee之旅(十):基于CC2430的温度监测系统)。之后呢,则一直被课业和学生工作所围困,很少有时间继续搞嵌入式方面的学习了。

  呵呵,不过最近机会来了,那就是我们大三下所开设的光电系统课程设计。课设要求同班级的4-5人组成一个小队,从老师提供的几个题目中选择一个进行开发。我们小组选择了“光电心率探测仪”,即制作出一个能够测量实时心率的电子设备。整个课设的流程可以概括为:方案论证 -> 电路板的设计与制作 -> 软件编程 -> 现场演示及答辩。现在我们小组已经完成了电路板的原理图和 PCB 图的设计,并交付厂家制作,下周一可以拿到实物了。在去广埠屯的电子市场把需要的电子元器件购买完毕之后,本周也就闲下来了,于是我开始鼓捣课设的软件开发方面,然后想到:何不把之前所总结的“分层思想”运用到这次的嵌入式编程中呢?对,就这么干!但是在之前我已经有一篇运用的例子(基于 CC2430 的温度监测系统,见上),如果还是完全和以前一样照搬的话,感觉只是换了汤没有换罐子,这篇博文的价值也就几乎为零了~

  因此本次对“分层思想”的运用,我觉得需要挖掘出一点点新的东西出来,所以就有了下面的“目的”一节:

目的

  如果说博文Zigbee之旅(十):基于CC2430的温度监测系统更多的只是 开发结果 的静态展示的话,本篇博文则更强调分层思想的具体 运用过程,读者朋友将会收获:

    如何在一个嵌入式项目中运用逐步运用分层思想
    如何绘制分层结构图
    如何在分层结构图的指导下,规划代码的构建顺序

实战

   光电系统课设包含硬件、软件的开发,但是本篇博文的仅仅讨论软件开发部分,即一块硬件连通完好的板子已经摆在你的面前了,现在就靠你所写的 code 让它 run 起来~

  OK,首先我们给出嵌入式软件开发的整体流程图,然后再一步步进行下去:

兄弟们,有活干了!
需求分析的重要性就不多说了,想象一下,若一个系统运行正常,但却不符合设计需求是多大的灾难!!!
大多数情况下,需求的实现者并不是需求的制定者。因此,开发人员必须反复与用户交流,不断完善并最终确定需求。
了解电路版上的硬件资源,以及MCU所提供的功能,据此综合考虑,最终设计出满足用户需求的项目解决方案。
▊ 重点!在下面的论述中,我们会一步步分析,如何分层,如何确定每一层的不同模块,如何建立层与层之间的联系。
根据解决方案,设计程序流程图,通常包括主程序流程图,子程序流程图,中断服务程序流程图。
▊ 终于可以开始着手编程了~首先通过程序结构图来规划代码的构建顺序(这一点在后面的叙述中会详细展开),然后按照既定的顺序迭代式地完成开发过程。
虽然在上面的迭代式开发中,每一阶段都进行了单元测试,但是最终的总体调试是必不可少的。最重要的是,要确保该项目符合最初的需求。如果出现运行错误,修改一下代码应该就可以解决;如果错误来自不正确的需求分析,OMG,测试一万遍也解决不了问题......
公司做产品不是做完了就了事儿,通常会为客户提供后期维护的服务。
当然,究竟能扛多少 years 同时取决于硬件设计与软件设计的好坏~

注:1. 带标记 ▊ 的为本篇博文的着重论述点。
  2. 流程图中并没有出现“总体设计”和“概要设计”,而是把它们细化成了一些具体的步骤。
  3. 以上的图文布局,本来是打算采用 table ,但是遇到了一个问题放弃了,于是在 div 中嵌套 img 和 span,然后搅合
点css 实现的,可惜在FireFox下无法正常显示,555,不是专业的 web 开发人员,这浏览器之间的兼容性还真是 eggache 啊~


1. 需求分析

  课设的需求分析比较简单,一般都是由老师给出一条条的项目要求~下面给出我们老师原版文档内容:

      [光电心率测量仪] 

  利用光电方法测量人体心率,并通过显示器显示出来,具体要求如下:

     采用51 系列单片机
     要求制作光电测量头
     用 LED数码管显示心率
     具有相关的控制键
    具有声音提示功能
     外接直流供电

  可以看出,以上包括了硬件开发、软件开发的需求。但由于本篇博文的定位仅涉及软件,所以需要转化一下,滤去硬件部分的需求,如下:

    √ 利用光电探测电路的输出脉冲电平,计算出两次心跳之间的时间间隔,并算出实时心率
    √ 将实时心率同步显示在数码管上,同时通过串口发送到PC中
    √ 若心率过高,蜂鸣器发短音报警;若心率过低,蜂鸣器发长音报警
    √ 通过按键可显示心率最高值、最低值、平均值

2. 熟悉硬件资源,设计解决方案

  个人认为如果对硬件知识一窍不通的话,做嵌入式软件开发无异于痴人说梦。下面,我们给出本小组设计的“光电心率探测仪”的硬件原理图和 PCB 图:

  [原理图]

  分析:整个电路图可分为MCU、晶振、光电心率探测电路、数码管显示电路、按键电路、JTAG调试接口电路、蜂鸣器电路、串口转USB电路以及电源电路。我想接触过嵌入式开发的童鞋,一看到原理图就啥都懂了,不多赘述~

  [PCB 图]

  分析:了解一个电路的原理当然是要看原理图了,但 PCB 图更接近最终产品的外观。我不多说,读者朋友能够找到原理图中的每个模块在上图中相对应的位置吧?呵呵~

  (注:若需要以上两图的清晰版本,请点此下载)

  在熟悉了电路板上的功能模块之后,我们开始着手项目的解决方案的设计。这里不是本文重点,直接给出,一时看不懂没关系:

  人体组织对红光半透明,而血液对之不透明,因此当手指的血流量随心脏跳动而改变时,透过指尖的红光的光强会发生改变。我们可以用红色发光二极管 LED照射手指,然后在手指后方放置一个光敏三极管。当透过手指的光强发生波动时,光明二极管的阻值会相应变化,因此其流经的电流也会发生变化。 

  然后,我们可以对此电流信号进行一系列处理(放大、低通滤波、整流),最终得到方波信号,其正脉冲即表示着心脏的跳动。

  接下来,我们将此方波信号接到单片机的外部中断口(INT0)上,因此每当一个正脉冲发生时,都会触发单片机的外部中断。我们可以编写外部中断服务子程序:

  当外部中断第一次发生时,启动 16 位定时器/计数器 T0,设置为计数器模式;此时 T0 就会不断加计数,溢出时会触发计数溢出中断。我们用变量 k(初值为0)将溢出中断的次数记录下来(每溢出一次,k=k+1)。在下一个正脉冲到来时,用 k的值计算出两次脉冲之间的间隔时间t:

  其中nt为计数器溢出值,n0为计数初值,T 为计数脉冲的周期。接下来,我们就可以利用此间隔时间,计算出实时的心率值N:

  然后,我们将心率值 N 的百位、十位、个位和十分位提取出来,分别显示到 4 个数码管上。接下来完成声音提示功能:当N < 40 时,控制蜂鸣器发出长音报警;当N > 120时,控制蜂鸣器发出短音报警。 

  当然,还需要一定的按键,暂配置4个按键:1 个按键用于硬件复位,其他三个按键可通过编程来实现需要的功能。如:1 个按键用于显示心率最大值,1 个按键用于显示心率最小值,剩下1个按键用于显示心率平均值。

3. 规划程序结构

  OK,现在才进入真正的重点,下面我们来详细介绍,在完成一个项目的方案论证之后,如何进行代码结构的安排,如何将代码规划为HAL(硬件抽象层)、FML(功能模块层)、APL(应用程序层)。

  首先我们再来回顾一下之前博文中介绍的分层思想:

  HAL:实现对特定 MCU 内资源 (如定时器、ADC、中断、I/O 等) 的通用驱动配置;隐藏具体的SFR操作细节,为上层提供简单清晰的调用接口。

  FML:在 HAL 的支持下,实现项目中所使用的电路板板上资源的通用驱动配置;隐藏具体的模块操作细节,并为上层提供简单清晰的调用接口。

  APL:在 FML 的支持下,实现产品的最终功能,满足客户需求。

  可以看出,HAL层 仅与特定的 MCU 芯片有关,与 MCU 周围的硬件环境、或应用在哪个具体场景都无关;FML 层仅与当前特定的硬件资源有关,与;而 APL 层仅与特定的项目情景有关,而与采用哪种硬件设备来实现无关。

  OK,下面我们采用自顶向下的方法来构建三层结构。首先来考虑 APL 层,它是和项目的需求分析密切相关的。我们仔细琢磨项目的需求分析,就可以得出 APL 层所必须要实现的模块:

  这就是 APL 层的四大模块,而且为了配合迭代式的开发方式,即下一模块的实现建立在上一模块的基础之上,我们把四大模块进行了如上的从左到右的排序。

  接下来,我们在 APL 的基础之上构建 FML 层。“检测实时心率,并显示在数码管上”,这一模块需要数码管、外部中断、和定时器的配合;“将实时心率发送到PC ”,需要“串口通讯”的支持;“心律异常报警”,需要“蜂鸣器”的支持;“按键显示心率的平均值、最大值、最小值”需要“数码管显示”和“按键”的支持。最终便得到了如下的层次结构图:

  最后,我们在 FML 层的基础上开始构建 HAL 层,即考虑如何使用 MCU 内的资源实现 FML 层。本项目使用 C8051F310 作为 MCU,它提供了丰富的片上资源,如 "I/O"、"Clock"、"UART"、"Interrupt"、"Comparators"、"SMBus"等(具体见C8051F310 的 datasheet )。但是不同的项目肯定只用到了其中的一部分。结合硬件原理图,我们分析出:“数码管显示”模块需要 "Clock" 和 "I/O" 的支持;“外部中断”模块需要 "Clock"、"I/O" 和 "Interrupt" 的支持;“定时器”模块需要 "Clock"、"I/O" 和 "Timer" 的支持;“按键”模块需要 "Clock" 和 "I/O" 的支持;“串口通讯”模块需要 "Clock"、"I/O" 和 "UART" 的支持;而“蜂鸣器”模块,则需要 "Clock"、"I/O" 的支持。最终便得出了如下的层次结构图:

  我们将上面两张图拼起来,然后再顺时针旋转90度,便形成了如下的一目了然的三层结构图了!  

4. 绘制程序流程图

  非重点,也是直接给出不作解释:

5. 规划代码构建顺序

  OK,现在代码结构图有了,程序流程图也有了,那么如何来着手代码构建呢?答案是——增量集成,下面摘自《代码大全》第29章对“增量集成”的介绍(P692):

  因此我采用的方法是,以 APL 层中的每一个模块作为开发单元进行迭代式开发。如下图所示,我们从“检测实时心率,并显示在数码管上”开始,然后在 FML 层中寻找与之相关的模块“数码管显示”、“外部中断”、“定时器”,接着在 HAL 层中寻找与 FML 层相关的模块 "Clock"、"I/O"、"Interrupt" 和 "Time"(见如下带有加粗黑框的部分)。这些模块组成一个“树枝”,也就是我们的第一次迭代式开发所涉及到的所有部分。在具体的每个“树枝”的开发中,有三种常见的方法,即:“自顶向下式”、“自底向上式”以及上面两种方法的结合。在此我们采用自顶向下,即首先编写 HAL 层的相关驱动,然后在此基础上构建 FML 层的各模块,最后在此基础上使用代码替换掉 APL 层中的伪代码。

  依此类推,在完成了第一个“树枝”之后,进行测试。测试完毕再在上一个“树枝”之上进行下一个“树枝”的生长,再测试,再进行下一个“树枝”的生长......长啊长,长啊长,最终长成一棵参天大树啦!

PS

  1)事后翻阅《代码大全2》,在“集成”一章中发现了对“增量集成”的策略的详细论述,仔细看了下,我的方法应该是“功能导向的集成”和“T型集成”的混合体吧!在此顺便强烈推荐一下《代码大全2》,它对嵌入式编程也同样有很大的启发作用,是可以一辈子使用的经典巨著!

  2)此处仅仅介绍“增量集成”的编码思想,下一篇博文中笔者会展示出全部的源代码,并融入“伪代码编程”的想法,向读者展示使用伪代码编程的好处~

7. 总体测试

  每一次的小测试并不能保证产品在整体上没有错误,所以总体测试是必须滴!板子没到,总体测试就先空着,等以后有时间再补上吧~

优点

  在博文嵌入式项目代码结构的分层——HAL、FML、APL中,笔者只是比较空泛地谈论了分层思想所带来的好处,并没有结合具体项目,这里是个好机会:

1. 可重用性高

  首先我们假设一个情景,假如说本次课程设计中,班上的另外一个小组也在做一个题目(不一定也是“光电心率探测仪”),但是他们中很多同学都要考 TOEFL/GRE ,时间很紧无法把课设完整地做下来,因此想借鉴一下我的代码,“重用”一下(说的不好听就是抄袭一下啦,这在大学比较普遍但有时也可以理解,呵呵)。好,既然提到“重用”,那我告诉他们我的代码有三层:HAL、FML、APL,你到底想“重用”哪一层呢?呵呵,不妨使用穷举法,2的3次方=8,下面就分8种情况分别讨论吧!

  【HAL + FML + APL】

  如果这个小组完全没有时间做课设的话,那就只能三层代码原版照抄啦,条件是所有东西都一样:同样的MCU,同样的电路板,同样的课设选题。

  【HAL + FML           】

  如果这个小组使用一样的 MCU,一样的电路板,但是想做不同的题目(即在应用层上做一些改变),那就可以直接使用我已近编好的 HAL 层和 FML 层了,我只需要写一个简单的使用文档提供给他们就行了。

  【HAL +              APL】

  如果这个小组选题和我们相同,使用同样的MCU,但是他们自己来做硬件电路板的,那就可以考虑移植 HAL 层和 APL 层的代码了,然后重写 FML 层以适应他们自己的电路板。

  【            FML + APL】

  如果该小组使用不同的MCU,同样的电路板,同样的选题的话......额......这种情况也不太容易出现吧,毙掉~

  【HAL                         】

  如果该小组只是和我们使用了同样的 MCU,但电路板和选题都不一样的话,那么可以使用我的 HAL 层代码就行,我只需要提供给他们一个 HAL 层说明文档就行。

  【           FML                 】

  如果该小组的使用不同的MCU,不同的选题,但是电路板一样的话......饿......等等,这种情况好像不太现实......好吧,打住......

  【                                  APL】

  如果这个小组使用不同的MCU,不同的电路板,那么可以考虑只移植 APL 层代买,其他两层代码自己重写写~

  【无                              】

  这种情况比较有意思,既然是“无”,那也就是说完全不参考我的code,那么这还叫“重用”吗?呵呵,我想这里的“无”并不是真正意义上的“无”,即使所有的细节都隐入了黑夜,但是分层的思想也是可以借鉴一下的嘛!

2. 可扩展性强

  如果需要在 APL层 扩展其他的功能,那么只需要在 FML 层中添加一个功能模块即可,非常的方便!

3. 降低了复杂度

  《代码大全2》中的一个很重要的观点就是,软件的首要技术使命:管理复杂度。我们的大脑可不比电脑,没有谁的大脑能容得下一个现代的计算机程序的所有代码。分层思想可以使嵌入式项目代码以清晰的结构组织起来,让程序员在同一时间内只关注与一件事情(比如说现在只编写 HAL 层代码,下一个时间段仅考虑 FML 层)。这样一来减少脑力负担,减少出错的几率,进一步提高程序员的编码效率。

4. 易于维护

  一个代码结构清晰,注释完善的源程序,维护起来当然是如鱼得水了!

5. 便于规划开发顺序

  在层次结构图的指导下,很容易制定出符合增量实现型的代码开发计划。

结语

  终于写完了正文部分,小累,写一些随感结尾吧~

  (1)学会活用以往的经验,不管是 web 开发还是 嵌入式开发 等等,它们都属于软件开发,为什么不把一个开发领域的经典思想运用到另一个中呢?或者进一步,

  (2)再一次推荐一下《代码大全2》这本经典巨著,千万不要被它的名字所迷惑,以为是各种语言的代码示例(笔者大二的时候就是被骗了,泪奔~)。还有,看代码大全最好结合实际的项目开发,一边学习一边实践,会有更大的收获~

  (3)对于在校大学生来说,一定要认真对待课程设计,即使你的学校再2,老师再挫,也最好不要打酱油。最终对自己负责的还是你自己!

  (4)写完日志,在博客园中看到了一篇《爱丽丝的发丝——<爱丽丝惊魂记:疯狂再临>制作点滴》,真的被震撼了。博主 Milo 扎实的数学功底、严谨的态度、创新的思维,更重要的是他面对工作时所表现出的一种“良质”,在这个浮躁的社会中真的值得学习!后来看他以前的日志,才发现 Milo 是香港的同胞,从高中开始就兼职游戏编程,之后进入香港某大学完成了本科与研究生的学业。我想,Milo 的强大,是他对游戏编程的兴趣,加上在一流大学中所接受的训练,这两者结合的结果。哎,想想自己,大二之前(不算大二)完全被应试教育所围困,没有兴趣只有分数,也看不到外面的世界。到大二我才开始慢慢转变,到现在终于把十几年的应试价值观推倒,有了自己的一些思考。

  呵呵,Milo 的强大只能膜拜,不过不是有句话叫做 it's never to say too late 嘛!

  Diving And Fighting!