找回密码
 注册
关于网站域名变更的通知
查看: 500|回复: 1
打印 上一主题 下一主题

单片机程序设计中的“分层思想”

[复制链接]

该用户从未签到

跳转到指定楼层
1#
发表于 2019-9-3 16:42 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式

EDA365欢迎您登录!

您需要 登录 才可以下载或查看,没有帐号?注册

x
  分层的思想,并不是什么神秘的东西,事实上很多做项目的工程师本身自己也会在用。看了不少帖子都发现没有提及这个东西,然而分层结构确是很有用的东西,参透后会有一种恍然大悟的感觉。如果说我不懂LCD怎么驱动,那好办,看一下datasheet,参考一下别人的程序,很快就可以做出来。但是如果不懂程序设计的思想的话,会给你做项目的过程中带来很多很多的困惑。
5 ~1 E* O; ?# {( s, Q  参考了市面上各种各样的嵌入式书籍,MCS-51,AVR,ARM等都有看过,但是没有发现有哪本是介绍设计思想的,就算有也是凤毛麟角。写程序不难,但是程序怎么样才能写的好,写的快,那是需要点经验积累的。结构化模块化的程序设计的思想,使最基本的要求。然而这么将这个抽象的概念运用到工程实践当中恩?那需要在做项目的过程中经历磨难,将一些东西总结出来,抽象升华为理论,对经验的积累和技术的传播都大有裨益。所以在下出来献丑一下,总结一些东西。& l3 y3 l8 t  ^" j: Y+ i2 b7 N
  有两个设计思想是非常重要的。9 E, }; }$ W" N2 _
  一个就是“时间片轮的设计思想”,这个对实际中解决多任务问题非常有用,通常可以用这个东西来判断一个人是单片机学习者,还是一个单片机工程师。这个必须掌握。由于网上介绍这个的帖子也不少,所以这里就不多说了。
: A( e' y5 M& v3 x" {- H: c' c9 J1 e  第二个就是“分层屏蔽的设计思想”。下面用扫描键盘程序例子作为引子,引出今天说的东西。
  \# K% m. o+ ^0 G/ F+ R  问题的提出
8 h- Z  a4 O9 h  单片机学习板一般为了简单起见,将按键分配的很好,例如整个4*4的键盘矩阵分配到P1口上面,8条控制线,刚好。这样的话程序也非常好写。只需要简单的; I$ N7 |: Z2 J# Z" @) w

! B/ o0 w* K1 H" r+ Q
  1.  KEY_DAT = P1;
复制代码

( L7 a) T6 I4 b1 R  端口的数据就读进来了。
& F$ s, ~! J" L" }  Y" s  诚然,现实中没有这么好的事情。在实际的项目应用当中,单片机引脚的复用相当厉害,这跟那些所谓的单片机学习板就有很大的差别了。8 {8 z* [- \4 Z" }2 r. N9 J  d5 H
  另外一个原因,一般设计来说,是“软件配合硬件”的设计流程,简单点说就是,先确定好硬件原理图,硬件布线,最后才是软件的开发,因为硬件修改起来比较麻烦,相对来说软件修改的时候比较好改。这个就是中国传统的阴阳平衡哲学原理。硬件设计和软件设计本来就是鱼和熊掌的关系,两者不可兼得。方便了硬件设计,很可能给写软件带来很大的麻烦。反过来说,方便了软件设计,硬件设计也会相当的麻烦。如果硬件设计和软件设计同时方便了,那只有两种可能,一是这个设计方案非常简单,二是设计师已经达到了一个非常高的境界。我们不考虑那么多情况,单纯从常用的实际应用的角度来看问题。
