0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

深入了解AMetal框架,通用按键接口不可忽视

AGk5_ZLG_zhiyua 来源:互联网 作者:佚名 2017-11-29 06:21 次阅读

周立功教授新书《面向AMetal框架与接口编程(上)》,对AMetal框架进行了详细介绍,通过阅读这本书,你可以学到高度复用的软件设计原则和面向接口编程的开发思想,聚焦自己的“核心域”,改变自己的编程思维,实现企业和个人的共同进步。经周立功教授授权,即日起,致远电子公众号将对该书内容进行连载,愿共勉之。

第八章为深入理解AMetal,本文内容为8.5 通用按键接口。

8.5 通用按键接口

>>> 8.5.1 定义接口

1. 接口命名

由于操作的对象是按键(key),按键是一种输入设备,为了使含义更加清晰,接口命名增加input 关键字,因此,接口命名以“am_input_key_”作为前缀。

按键的操作主要分为两大部分:按键检测和按键处理。按键检测与具体硬件相关,按键处理与应用相关,由用户完成。

对于用户,其只需关心如何对按键进行处理,进而定义相应的按键处理方法(函数),不需要关心按键检测的具体细节。

对于按键检测,其只需要检测是否有按键事件发生(按键按下或按键释放),当检测到按键事件时,应该通知到用户,以便进行相关的按键处理。

显然,按键处理方法是由用户定义的,只有用户知道,按键检测模块无从得知。当检测到按键事件时,为了能够执行到相应的按键处理方法,必须使按键检测模块可以通过某种方法调用到相应的按键处理方法。

由此可见,按键处理方法是由用户定义的,但却需要由按键检测模块调用,可以使用典型的回调机制来处理这种情况,即:将按键处理方法视为需要由按键检测模块回调的函数,用户将按键处理函数注册到按键检测模块中(将函数地址传递给按键处理模块,按键处理模块使用函数指针将其保存),当检测到按键事件时,查找模块中已经注册的按键处理函数,然后一一调用(通过函数指针调用)。

虽然使用单一的回调机制可以实现按键管理,但是,却使得按键检测模块的职责变得不单一,其不仅要处理与硬件相关的按键检测,还要管理用户注册的回调函数,有悖于单一职责原则。

基于按键检测模块的本质:检测按键事件。可以将管理用户注册回调函数的部分分离出来,形成一个单独的按键管理模块。

基于此,用户不再直接与按键检测模块产生交互,用户将按键处理方法注册到按键管理模块中。当按键检测模块检测到按键事件时,通知按键管理模块,告知有按键事件发生,按键管理模块接收到通知后,查找模块中已经注册的按键处理函数,然后一一调用。

由此可见,新增按键管理模块后,用户和按键检测都仅仅与按键管理模块交互,实现了用户和按键检测的完全分离。这就是典型的分层设计思想,用户属于应用层、按键管理模块属于中间层、按键检测属于硬件层,示意图详见图8.14,中间层将应用层与硬件层完全隔离。

图8.14 按键系统分层结构图

中间层需要为应用层提供一个注册按键处理函数的接口,其接口名定义为:

  • am_input_key_handler_register

同时,中间层还需要为硬件层提供一个上报按键事件的接口,用于当检测到按键事件时,使用该接口通知中间层有按键事件发生,进而使中间层调用用户注册的按键处理函数。其接口名定义为:

  • am_input_key_report

2. 接口参数

要使用am_input_key_handler_register()接口注册按键处理函数,首先必须定义好按键处理函数的类型。

在AMetal 中,一般将回调函数的第一个形参设置为void *类型的p_arg 参数,在用户注册回调函数时,指定一个void*类型的变量作为调用回调函数时传递给第一个参数的实参,以便在回调函数中处理用户自定义的一些上下文数据。

此外,系统中可能存在多个按键,为了使用户可以区分各个按键,以便针对不同的按键作不同的处理,可以为每个按键分配一个唯一编码key_code。编码是一个整数,如0、1、2……为了可读性,可以使用宏的形式定义一些常见的具有实际意义的按键编码,如对应PC 键盘,可以定义KEY_A ~ KEY_Z(字母键)、KEY_0 ~ KEY_9(数字键)、KEY_KP0 ~ KEY_KP9(小键盘数字键)等等,将按键编码定义在am_input_code.h 文件中,如KEY_0 ~ KEY_9的定义详见程序清单8.37。

