电子说
面向AMetal框架与接口的编程(上),对AMetal框架进行了详细介绍,通过阅读这本书,你可以学到高度复用的软件设计原则和面向接口编程的开发思想,聚焦自己的“核心域”,改变自己的编程思维,实现企业和个人的共同进步。经周立功教授授权,即日起,致远电子公众号将对该书内容进行连载,愿共勉之。
第四章为面向接口的编程,本文内容包括:4.3 LED 数码管。
4.3 LED 数码管
>>> 4.3.1 静态显示
如图4.10 所示是由2 个共阳极的LN3161BS 组成的LED 数码管电路,R1~R8 为限流电阻,c1 和c2在内部并联连接。如果将段选端a~dp 与位选端com0、com1 连接到AM824-Core 的PIO0_8~PIO0_15 与PIO0_17、PIO0_23,则通过程序即可控制笔段的亮灭。由于数码管的8 个段选端全部都要经过com 口才能得到供电,因此需要增加三极管提高com 口的驱动电流,以弥补LPC824 GPIO 驱动电流的不足。当com 为低电平时三极管导通,则数码管的c1、c2 为高电平,即选通数码管。此时只要数码管的任一段选端为低电平,则点亮数码管相应的笔段。
图4.10 LED 显示器电路图
MiniPort-View 数码管模块通过MiniPort B(排母)与AM824-core 相连,同时将其余不使用的I/O 通过MiniPort A(排针)引出,实现模块的横向堆叠,其对应AM824-Core 的MiniPort 接口J4的功能定义详见图4.11。
图4.11 数码管模块实物与接口定义
“日”形数字显示除了能够显示10 进制数字0~9,有时也用于显示16 进制字母AbCdEF 或其它一些非常简单的符号。按照二进制的计算方法,8 段显示有256 种组合,去掉“点(dp)”的显示,其笔段的组合为128 种(27),而数字0~9 只有10 个符号,因此要想得到我们希望的显示符就必须对显示段进行编码。显然,如果要想点亮数码管的某一个笔段,则只需将对应的笔段置0就可以了。即输出低电平至com0 端,同时输出低电平至b、c 段,点亮LED 得到字符“1”。由此可见,按照数字的笔画排列,则很容易得到10 个数字0~9 共10 个显示字符,七段共阳数码管10 个数字段码表详见表4.2。
表4.2 七段共阳极数码管段码表
如果以8 位数值表示段码,当其相应位为0 时,则表示对应的段点亮。bit7~0 分别与dp~a 对应,假设bit0 为0,即点亮a。为了方便访问,不妨将段码存放到一个数组中。即:
AMetal 软件包提供了熄灭所有数码管的板级初始化函数、段码传送函数、位码传送函数和数字显示扫描函数,其函数接口详见程序清单4.10。
程序清单4.10 digitron0.h 接口
其中,code 为待显示数字0~9 所对应的段码,pos 为com0 或com1 对应的数字下标(0~1),num 为待显示的数字0~9。当后续调用这些函数时,则只需要“#include "digitron0.h"”就可以了。比如,在com0 数码管上显示数字1,详见程序清单4.11。
程序清单4.11 静态显示数字1 范例程序
如果让单个数码管循环显示0~9,且循环的时间为1s,显然显示时间也是1s,那么这就是一个简单的秒计数器,详见程序清单4.12。
程序清单4.12 秒计数器范例程序
当你看到程序清单4.12(9)中的“i = (i + 1) % 10;”代码时,是否有一种似曾相识的感觉呢?这行代码是从LED 流水灯实验中提炼出来的。如果需要倒计时呢?则将其修改为“i= ((i – 1) + 10) % 10;”。如果要从9 开始倒计数呢?那就将i 的初始值修改为9。至此已经实现了0~9 的循环显示,能否循环显示0~99 呢?这就是下面将要介绍的数码管动态扫描显示。
>>> 4.3.2 动态显示
如果要显示多位数字,则需将多个数码管并接在一起使用。此时将会出现一大堆段选端的问题,比如,两位数码管需要2×8=16 个段选信号,而LPC824 一共才16 个I/O,无法满足需求,同时管脚使用越多,连线也会变得越复杂。所以为避免使用过多的管脚而造成资源浪费和连线复杂,人们发明了一种动态扫描方式来实现多个数码管的显示。
由于数码管的段码是连接在一起的,那么同一时刻两个数码管的段码必然是相同的,如果简单地使2 个公共端(com)均有效来实现2 个数码管的显示,那么必然都会显示相同的内容。怎么办?分时显示,即一段时间数码管0 正常显示(com0 有效,com1 无效,段码为数码管0 需要显示的图形),另外一段时间数码管1 正常显示(com1 有效,com0 无效,段码为数码管1 需要显示的图形)。如要显示一个数值12?即在com0 显示1,在com1 管显示2,详见程序清单4.13。
程序清单4.13 显示数值12 范例程序
虽然在实际的操作过程中数字是轮流显示的,但只要轮流操作的速度达到一定的范围,那么在人眼看起来就能达到和整体显示的效果一样,就像我们经常看的电影技术一样。
再细心观察一下实验现象可以发现,虽然显示的数字是12,但是数码管显示的1 和2 都会有另外一个数字的影子。com0 显示的是1,但也能看到2 的影子。
digitron_disp_num()就是digitron_com_sel ()和digitron_segcode_set()的一个简单组合。其显示过程是先传送位码、后传送段码,于是在传送位码和传送段码之间就产生了时间间隙。当新的com 端有效时,仍然还在使用此前的段码,所以出现了短暂的错误现象。怎么办?可以在这段时间内熄灭所有的数码管,避免错误显示。即:
那如何循环显示0 ~ 59 呢?即将要显示的数值加1,详见程序清单4.14。
程序清单4.14 0~59 秒计数器范例程序(1)
程序还可以继续优化吗?现在的问题是,为了显示一个数据,即便数据没有改变,也必须动态扫描数码管,否则无法显示。首先将待显示的数据存放到缓冲区(存储单元),然后每隔一段时间从缓冲区读取待显示的数据。即:
读缓冲区的数据实现动态扫描的函数详见程序清单4.15。
程序清单4.15 动态扫描显示函数
由此可见,缓冲区的段码就是当前要显示的数据,当再次切换时,则继续调用该函数,则在下一个位显示数据,以此类推。由于位选变量pos 每次都是在上一次显示的位的基础上变化的,因此必须将其声明为静态变量。显然,只要将显示的内容存放到缓冲区中,同时保证以一定的时间间隔(各个数码管显示后的延时)调用该函数,即可实现动态扫描。
为了便于复用数码管程序,则将上述代码全部存放到digitron1.c 文件,函数声明放到digitron1.h 文件。其接口如下:
(1) digitron_init():初始化相关引脚;
(2) digitron_disp_scan():动态扫描函数。
由于要将待显示内容存放到缓冲区,同时还可能访问段码表,因此不允许调用者直接操作其中的变量、数组等,基于此增加3 个接口函数,其分别为传送段码到显示缓冲区,传送数字0~9 到显示缓冲区与获取待显示数字的段码,其相应的代码详见程序清单4.16。
程序清单4.16 操作缓冲区和段码表接口函数
如果要在com0 显示“3.”,则可以直接这样使用:
最后将这些接口全部声明在程序清单4.17 所示的digitron1.h 文件中,相关的实现代码详见“深入浅出AMetal——动态显示”介绍的digitron1.c 文件。
程序清单4.17 digitron1.h 文件内容
注意,在digitron1.h 接口中,已经使用digitron_disp_num_set ()和digitron_disp_code_set()替代digitron_disp_num()和digitron_disp_code(),程序清单4.18 就是通过迭代后的循环显示0~59 秒计数器范例程序。
程序清单4.18 0~59 秒计数器范例程序(2)
>>> 4.3.3 闪烁处理
在显示过程中,有时为了修改某位数码管的值,需要对数码管进行闪烁处理。在温度采集场合,当温度超过一定的值后,可以将显示的温度值做全闪处理,以引起观察者注意。
实际上,只要让数码管显示一段时间,熄灭一段时间,就产生了闪烁的效果,显然只要直接操作缓冲区就可以了。假设每秒闪烁2 次,在个位不断闪烁,详见程序清单4.19。
程序清单4.19 实现秒计数器个位闪烁(1)
在程序中,1s 闪烁2 次,每次闪烁占用500ms,即显示250ms,熄灭250ms。每秒结束后,秒计数器加1,显然用同样的方法也可以使秒计数器的十位闪烁,由此可见,实现闪烁仅需交替传送正常显示的段码和熄灭显示的段码即可。由于熄灭显示的段码非常特殊,固定为0xFF。因此,只要在合适的时间传送相应段码即可,段码传送函数digitron_segcode_set()是在digitron_disp_scan()函数中调用的。通过修改该函数,使其在一段时间内传送缓冲区中正常显示的段码,一段时间内传送熄灭显示的段码0xFF,也能实现闪烁,详见程序清单4.20。
程序清单4.20 带闪烁功能的digitron_disp_scan()函数(1)
程序将时间分隔为500ms 的时间片,当需要闪烁时,显示250ms,熄灭250ms,每隔5ms 调用一次digitron_disp_scan(),cnt 循环计数+1。如何让秒计数器十位闪烁呢?直接将if(pos == 1)修改为if(pos == 0)。
显然,数码管的闪烁状态和显示状态可以用一个标志位来表示,即0 表示显示,1 表示闪烁,bit0 和bit1 分别表示com0 和com1 的状态。定义一个全局变量如下:
如果bit1 闪烁,则将bit1 的初始值设置为1。即:
这样一来,即可需要根据该变量的值来获取需要闪烁的位,详见程序清单4.21。显然,只要将g_blink_flag 对应的位置1 就能实现闪烁,否则将其对应位清0。
程序清单4.21 带闪烁功能的digitron_disp_scan()函数(2)
为了提高程序的可阅读性,则进一步优化两个if-else 语句,详见程序清单4.22。
程序清单4.22 带闪烁功能的digitron_disp_scan()函数(3)
由于g_blink_flag 变量定义在实现代码中,不能直接将该变量提供给用户修改,因此必须提供一个接口函数用于设定闪烁位,其相应的代码详见程序清单4.23。
程序清单4.23 digitron_disp_blink_set()函数
digitron_disp_blink_set()的pos 用于指定设置闪烁属性的数码管位置,isblink 设置闪烁属性,值为AM_TRUE 表示闪烁,AM_FALSE 表示不需要闪烁。am_bool_t 是AMetal 在am_types.h 文件中自定义的类型,该类型数据的值只可能为AM_TRUE(真)或AM_FALSE(假),设定com0 闪烁的方法如下:
设定com0 停止闪烁的方法如下:
添加digitron_disp_blink_set()的接口函数,详见程序清单4.24 所示的digitron1.h。
程序清单4.24 digitron1.h 文件内容
有了该接口函数后,实现闪烁就很容易了,程序清单4.25 实现了秒计数器个位闪烁。
程序清单4.25 实现秒计数器个位闪烁(2)
由此可见,与程序清单4.18 相比,仅增加了一行代码就实现了闪烁功能,显然接口的设计非常重要。
全部0条评论
快来发表一下你的评论吧 !