5 ]( g- E9 x# F. K7 C  硬件为了布线的方便,很多时候会可能将IO口分配到不同的端口上面,例如上面说的4*4键盘,8根线分别分配到P0 P1 P2 P3上面去了。那么,开发板的那些扫描键盘程序可以去见鬼了。怎么扫按键?我想起了我刚开始学习的时候,分成3段非常相似的程序,一个一个按键的扫描的经历……# C; q- ^! Z; N1 I2 H  l" m
  或许有人不甘心,“那些东西我花了很长时间学习的,也用的好好的,怎么能说一句不用就不用?”虽然有点残忍,但是我还是想说“兄弟,接受现实吧,现实是残酷的……”
/ p4 V5 g8 n( \8 w/ R  不过,人区别于低等动物的差别,是人会创造,在碰到困难的时候会想办法解决,于是我们开始了沉思……5 f, h/ |% q& ?7 n8 i! {; @' `9 b7 ^
  最后我们引入初中数学学的“映射”的概念来解决问题。基本思想就是,将不同端口的按键映射到相同端口上面。
- H1 Z/ z( e! S6 f7 ^( T  这样按键扫描程序就分成3个层次了。# U) I0 [3 d! d  r# E" B/ e
  1)最底层的是硬件层,完成端口扫描,20ms延时消抖,将端口的数据映射到一个KEY_DAT寄存器上面,KEY_DAT作为对上层驱动层的一个接口。  C( Z7 Y6 t' X/ E  u8 B' b* h
  2) 中间的一层是驱动层,驱动层只对KEY_DAT寄存器的数值进行操作。简单点说,我们无论底层的硬件是怎么接线的,在驱动层都不需要关心,只需要关心 KEY_DAT这个寄存器的数值是什么就可以了。这样出来的间接效果就是“屏蔽了底层硬件的差异”,所以驱动层写的程序就可以通用了。
! {) b  S6 _% H+ j9 ~  驱动层的另外一个功能是为了上层提供消息接口。我们用了类似window程序的消息的概念。这里可以提供一些按键消息,例如:按下消息,松开消息,长按键消息,长按键的时候的步进消息,等等。) n: @# x  \) i
  3)应用层。这里就是根据项目的不同分别写按键功能程序,属于最上层的程序。它使用的是驱动层提供的消息接口。在应用层写程序的思想就是,我不管下层是怎么工作的,我只关心按键消息。有按键消息来的时候我就执行功能,没有消息来的时候,我就什么也不做。
" S0 [9 t+ j& Y& u# X4 @  下面用一个简单的常用的例子,说明我们这个设计思想的用法。7 U$ F2 n& K1 e6 u& j
  秒表调整时间的时候,要求按着某个按键不放,时间能连续的向上增加。这个东西很实用,实际的家电中用途很广泛。
4 V% ?. `0 M3 |7 I9 t4 O  在看下面的东西之前,大家可以想一下,这东西难吗?相信大家都会很响亮的回答,“不难!!”,然而我再问:“这东西麻烦吗?”我相信很多人肯定会说“很麻烦!!”这不禁让我想起开始学单片机的时候写这种按键的那程序,乱七八糟的结构。如果不相信的话,可以自己用51写一下哦,那样就更加能体会本文说的分层结构的优越性。
( x3 s# F$ T9 B. e$ m+ W0 Q  项目要求:. q7 P7 m' _, |  F2 _/ B( V$ m: G
  两个按键,分别分配在P10和P20,分别是“加”“减”按键,要求长按键的时候实现连续加和连续减的功能。" v1 `# i! r! G" ~5 b4 E
  实战:) C* G% \% C2 c; ]# X4 Y) n5 \
  假设:
4 {/ V% {5 E) f- |- T  按键上拉,没有按键的时候高电平,有按键的时候低电平,另外,为了突出问题,这里没有将延时消抖的程序写上去,在实际项目中应该加上。C语言函数参数的传递多种多样,这里作为例子,用了最简单的全局变量来传递参数,当然你也可以用unsigned char ReadPort(void)返回一个读键结果,甚至还可以void ReadPort(unsigned char *pt)用一个指针变量传递地址而达到直接修改变量的目的。方法是多种多样的,这个决定于每个人的程序风格。
6 |- Z" \# q1 q1 m+ f  _8 A  1)开始写硬件层程序,完成映射
3 j& V9 U+ J5 Z$ I" b7 r- j' Y2 G: G& v& w8 p
  1.  #define KYE_MIN 0X01
    : o6 g" C8 h" O0 c9 M

  2. " a  ]% }! |/ v- q- e3 s! B7 h* n5 x4 g
  3.   #define KEY_PLUS 0X016 N1 J: m) z/ u- U% v* f

  4. 4 b7 Y; R, x3 T* d9 l
  5.   unsigned char KeyDat;/ ~" [6 n1 N) f
  6. . _. f& c3 F4 |  y/ B
  7.   void ReadPort(void)
    3 \' d7 p! b7 B# Y( q, r6 d
  8. 4 g: R5 a$ o  ]  Q
  9.   {
    9 K0 t0 t/ s# z" c

  10. ' D9 f# v" H0 I* d
  11.   IF (P1 KEY_PLUS == 0 ){; T4 Q4 V8 E$ w8 ]

  12.   q2 V  w3 R: ~2 a
  13.   KeyDat |= 0x01 ;
    & S( P, q( T: V" j" @9 _+ X

  14. * \' E9 ^: {1 t$ L4 L+ o) m* B
  15.   }6 c. i$ V+ }" h6 s7 A

  16. + A: q* J! Y0 U4 U9 q, F" M' N
  17.   if (P2 KEY_MIN == 0 ){
    * G" Y$ w, g6 y' p. ?2 Y9 u( j
  18. / K# f- [" O& g7 U2 B
  19.   KeyDat |= 0x02 ;4 N: ^; w) D: B( T# m- j

  20. : Z- u$ I2 H' a& `7 ?
  21.   }
复制代码

! A6 O) Y: d; N/ O  C语言应该很容易看懂吧?如果KEY_PLUS按下,P10口读到低电平,则P1 KEY_PLUS的结果为0,满足if的条件,进入KeyDat |= 0x01是将KeyDat的bit0置一,也就是说,将KEY_PLUS映射到KeyDat的bit06 K2 }. M/ |# P+ \
  KEY_MIN是同样的道理映射到KeyDat的bit13 P5 V; u% z8 h8 ], [8 O
  如果KeyDat的bit0为1,则说明KEY_PLUS按下,反则亦然。* {* P1 S! {% K" ]$ ~% ^' t7 a
  不需要想的很神秘,映射就是这么一回事。如果还有其他按键的话,用同样办法,将他们全部映射到KeyDat上面。3 c, C, k' M* |. _
  2)驱动层程序编写- a# c2 Y  k  x; p$ G/ h
  如果将KeyDat想象成P1口,那么这个跟学习板那标准的扫描程序不就是一样了吗?对的,这个就是底层映射的目的了。9 X+ {" P. L$ B& ]0 e7 _& L
  3)应用层程序编写
) S' ?$ r) b  C. V" i/ e" [  根据消息" b3 _5 B" U6 @% I: h% K- z' R
  硬件层是必须分离出来,然而驱动层和应用层的要求就不那么严格了,事实上一些简单的项目没有必要将这两层分离开来,根据实际应用灵活应对就可以了。其实这样写程序是很方便移植的,根据板子的不同而适当的修改一下硬件层那个ReadPort函数就完成了,驱动层和应用层很多代码可以不经过修改直接用,很能提高开发效率的。当然这个按键程序会存在一定的问题,特别是遇到常闭按键和点触按键的混合使用的场合。这个留给大家自己去想了,反正问题总是能找到解决办法的,尽管方法有好有坏。
3 m3 u. m- {9 r/ [1 M  结束语+ ]) {# [) ^8 m" e2 g
  以按键为媒介,介绍了程序设计当中的“分层屏蔽” 的思想的原理和应用,按键只是一个例子,其实分层的思想普遍存在着程序设计当中。细心留意一下的话发现其实window,linux,网络的tcp/ip 结构全部都是分层的。这东西不是绣花枕头,而是实际用在工程上面的,只是平时不多见帖子介绍,或者没有人特意这样来总结,又或者是有经验的工程师作为藏在心中的法宝吧,这个就不得而知。
1 j# R9 d. p' u$ X& S$ z2 z: V6 v  F7 c3 h7 A

( Y9 T, B& n% E/ G  q: z% z( c
  • TA的每日心情
    开心
    2023-5-15 15:25
  • 签到天数: 1 天

    [LV.1]初来乍到

    2#
    发表于 2020-4-20 11:00 | 只看该作者
    谢谢。楼主,很详细
    您需要登录后才可以回帖 登录 | 注册

    本版积分规则

    关闭

    推荐内容上一条 /1 下一条

    EDA365公众号

    关于我们|手机版|EDA365电子论坛网 ( 粤ICP备18020198号-1 )

    GMT+8, 2025-7-19 03:52 , Processed in 0.125000 second(s), 24 queries , Gzip On.

    深圳市墨知创新科技有限公司

    地址:深圳市南山区科技生态园2栋A座805 电话:19926409050

    快速回复 返回顶部 返回列表