程序清单8.37 按键编码定义范例(am_input_code.h)

如此一来,应用程序可以直接使用具有实际意义的按键编码宏,而无需关心其对应的具体编码值,这样不仅增加了应用程序的可读性,也使应用程序不依赖于具体的编码值,更有利于跨平台复用。例如,在一个应用中,其使用了数字键0,在当前平台中,数字键0 对应的按键编码值为11,假如在另外的某一平台中,数字键0 的编码值为12。若当前应用程序是使用数字键0 对应的宏KEY_0 实现的,则更换平台后,应用程序无需作任何修改;但若应用程序直接使用了编码值11,则更换平台后需要修改程序,将编码值修改为12。

虽然在绝大部分情况下都只需要处理按键按下事件,但是,作为通用接口,还需要考虑到,在一些特殊的应用场合,可能需要处理按键释放事件,为此,可以使用一个表示按键状态的key_state 参数。由于key_state 仅用于表示按下或释放,可以使用宏的形式将可能的取值定义出来,其定义如下(am_input.h):

回调函数作为按键处理函数,不需要反馈任何信息给实际调用者(中间层),因此无需返回值。基于此,按键处理函数的类型即为:无返回值,具有3 个参数:p_arg,key_code,key_state 的函数。注册的回调函数需要使用函数指针来存储函数的地址,以便当按键事件发生时,使用函数指针调用实际的按键处理函数,函数指针的类型定义为:

注册按键处理函数时,需要指定注册的按键回调函数以及一个void*类型的p_arg 参数作为按键处理函数的第一个参数。am_input_key_handler_register()的函数原型为:

实际中,回调函数和p_arg 需要存储在内存中,才能在合适的时候使用它们。可以定义一个专门的结构体类型存储它们,假定类型为:am_input_key_handler_t(按键处理器),其具体的定义在后文根据实现来定义。显然,每注册一个按键回调函数,都需要提供这样一个类型的内存空间。因此,注册按键处理函数时,需要使用该类型的指针指定一个按键处理器空间,完善am_input_key_handler_register()的函数原型为:

根据前面分析的按键处理函数类型,在按键管理模块调用回调函数时,需要知道按键的编码和按键的状态,以便应用程序根据实际情况进行处理。

显然,按键编码和按键状态需要由按键检测模块进行检测,当检测到某一编码的按键发生按键事件时,就将按键编码和按键状态上报给按键管理模块,按键管理模块进而根据这些信息调用按键处理函数,基于此,使用am_input_key_report 接口上报按键事件时,需要指定按键编码key_code 和按键状态key_state,其函数原型为:

3. 返回值

接口无特殊说明,直接将所有接口的返回值定义为int 类型的标准错误号。按键管理模块接口的完整定义详见表8.6。其对应的类图详见图8.15。

图8.15 按键管理接口

表8.6 按键管理通用接口(am_input.h)

>>> 8.5.2 实现接口

1. 实现am_input_key_handler_register()接口

由于按键管理器是一个中间层模块,其本身与具体硬件无关,因此可以直接实现相应的接口,而无需为适应不同的硬件而定义抽象方法。

在定义接口参数时,提到了使用am_input_key_handler_t 类型的按键处理器来存储指向回调函数的指针和回调函数的p_arg 参数,基于该用途,其类型定义为:

显然,一个系统中,可能远不止一个按键处理器,比如,A 应用需要处理编码为KEY_1的按键,B 应用需要处理编码为KEY_2 的按键,它们可以分别定义按键处理函数以处理各自的KEY_1 或KEY_2 按键,此时,就需要两个按键处理器来分别存储A 应用和B 应用的按键处理函数。

当系统中有多个按键处理器时,就存在一个如何管理的问题,由于按键处理器的个数与应用相关,具体个数不定,因此,采用单向链表的方式进行管理,为此,在按键处理器类型中新增一个p_next 成员,使其指向下一个按键处理器:

