第八章为深入理解AMetal,本文内容为8.6 通用数码管接口。
8.6 通用数码管接口
>>> 8.6.1 定义接口
1. 接口命名
由于操作的对象是数码管(digitron),因此接口命名以“am_digitron_”作为前缀。数码管最常见的操作是设置数码管的显示内容,提供一个显示字符和字符串的接口,其对应的接口名为:
-
am_digitron_disp_char_at
-
am_digitron_disp_str
当显示字符或字符串时,需要将各个字符解码为对应的段码后,数码管才能正常显示。为此需要提供一个设置解码函数的接口,便于用户根据实际数码管自定义解码函数,然后通过该接口设置到系统中。当需要显示一个字符时,系统首先会使用该解码函数将字符解码为段码。其对应的接口名为:
-
am_digitron_disp_decode_set
在一些应用场合,可能需要显示特殊的图形,此时仅仅有显示字符或字符串的接口是不够的,还需要提供一个直接显示段码的接口,其对应的接口名为:
-
am_digitron_disp_at
此外,作为一个显示器,还需要清除当前数码管显示的所有内容,便于重新设置显示内容,其对应的接口名为:
-
am_digitron_disp_clr
特别地,除设置显示内容相关的操作外,还需要数码管闪烁显示,其对应的接口名为:
-
am_digitron_disp_blink_set
2. 接口参数
通常系统存在多个数码管,比如,同时使用MiniPort-View 和MiniPort-ZLG72128。在一个数码管设备中,又可能包含多个数码管,比如,MiniPort-View 和MiniPort-ZLG72128均包含两个数码管。
为了区分不同的数码管设备,需要为每个数码管设备分配一个唯一ID,基于此,将所有接口的第一个参数设定为数码管ID,用于指定需要操作的数码管设备。
am_digitron_disp_char_at()接口用于显示一个字符,虽然有数码管设备ID 用于确定显示该字符的数码管设备,但仅仅通过数码管设备ID 还不能确定在数码管设备中显示的具体位置,为此需要新增一个索引参数,用于指定字符显示的位置,索引的有效范围为0 ~(数码管个数–1),如MiniPort-View 有两个数码管,则索引的有效范围为 0 ~ 1。此外,该接口还需要一个参数用以指定要显示的字符。定义该接口的函数原型为(暂未定义返回值类型):
对于am_digitron_disp_str()接口,其用于显示一个字符串,除数码管设备ID 外,同样需要一个索引参数以指定字符串显示的起始位置,此外,还需要使用参数指定要显示的字符串以及显示字符串的长度。定义该接口的函数原型为(暂未定义返回值类型):
其中,len 指定显示的长度,p_str 指定要显示的字符串,实际显示的长度为字符串长度和len 中的较小值。
对于am_digitron_disp_decode_set()接口,其用于设定字符的解码函数,显然,一个数码管设备中的多个数码管往往是相同的,可以使用同样的解码规则,共用一个解码函数。因而接口仅需使用ID 指定数码管设备,无需使用index 指定具体的数码管索引。解码函数的作用是对字符进行解码,输入一个字符,输出该字符对应的编码。基于此,定义该接口的函数原型为(暂未定义返回值类型):
其中,pfn_decode 是指向解码函数的指针,表明了解码函数的类型:具有一个16 位无符号类型的ch 参数,返回值为16 位无符号类型的编码。这里使用16 位的数据表示字符和编码,是为了更好的扩展性。如除8 段数码管外,还存在14 段的米字型数码管、16 段数码管等,这些情况下,8 位数据就无法表示完整的段码了。
对于am_digitron_disp_at()接口,其用于直接设置显示的段码,和显示一个字符类似,除数码管设备ID 外,同样需要使用参数指定显示的位置以及要显示的内容(段码),可定义该接口的原型为(暂未定义返回值类型):
对于am_digitron_disp_clr()接口,其用于清除一个数码管设备显示的所有内容,仅需使用ID 指定需要清除的数码管设备,无需其它额外参数。定义该接口的原型为(暂未定义返回值类型):
对于am_digitron_disp_blink_set()接口,其用于设置数码管的闪烁属性,除数码管设备ID 外,还需要使用参数指定设置闪烁属性的数码管位置以及使用本次设置的闪烁属性(打开闪烁还是关闭闪烁),定义该接口的原型为(暂未定义返回值类型):
其中,index 指定本次设置闪烁属性的数码管位置,blink 指定闪烁属性,当值为AM_TRUE 时,则打开闪烁;当值为AM_FALSE 时,则关闭闪烁。
3. 返回值
接口无特殊说明,直接将所有接口的返回值定义为int 类型的标准错误号。数码管接口的完整定义详见表8.8。其对应的类图详见图8.16。
图8.16 数码管接口
表8.8 数码管通用接口(am_digitron_disp.h)
>>> 8.6.2 实现接口
1. 抽象的数码管设备类
类似地,应该根据通用数码管接口,定义相应的抽象方法,以显示字符函数为例,按照前面的通用做法,其抽象方法定义为:
相比于通用接口,其新增了一个用于指向设备自身的p_cookie 参数。在定义数码管通用接口时,使用ID 唯一的代表了一个数码管设备,可见,在这里,p_cookie 和id 均代指了某一确定的数码管设备,由于抽象方法是由具体数码管设备实现的,p_cookie 也用于指向设备自身,通过p_cookie 已经能够唯一的确定某一具体设备,因此,ID 参数在抽象方法中再无实际用处,将ID 参数从抽象方法中移除,即:
实际上,可以看作是参数由抽象意义的ID(仅是一个数字)变为了具有实际意义的p_cookie(指向设备自身的指针)。
基于此,根据其它通用数码管通用接口,为它们一一定义相应的抽象方法,并将其存放在一个虚函数表中,即:
读者可能会发现,在实现LED 接口时,定义的抽象方法同时包含了p_cookie 和led_id参数。即:
这是由于在通用LED 接口的设计中,ID 并非是对LED 设备进行的编号,而是对系统中所有LED 进行的编号,如AM824-Core 板载了2 个LED,MiniPort-LED 上有8 个LED,如果它们同时使用,则系统中有两个LED 设备,但总共有10 个LED,LED 编号为0~9。因此,虽然p_cookie 能够确定要操作的LED 设备,但还是不能确定要操作的具体LED,因此,必须将LED 的编号作为参数传递给具体方法,以便准确的操作到某一具体的LED。
在通用数码管接口的设计中,ID 是对数码管设备的编号,如同时使用MiniPort-View 和MiniPort-ZLG72128 时,系统中有两个数码管设备,虽总共有4 个数码管,但数码管设备的编号只会是0~1。因为如此,数码管设备ID 中并不包含具体数码管的位置信息,为了将显示内容显示到某一确定的数码管上,需要使用额外的index 参数指定。
类似地,将抽象方法和p_cookie 定义在一起,即为抽象的数码管设备。比如:
和LED 抽象设备类似,实际上可能存在多个数码管设备,由于它们的具体数目是无法预先确定的,因此这里使用单向链表进行动态管理,在am_digitron_dev_t 中增加一个p_next成员,用以指向下一个设备。即:
此时,系统中的多个数码管设备使用链表的形式管理。由于在通用接口中,使用id 区分不同的数码管设备。因此,在通用接口的实现中,需要能够通过ID 号找到对应的数码管设备,以便使用其中的抽象方法。和LED 设备类似,可以将一个数码管设备和该设备对应的ID 信息绑定在一起,就可以通过ID 找到对应的数码管设备。
一个数码管设备对应了一个唯一的ID,可以定义数码管设备ID 信息的类型为:
在设备中新增指向ID 信息的p_info 指针,便于在通用接口实现中根据ID 查找到对应的数码管设备,即:
基于此,am_digitron_disp_char_at()函数的实现详见程序清单8.44。
程序清单8.44 am_digitron_disp_char_at()接口实现范例
其中,__digitron_dev_find_with_id()的作用就是遍历设备链表,与各个设备中的ID 信息一一比对,以找到数码管ID 对应的数码管设备,其实现详见程序清单8.45。
程序清单8.45 查找指定id 的数码管设备
其中,__gp_head 是一个全局变量,指向数码管设备的链表头,初始为NULL,表示初始时系统中无任何数码管设备。同理可得到其它接口的实现,详见程序清单8.46,它们的实现都非常类似,均为首先通过__digitron_dev_find_with_id()函数找到ID 对应的数码管设备,然后直接调用设备中的抽象方法。
程序清单8.46 其它数码管接口的实现范例
由于当前没有任何数码管设备,因此__digitron_dev_find_with_id()的返回值始终为NULL,使得通用接口的返回值始终为-AM_ENODEV(错误:无此设备)。
为了使通用接口能够操作到具体有效的数码管设备,就必须在使用通用数码管接口前,向系统中添加有效的数码管设备。根据数码管设备类型的定义,添加一个设备时,需要完成p_ops、p_cookie 和p_info 的正确赋值,这些成员的值是由具体数码管设备实现或定义的,为此,可以为具体数码管的设备驱动提供一个添加数码管设备的接口,定义其函数原型为:
其中,为了方便直接添加一个设备,避免直接操作数码管设备的各个成员,将需要赋值的成员通过参数传递给接口函数。其实现详见程序清单8.47。
程序清单8.47 向系统中添加数码管设备
首先检查了各个参数的有效性,然后使用__digitron_dev_find_with_id()函数判断新设备的ID 号是否已经在系统中,若系统中已经存在该ID,则添加失败,直接返回操作不允许错误(-AM_EPERM)。若系统中不存在该ID,则继续执行,以确保添加的各个数码管设备的ID 不冲突,保证了数码管设备编号的唯一性,接着将设备中的各个成员赋值,最后通过程序清单8.47 的17~18 这2 行代码将新设备添加到链表首部。
图8.17 抽象的数码管设备类
显然,接下来需要在具体的数码管设备中,实现相应的抽象方法,然后使用am_digitron_dev_add()接口将设备添加到系统中,使得用户可以使用数码管通用接口操作到具体有效的数码管。为了便于查阅,如程序清单8.48 所示展示了数码管设备接口文件(am_digitron_dev.h)的内容。
程序清单8.48 am_digitron_dev.h 文件内容
2. 具体的数码管设备类
以使用GPIO 驱动的MiniPort-View 为例,简述具体数码管设备的实现方法。首先应该基于抽象设备类派生一个具体的设备类,其类图详见图8.18,定义具体的数码管设备类如下:
图8.18 具体的数码管设备类
am_digitron_miniport_view_t 即为具体的数码管设备类。具有该类型后,即可使用该类型定义一个具体的数码管设备实例,即:
对于动态扫描类的数码管,需要将数码管显示的段码缓存到一段内存中,然后定时扫描,依次扫描各个数码管,从缓存中取出当前扫描数码管的段码,然后将段码输送到相应的引脚上显示。
为了实现数码管定时自动扫描,需要使用到软件定时器,可以新增一个软件定时器timer成员;在扫描过程中,需要实时记录当前的扫描位置,以便从相应的数码管缓存中取出对应的段码,一个数码管扫描结束后,扫描位置要更新为下一个数码管的位置,可以新增scan_idx成员来实时存储当前数码管的扫描位置。即设备类型可定义为:
此外,为了保存闪烁属性,可以新增一个blink_flags 的成员表示各个需要数码管的闪烁属性,某一位的值为1 时,表明对应的数码管需要闪烁。在一个闪烁周期中,一段时间需要点亮,一段时间需要熄灭,为了判定当前应该处于何种状态,可以新增一个闪烁计时器成员blink_cnt,用于在一个闪烁周期内计时。特别地,在通用接口中,有一个设置数码管解码函数的接口,为了在用户显示字符时,能够使用其设置的解码函数对字符进行解码,则需要一个函数指针保存用户设置的解码函数,基于此,设备类型可定义为:
此外,为了正常使用数码管,还需要知道一些硬件相关的基本信息,如:位选引脚信息、段码引脚信息、数码管个数,段码数目等,据此,可以定义数码管设备的信息类型为:
同时,对于动态扫描类数码管,需要一个缓存用以存储显示的段码,缓存的大小应该与数码管个数相同,可以新增一个p_disp_buf 指针以指向相应的缓存。此外,数码管动态扫描时,扫描的频率必须大于25Hz,才能使肉眼看不到动态扫描的过程,使整个数码管的显示完整、流畅。显然,频率越高,扫描越快,显示就越流畅,但扫描数码管时占用的CPU 资源也就越多;频率越低,系统CPU 资源占用也就越少,但也不能过低。为此,可以在设备信息中新增一个scan_freq 成员,用以指定扫描的频率,使得扫描频率可以由用户根据实际情况配置。扫描频率直接决定了定时器定时扫描的周期,若扫描频率为50Hz,则扫描一次数码管的时间为20ms,由于MiniPort-View 存在两个数码管,因此定时器定时扫描的周期为10ms。可见,定时器定时扫描的时间间隔为:1000 / scan_freq / digitron_num。
此外,当数码管需要闪烁时,为了更加个性化的定制闪烁的效果,可以使用blink_on_time和blink_off_time 分别指定一个闪烁周期内点亮的时间和熄灭的时间,它们的时间之和即为闪烁周期,决定了闪烁的频率。
同时,在数码管通用接口中,各个数码管设备使用ID 号进行区分,显然,这就要求为具体的数码管设备分配一个唯一ID,可以在设备信息中新增表示ID 信息的成员dev_info。完整的数码管设备信息的类型定义为:
当将AM824-Core 与数码管配板MiniPort-View 联合使用时,若分配给数码管设备的ID号为0,扫描频率为50Hz,在一个闪烁周期内,数码管点亮和熄灭的时间均为500ms,基于等效电路图以及数码管信息,定义与MiniPort-View 对应的设备实例信息为:
类似地,在数码管的设备类型中需要维持一个指向数码管设备信息的指针,以便在任何时候都可以从数码管设备中取出相关的信息使用,完整的数码管设备类型定义为:
实际开发过程中,通常并不能一次性完整的定义出设备或设备信息的结构体类型,往往是在定义好基本结构后,在后续实现各个抽象方法的过程中,根据需要增加成员,不断完善结构体类型的定义。
为了正常扫描数码管,需要完成设备中各成员的赋值,在完成初始赋值后,则可以启动软件定时器,进而以设备信息中指定的扫描频率自动扫描数码管。这些工作通常在驱动的初始化函数中完成,定义初始化函数的原型为:
其中,p_dev 为指向am_digitron_miniport_view_t 类型实例的指针,p_info 为指向am_digitron_miniport_view_info_t 类型实例信息的指针,其调用形式如下:
初始化完成后,可使用通用数码管接口操作编号为0 的数码管设备,初始化函数的实现范例详见程序清单8.49。
程序清单8.49 初始化函数实现范例
该程序首先检查了参数的有效性,然后完成了设备中各个成员的初始赋值,接着根据位选引脚和段码引脚的激活电平,将位选引脚和段码引脚配置成输出模式,并将初始电平设置为未激活电平,以使数码管初始处于完全熄灭状态。
紧接着初始化并启动了软件定时器,根据扫描频率设定了软件定时器的定时周期,必将软件定时器的周期性回调函数设置为了__digitron_dynamic_scan_timer_cb(),即在该函数中完成数码管的扫描。最后,使用am_digitron_dev_add()接口将设备添加到了系统中,并将数码管ID 号信息作为该接口p_info 的实参,&__g_digitron_dev_ops 作为该接口p_ops 的实参,指向自身的指针p_dev 作为了接口p_cookie 的实参,__g_digitron_dev_ops 中即包含了各个抽象方法的实现。
由此可见,实现整个具体数码管设备的关键,是在__digitron_dynamic_scan_timer_cb()中完成数码管的扫描,以及实现各个抽象方法并存于__g_digitron_dev_ops 中。
定时器回调函数__digitron_dynamic_scan_timer_cb()的实现详见程序清单8.50。
程序清单8.50 定时器回调函数的实现(数码管扫描)
该程序首先处理闪烁计时器,若存在闪烁的数码管,则增加闪烁计时器p_dev->blink_cnt,增加的值即为扫描时间间隔。特别地,若值增加后超过了闪烁周期,则重新回到0。然后使用__scan_seg_send()发送消影段码,使用__scan_com_sel()处理位选。若当前数码管需要正常显示,即当前数码管不需要闪烁,或者虽然需要闪烁,但根据闪烁计时器判定当前时间处在点亮数码管的时间周期,则从显示缓存中取出当前数码管的段码,并使用__scan_seg_send()发送出去。最后更新了扫描位置索引scan_idx 的值,以便下一次扫描时继续扫描下一个数码管,段码发送函数和位选函数的实现详见程序清单8.51。
程序清单8.51 段码发送函数和位选函数的实现
接下来,需要一一实现抽象数码管设备中共计定义了6 个抽象方法,以完成__g_digitron_dev_ops 的定义。
-
pfn_decode_set
该方法用于设定解码函数,便于显示时,对各个字符进行解码。显然,需要将其保存到设备中,以便后续使用。范例程序详见程序清单8.52。
程序清单8.52 设置解码函数的实现范例程序
-
pfn_blink_set
该方法用于设定某一个数码管的闪烁属性,设置闪烁属性时,只需要将设备中的闪烁标记blibk_flags 相应位置1(闪烁)或清零(不闪烁)即可,范例程序详见程序清单8.53。
程序清单8.53 设置闪烁属性函数的实现范例程序
-
pfn_disp_at
该方法用于在指定数码管上显示指定的段码图形,只需要将段码存放在数码管缓存中即可,范例程序详见程序清单8.54。
程序清单8.54 显示段码函数的实现范例程序
该程序调用了__digitron_disp_buf_set()函数将段码设置到缓存中,详见程序清单8.55。
程序清单8.55 __digitron_disp_buf_set()函数实现
该程序根据数码管段的激活电平决定是否需要将用户设置的段码取反后存入缓冲区中。
-
pfn_disp_char_at
该方法用于在指定位置显示字符,这就需要先使用解码函得到字符对应的段码,然后将段码设置到缓冲区中,范例程序详见程序清单8.56。
程序清单8.56 显示字符函数的实现范例程序
该程序首先通过解码函数得到字符的段码存于seg 中。然后将段码保存到相应的缓冲区中,且在保存段码时对小数点作了特殊的处理。
当字符为小数点时,则使用__digitron_disp_buf_xor()函数将段码设置到函数区;否则直接使用__digitron_disp_buf_set()函数将段码设置到缓存区中。
由于由于小数点比较特殊,因此显示小数点时,往往不希望影响该位数码管的正常显示内容,比如,如果当前数码管显示数字3,又需要在该数码管添加小数点,则期望的结果是显示“3.”,而不仅仅显示一个小数点,将之前的3 覆盖掉。由此可见,在显示小数点时,可视为显示内容的一种叠加,而不是直接改变显示内容,__digitron_disp_buf_xor()函数的实现详见程序清单8.57。
程序清单8.57 __digitron_disp_buf_xor()函数实现
若数码管段为低电平激活,则需要在原缓冲区段码的基础上,将显示小数点需要点亮的段清零,以显示小数点;反之则将显示小数点需要点亮的段置1,以显示小数点。
-
pfn_disp_str
该方法用于从指定位置开始显示一个字符串,范例程序详见程序清单8.58。
程序清单8.58 字符串显示函数实现范例程序
该程序首先确定了字符串的长度,字符串长度取字符串实际长度和参数len 中的较小值,然后再while 循环中,调用了__digitron_disp_char_at()函数依次显示单个字符。
其中的idx 用于指定显示位置,初始值为index – 1,即字符串显示起始位置的上一个数码管位置,若起始位置为0,则idx 的初始值表示了一个无效的位置。每次显示新内容前,需要更新idx 的值(将idx 加1)。但在某种特殊情况下,小数点不需要更新显示位置,例如,显示字符串“3.5”,期望显示的效果是只占用2 个数码管,分别显示“3.”和“5”,而不是占用3 个数码管,这种情况下,当显示小数点时,直接显示在“3”所在的数码管中即可,无需将其单独显示到一个数码管上。程序需要更新显示位置的条件为:
由此可见,不需要更新位置的条件即为上述条件的反面:
不需要更新位置的条件为:当前显示的字符为小数点,且上一个字符不为小数点,同时idx 指定的显示位置有效。
-
pfn_clr
该方法用于清空数码管显示,需要将缓冲区中的内容全部设置为熄灭段码,范例程序详见程序清单8.59。
程序清单8.59 清空显示内容函数的实现范例程序
至此,实现了各个抽象方法,基于各个抽象方法的实现函数,__g_digitron_dev_ops 的定义详见程序清单8.60。
程序清单8.60 __g_digitron_dev_ops 的定义
当用户使用初始化函数完成一个具体数码管设备的初始化后,即可使用通用数码管接口操作数码管,显示具体内容。但是,在显示字符或字符前,必须使用通用接口设置一个解码函数。对于8 段数码管,可以将各个ASCII 字符的段码定义在一个数组中,然后实现一个解码函数,详见程序清单8.61。
程序清单8.61 解码函数的实现
基于此,在用户使用数码管接口显示字符或字符串前,可以使用设置解码函数的接口将该解码函数设置到系统中,以便正确解码。比如:
如果用户每次使用数码管前,都需要自定义一个解码函数,则显得非常麻烦。对于8段数码管,常见图形的显示方法是固定的,对应的段码是可以确定的,如数字0 ~ 9。用户如果没有特殊需求,使用程序清单8.61 所示的解码函数是能满足绝大部分应用的。基于此,可以将程序清单8.61 所示的解码函数定义在系统中,直接供用户使用。为方便用户使用,可以将该解码函数声明到数码管接口文件中。
为了便于查阅,如程序清单8.62 所示展示了具体数码管设备(MiniPort_View)接口文件(am_digitron_miniport_view.h)的内容。
程序清单8.62 am_digitron_miniport_view.h 文件内容
原文标题:周立功:深入理解AMetal——通用数码管接口
文章出处:【微信号:ZLG_zhiyuan,微信公众号:ZLG致远电子】欢迎添加关注!文章转载请注明出处。
发布评论请先 登录
相关推荐
评论