基于此,可以实现注册按键处理函数接口,其范例程序详见程序清单8.38。

程序清单8.38 am_input_key_handler_register()接口实现范例程序

该程序首先判定了参数的有效性,然后完成了按键处理器中pfn_cb 和p_usr_data 的赋值,将用户的按键处理函数和用户参数保存到了按键处理器中,接着通过程序清单8.38 的14 ~ 15 行共计2 行代码将新的按键处理器添加到链表首部。全局变量__gp_handler_head 指向了链表头,初始时,由于没有注册任何按键处理器,因此其值为NULL。

2. 实现am_input_key_report()接口

该接口用于当硬件层检测到按键事件时,通过该接口上报按键事件。当接收到上报事件时,需要遍历当前系统中所有的按键处理器,并一一调用它们的按键处理函数,基于此实现按键事件上报接口的范例程序详见程序清单8.39。

程序清单8.39 am_input_key_report()接口实现范例程序

该程序从链表的头结点开始,依次遍历各个按键处理器,然后通过函数指针调用其中的按键处理函数,在调用按键处理函数时,将按键处理器中存储的用户参数p_usr_data 作为按键处理函数的用户参数传递,key_code 和key_state 则直接使用上报的按键编码和按键状态。

上述程序作为一种范例,实现非常简洁,和其它通用接口的实现不同的是,这里没有定义任何抽象方法,仅仅通过简短的代码直接实现了相应的两个接口,这是由于按键管理器本身是基于分层设计的思想定义出来的,其不依赖于具体硬件,它为具体硬件检测模块提供了一个am_input_key_report()接口用于上报按键事件。

为了便于查阅,如程序清单8.40 所示展示了按键管理接口文件(am_input.h)的内容。

程序清单8.40 am_input.h 文件内容

>>> 8.5.3 检测按键的实现

上面实现了按键管理器的接口,按键管理器作为一个中间层,其为上层应用提供了注册按键处理函数的接口,为下层硬件驱动提供了按键事件的上报接口。显然,对于不同的硬件,其按键扫描的方法是不同的,但当扫描的按键事件时,均只需要调用am_input_key_report()接口上报按键事件即可。

本节以独立键盘为例,讲述硬件层检测按键的具体实现方法。根据面向对象的设计思想,将独立键盘看做一个对象,定义其类型为:am_key_gpio_t。即:

具体需要包含哪些成员呢?为了实现按键定时自动扫描,需要使用软件定时器,可以新增一个软件定时器timer 成员;在扫描过程中,为了实现消抖需要将当前扫描的键值和上一次扫描得到的键值比较,可以新增一个key_prev 成员,用于保存上一次扫描到的键值;当检测到有效的扫描键值时(本次扫描得到的键值和上一次扫描得到的键值相同),需要和上一次有效的扫描键值进行比较,以确定哪些按键的状态发生了变化,可以新增一个key_press成员,用于保存上一次有效的扫描键值。独立键盘的类型可以定义为:

am_key_gpio_t 即为独立键盘设备类。具有该类型后,即可使用该类型定义一个独立键盘设备实例,即:

此外,为了正常使用独立键盘,还需要知道一些硬件相关的基本信息,比如,引脚信息、按键按下的电平信息(按下为高电平还是低电平)和按键数目,同时还可以指定一个键盘扫描的时间间隔,即软件定时器的定时周期,决定了键盘扫描的快慢。可以定义独立键盘的信息类型为:

特别地,当检测到某一按键事件时,需要使用am_input_key_report()上报按键事件,按键事件包括按键的编码和按键的状态,按键的状态(按下或释放)可以通过按键扫描的键值判定出来。为了便于用户区分不同按键,为每个按键分配的唯一编码值,相当于唯一ID 号,因此按键的编码值只能由用户决定,按键扫描程序是无法决定的,为了在上报按键事件时使用正确的编码,需要由用户提供各个按键的编码信息,为此,在独立键盘的信息中,新增p_codes 成员,使其指向按键的编码信息,完整的独立键盘信息类型定义为:

AM824-Core 上板载了一个独立按键,当J14 的1 和2 短接时,KEY 与PIO_KEY(PIO0_1)连接。假定为其分配的按键唯一编码为KEY_KP0,则独立键盘的信息可以定义为:

类似地,在独立键盘的设备类型中需要维持一个指向独立键盘信息的指针,以便在任何时候都可以从独立键盘设备中取出相关的信息使用,完整的独立键盘设备类型定义为:

显然,要使按键能够正常扫描,需要完成设备中各成员的赋值,在完成初始赋值后,则可以启动软件定时器,进而以设备信息中指定的扫描时间间隔自动扫描按键。这些工作通常在驱动的初始化函数中完成,定义初始化函数的原型为:

其中,p_dev 为指向am_key_gpio_t 类型实例的指针,p_info 为指向am_key_gpio_info_t类型实例信息的指针。基于前面定义的设备实例和实例信息,其调用形式如下:

初始化函数的实现范例详见程序清单8.41。

程序清单8.41 独立键盘初始化函数实现范例

该程序首先判定了参数的有效性,需要特别注意的是,由于当前设备中使用了uint32_t类型的数据存储扫描键值(如key_prev,key_press),最多只能支持32 个按键,因此当按键数目超过32 时,返回“不支持”的错误号。若为了支持更多的独立按键,可以使用位宽更宽的数据类型,但实际上独立键盘每个按键需要占用一个引脚,往往独立按键的数目都不会过多,具有大量按键时,往往采用矩阵键盘。

接着根据按键按下时的电平,将引脚配置为了输入模式,并将key_prev 和key_press 初始赋值为所有按键均未按下时对应的键值。最后,初始化并启动了软件定时器,将软件定时器的定时周期设定为了独立键盘信息中的扫描时间间隔,软件定时器的周期性回调函数设置为了__key_gpio_timer_cb,即在该函数中完成独立键盘的扫描,其实现详见程序清单8.42。

程序清单8.42 独立键盘扫描函数实现

首先使用了__key_val_read()函数读取当前的扫描键值,在__key_val_read()函数的实现中,依次读取各个按键对应的引脚电平,将各个引脚的电平信息保存在对应的位中。当前扫描到的键值存储在key_value 中。

然后将key_value 与上一次的扫描键值p_dev->key_prev 比较,若两者相等,表明本次扫描键值key_value 是有效的键值。此时将有效键值key_value 与上一次的有效扫描键值p_dev->key_press 比较,若二者不等,则表明有按键事件发生,通过异或运算找出两者之间发生变化了的位,其值存储在key_change 中。

接着遍历key_change 的各个位,若key_change 的相应位为1,则表明对应按键的状态发生的变化,需要上报。按键位值和active_low 共同决定了当前按键的状态(按下或者未按下),其对应的真值表详见表8.7。可见,当按键扫描的值与active_low 相等时,表明当前按键未处于按下状态,此次状态变化是由于按键释放产生的,应该上报按键释放事件。反之,表明当前按键处于按下状态,此次状态变化是由于按键按下产生的,应该上报按键按下事件。上报事件时,按键编码是从独立键盘信息中的编码信息中得到的。注意,在进行比较之前,将它们连续进行了两次“取非”操作,即 “!!”,确保待比较的值只能为0 或1。

表8.7 按键状态真值表

在所有按键事件上报结束后,表明完成了对一次有效扫描键值的处理,需要更新上一次的有效扫描键值p_dev->key_press 为key_value。无论有效按键的键值是否发生变化,在程序的末尾都会更新上一次的扫描键值p_dev->key_prev 为本次的扫描键值key_value。

为了便于查阅,如程序清单8.43 所示展示了独立键盘接口文件(am_key_gpio.h)。

程序清单8.43 am_key_gpio.h 文件内容

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 嵌入式
    +关注

    关注

    4981

    文章

    18274

    浏览量

    288367
  • 周立功
    +关注

    关注

    38

    文章

    130

    浏览量

    37078
  • 回调函数
    +关注

    关注

    0

    文章

    87

    浏览量

    11455

原文标题:周立功:深入理解AMetal——通用按键接口

文章出处:【微信号:ZLG_zhiyuan,微信公众号:ZLG致远电子】欢迎添加关注!文章转载请注明出处。

收藏 人收藏

    评论

    相关推荐

    面向AMetal框架接口的编程原理和技巧

    《面向AMetal框架接口的编程(上)》一书对AMetal框架进行了详细介绍,通过阅读这本书,你可以学到高度复用的软件设计原则和面向
    的头像 发表于 10-24 18:15 6008次阅读
    面向<b class='flag-5'>AMetal</b><b class='flag-5'>框架</b>与<b class='flag-5'>接口</b>的编程原理和技巧

    面向ametal框架接口的编程ametal uart总线

    《面向AMetal框架接口的编程(上)》一书对AMetal框架进行了详细介绍,通过阅读这本书,你可以学到高度复用的软件设计原则和面向
    的头像 发表于 11-06 06:55 6122次阅读
    面向<b class='flag-5'>ametal</b><b class='flag-5'>框架</b>与<b class='flag-5'>接口</b>的编程<b class='flag-5'>ametal</b> uart总线

    关于ametal_led通用接口的设计技巧分享

    第八章为深入理解AMetal,本文内容为8.1 LED 通用接口
    的头像 发表于 11-23 07:52 6395次阅读
    关于<b class='flag-5'>ametal</b>_led<b class='flag-5'>通用</b><b class='flag-5'>接口</b>的设计技巧分享

    学好ametal_通用数码管接口技术,做项目事半功倍

    第八章为深入理解AMetal,本文内容为8.6 通用数码管接口
    的头像 发表于 11-30 08:31 2771次阅读
    学好<b class='flag-5'>ametal</b>_<b class='flag-5'>通用</b>数码管<b class='flag-5'>接口</b>技术,做项目事半功倍

    详解ametal_键盘扫描接口和pwm接口技术

    第五章为深入浅出AMetal,本文内容为5.3 键盘扫描接口和5.4 PWM 接口
    的头像 发表于 12-07 08:16 6974次阅读
    详解<b class='flag-5'>ametal</b>_键盘扫描<b class='flag-5'>接口</b>和pwm<b class='flag-5'>接口</b>技术

    深入了解逻辑分析仪入门手册

    深入了解逻辑分析仪入门手册引言与许多电子测试和测量工具一样,逻辑分析仪是一种针对特定类型问题的解决方案。它是一种通用工具,可以帮助您调试数字硬件、检验设计和调试嵌入式软件。对设计数字电路的工程师来说
    发表于 11-19 11:40

    深入了解示波器

    深入了解示波器
    发表于 11-14 22:32

    深入了解LabVIEW FPGA资料分享

    深入了解LabVIEW FPGA
    发表于 05-27 08:35

    带你深入了解光耦

    电子发烧友网带你深入了解光耦相关知识,讲述光耦的作用,光耦原理及各种光耦型号和替代型号,让大家全面了解光电耦合器
    发表于 03-16 16:43
    带你<b class='flag-5'>深入了解</b>光耦

    《面向AMetal框架接口的编程(上)》周立功最新力作

    面向Ametal框架接口的编程
    发表于 08-21 13:49 47次下载
    《面向<b class='flag-5'>AMetal</b><b class='flag-5'>框架</b>与<b class='flag-5'>接口</b>的编程(上)》周立功最新力作

    深入浅出AMetal接口与实现

    《面向AMetal框架接口的编程(上)》一书对AMetal框架进行了详细介绍,通过阅读这本书,你可以学到高度复用的软件设计原则和面向
    的头像 发表于 10-27 15:21 6256次阅读
    <b class='flag-5'>深入</b>浅出<b class='flag-5'>AMetal</b>之<b class='flag-5'>接口</b>与实现

    面向AMetal框架接口的编程

    面向AMetal框架接口的编程
    发表于 11-23 15:22 7次下载

    带你深入了解示波器

    带你深入了解示波器
    发表于 02-07 14:26 18次下载

    深入了解安全光栅

    深入了解安全光栅
    的头像 发表于 06-25 13:53 751次阅读
    <b class='flag-5'>深入了解</b>安全光栅

    深入了解 GaN 技术

    深入了解 GaN 技术
    的头像 发表于 12-06 17:28 4333次阅读
    <b class='flag-5'>深入了解</b> GaN 技术