电子说
本文导读
本文详细介绍了AWorks中开发设备驱动的一般方法。基于这些通用的方法,用户可以尝试独立开发一些设备的驱动,以进一步加深对AWbus-lite的理解。同时,当后续遇到一些AWorks 暂不支持的设备时,也可以自行开发设备相应的驱动。
本文为《面向AWorks框架和接口的编程(上)》第三部分硬件篇——第13章——第4小节:驱动开发的一般方法。13.4 驱动开发的一般方法
上面以LED为例,从接口定义到驱动开发都进行了详细的介绍,对AWbus-lite中相关的概念有了更加深入的理解。完整的设备相关程序主要分为三个部分:
通用接口
位于最上层,与具体硬件无关,由应用程序直接访问,构成可以跨平台复用的应用程序。虽然通用接口看似简单,但要完成其完善的定义并不容易,往往需要经过大量项目的积累,从接口功能和设计原则等多个方面考虑,才能定义出既简洁又实用的通用接口,一般来讲,通用接口无需用户定义,由广州致远电子有限公司统一定义和维护。
接口实现
位于中间层,完成如抽象方法、LED服务、METHOD类型等的定义。中间层同样与具体硬件无关,主要使用抽象方法的形式实现上层定义的通用接口。该层往往在定义通用接口时由广州致远电子有限公司实现。对于驱动开发者,仅需了解这里定义的各个抽象方法,以便在开发具体驱动时,根据具体硬件实现各个抽象方法。
具体驱动实现
位于最底层,根据具体硬件完成抽象方法的实现,定义Method对象列表,提供相应的服务。随着AWorks的不断发展和完善,迄今为止已经积累了大量的设备驱动,常见设备均已支持。由于实际硬件的千差万别,用户可能遇到AWorks暂不支持的设备(暂无对应驱动),此时,用户可以自行开发设备驱动。
在LED驱动开发的介绍中,由于很多概念初次遇到,因而花费了较多篇幅介绍这些基本概念,略显繁琐。实质上,驱动开发的核心就是完成一个驱动信息结构体常量的定义,比如LED驱动开发的结果,就是完成了结构体常量__g_drvinfo_led_gpio的定义(详见程序清单13.48)。下面,针对驱动开发进行简要的梳理,归纳出驱动开发的一般步骤。
1、 定义驱动名;
2、 确定总线类型和设备类型;
3、 定义实际设备类型;
4、 定义设备信息类型;
5、 实现三个阶段的初始化函数;
6、 实现设备要提供的服务,比如LED服务;
7、 定义Method对象,以便上层获取设备提供的服务;
8、 定义驱动结构体常量,实现驱动注册函数。
在上一章中,直接使用了PCF85063驱动定义的驱动名、设备类型、设备信息类型等,完成了PCF85063硬件资源的定义(详见程序清单12.12)。下面,将按照驱动开发的一般步骤,尝试基于AWorks中现有的RTC架构,开发PCF85063实时时钟芯片的驱动。深入理解PCF85063驱动的具体由来。
PCF85063是NXP半导体公司推出的一款低功耗实时时钟/日历芯片,它提供了实时时间的设置与获取、闹钟、可编程时钟输出、定时器/报警/半分钟/分钟中断输出等功能。引脚封装详见图13.3,其中,SCL和SDA为I2C接口引脚,VDD和VSS分别为电源和地;OSCI和OSCO为32.768KHz的晶振连接引脚,作为PCF85063的时钟源;CLKOUT为时钟信号输出,供其它外部电路使用;INT为中断引脚,主要用于闹钟等功能。
图13.3 PCF85063引脚定义
13.4.1 定义驱动名
作为PCF85063的驱动,可以直接将驱动名定义为:"pcf85063",即:
基于此,基础驱动信息中p_drvname的值为:AWBL_PCF85063_NAME。
13.4.2 确定总线类型和设备类型
确定驱动所处的总线类型,对PCF85063作简要了解可知,该芯片通过I²C总线访问,芯片所处总线的类型即为:AWBL_BUSID_I²C。总线类型是一个非常重要的信息,驱动结构体常量、设备类型的定义均与总线类型相关。
确定驱动对应设备的类型,是普通设备还是特殊的总线控制器设备。对于PCF85063设备,其不能再继续扩展下游总线,仅能提供RTC功能,是普通设备,即对应的设备类型为:AWBL_DEVID_DEVICE。
基于此,基础驱动信息中bus_id的值为:AWBL_BUSID_I²C | AWBL_DEVID_DEVICE(或省略AWBL_DEVID_DEVICE,直接设置为AWBL_BUSID_I²C)。
13.4.3 定义设备类型
实际设备类型从基础设备类型派生而来,以添加设备相关的私有成员。在AWBus-lite中,I²C总线上的设备基础类型定义为:struct awbl_i2c_device。其定义详见程序清单13.53。
程序清单13.53 struct awbl_i2c_device类型定义
由此可见,struct awbl_i2c_device类型是从AWBus-lite基础设备类型派生而来的,当前并未添加任何其它成员,主要是为了方便后续扩展,增加I²C总线从机设备相关的私有成员。
基于此,PCF85063设备类型的定义形式详见程序清单13.54。
程序清单13.54 PCF85063设备类型定义(1)
虽然PCF85063并未直接从AWBus-lite基础设备类派生,但本质上,其还是属于AWBus-lite基础设备类型的派生类。对应类图详见图13.4。
图13.4 PCF85063设备类关系
显然,要完成PCF85063设备类型的定义,重点是考虑需要增加哪些其它成员。PCF85063设备的核心功能是为系统提供RTC服务,在AWBus-lite中,定义了RTC服务结构体类型struct awbl_rtc_service,其具体定义详见程序清单13.55。
程序清单13.55 RTC服务类型定义(awbl_rtc.h)
其中,p_next用于指向下一个RTC服务,使系统可以以链表的形式组织多个RTC服务。p_servinfo为RTC服务相关的信息,提供RTC服务时,必须指定RTC服务的信息,其类型struct awbl_rtc_servinfo的定义详见程序清单13.56。
程序清单13.56 RTC服务信息定义(awbl_rtc.h)
由此可见,RTC服务信息中仅包含ID号信息。每个RTC服务都具有一个唯一ID,当用户使用通用接口访问RTC服务时,需要传入一个ID号,用于指定需要使用的RTC服务。系统将传入的ID号与各个RTC服务对应的ID号一一比对,进而查找到指定的RTC服务。
p_servfuncs指向一个虚函数表,其类型struct awbl_rtc_servopts包含了RTC服务定义的抽象方法,详见程序清单13.57。
程序清单13.57 RTC抽象方法的定义(awbl_rtc.h)
显然,要使PCF85063能够提供RTC服务,驱动就必须实现这里定义的抽象方法,抽象方法的具体实现将在提供RTC服务小节中详细介绍。
p_cookie由驱动设置,系统在调用抽象方法时,将原封不动将其的作为抽象方法的第一个参数,传递给驱动使用。
PCF85063可以提供RTC服务,在设备类型中,应该包含一个RTC服务结构体成员,实现RTC服务实质上就是完成RTC服务中各个成员的赋值,系统上层获取RTC服务就是获取指向RTC服务结构体变量的指针。基于此,可以更新PCF85063设备类型的定义,详见程序清单13.58。
程序清单13.58 PCF85063设备类型定义(2)
当前仅仅从PCF85063的主要功能出发,完成了PCF85063设备类型的定义,若在开发过程中,发现需要在设备类型中增加新的成员,可以随时动态添加。
13.4.4定义设备信息类型
PCF85063可以提供RTC服务,提供RTC服务时,需要一并设置相应的RTC服务信息(为p_servinfo成员赋值),以供系统使用。当前的RTC服务信息仅包含一个ID号(详见程序清单13.56),ID号是一种唯一标识,不同设备提供的RTC服务对应的ID号是不同的,具体数值应由用户分配,为此,用户在使用PCF85063时,应该提供RTC服务信息,基于此,PCF85063设备信息类型的定义程序清单13.59。
程序清单13.59 PCF85063设备信息类型定义(1)
此外,PCF85063是一种I²C从机器件,I²C从机器件具有一个从机地址,该地址可以由用户指定。为此,设备信息可以新增一个addr地址信息。完整的定义详见程序清单13.60。
程序清单13.60 PCF85063设备信息类型定义(2)
13.4.5 实现三个阶段的初始化函数
实现三个阶段的初始化函数,以便为基础驱动信息中的驱动入口点p_busfuncs(详见程序清单13.19)赋值。在具体实现前,可以先搭建好软件结构,详见程序清单13.61。
程序清单13.61 三个阶段初始化函数的结构性代码
其中,__g_pcf85063_drvfuncs的地址即可作为驱动入口点p_busfuncs的值。
在实现各个初始化函数前,需要梳理出具体要执行哪些初始化操作。对于PCF85063,本驱动仅使用其提供的通用实时时钟功能,即获取或设置当前时间(年、月、日、时、分、秒等时间信息),闹钟、中断、时钟输出等功能均不使用。PCF85063在上电后,其时间即会正常运行,闹钟等功能处于关闭状态,由此可见,时钟方面,并不需要作任何特殊的操作。
特别地,PCF85063可以通过CLKOUT引脚输出时钟信号,信号的频率可以通过控制和状态寄存器2(control and status register2,寄存器地址为0x01)的低三位(bit2 ~ bit0)进行设定,详见表13.8。这些信息更加详细的说明可以通过PCF85063的数据手册获得。
表13.8 CLKOUT控制值与输出频率的关系
控制值的默认值为000,即输出频率为32768。由于本驱动并未使用CLKOUT功能,因此,应该将其输出关闭,避免其对外部电路产生影响,这就需要将控制值修改为111。
PCF85063需要通过I²C总线对其中的寄存器值进行访问。在AWBus-lite中,提供了I²C读写函数,用于对I²C从机设备进行读写,接口原型详见表13.9。
表13.9 I²C标准接口函数
在外设通用接口的介绍中,讲解了I²C通用接口(详见表7.14,接口命名前缀为“aw_”),对比可以发现,它们的形式非常类似,不同之处的仅有两点。
操作的对象类型不同
在这里,以“awbl_”为前缀的读写接口操作的对象(第一个参数)是AWBus-lite中的I²C从机设备,类型为struct awbl_i2c_device,该类型的从机设备挂载在AWBus-lite中,基于AWBus-lite拓扑结构,可以知道该从机设备挂载的位置,从而获得其对应的I²C总线控制器,进而完成读写操作。
以“aw_”为前缀的通用I²C读写接口操作的对象是用户使用aw_i2c_mkdev()接口定义的通用I²C从机设备,类型为aw_i2c_device_t,这类设备是应用程序直接操作的设备,并没有挂载在AWBus-lite中,其对应的I²C总线控制器无法通过AWbus-lite的拓扑结构获得,因而,在定义设备时,必须通过ID号指定该从机设备对应的总线ID,系统通过ID找到对应的I²C总线控制器,进而完成读写操作。
显然,在PCF85063驱动程序中,I²C总线操作的对象是PCF85063。
PCF85063设备类型是基于struct awbl_i2c_device类型派生而来的,而struct awbl_i2c_device类型是基于AWBus-lite基础设备类型派生而来的,因此,在各阶段初始化函数中,若要对PCF85063进行读写操作,则可以将基础设备类型的p_dev指针(其实际指向的是PCF85063设备)直接强制转换为struct awbl_i2c_device类型的指针使用。
参数个数不同
在通用I²C读写接口中,除p_dev外,仅subaddr、p_buf和nbytes三个参数,分别表示寄存器子地址、读/写数据缓存、读/写数据字节数。而这里的读写接口多了flags和addr两个参数,分别表示从机设备属性和从机设备地址,实际上,通用I²C接口也有这两个信息,不过是在使用aw_i2c_mkdev()定义从机设备时,存储在了从机设备中,对于通用I²C接口,这两个信息在aw_i2c_mkdev()接口中指定。本质上,它们表示的含义是完全相同的。
从机属性的定义详见表7.15,主要指定了从机地址的位数、是否忽略无应答和器件内子地址(通常又称之为“寄存器地址”)的字节数;从机地址即I²C设备的从机地址。
例如,要将控制和状态寄存器2(寄存器地址为0x01)的低3位修改为111,以禁能CLKOUT输出,范例程序详见程序清单13.62。
程序清单13.62 禁能CLKOUT输出的范例程序
程序中,首先将p_dev转换为了PCF85063设备类型指针,并通过p_dev获得了设备信息,以获取其中的从机地址信息。然后使用读取接口读取出地址0x01的值,若其低三位不为111,则修改为111并重新写入寄存器中。
该段程序作为PCF85063的初始化程序,应该处于哪一阶段呢,由于I²C是一种相对低速的通信接口,读写数据往往比较耗时(毫秒级别),因此,建议放在第三阶段中。为此,可以完善第三阶段初始化函数的实现,详见程序清单13.63。
程序清单13.63 第三阶段初始化函数的实现
程序中,为了程序的简洁和可读性,使用宏的形式对p_dev的强制转换、设备信息的获取以及寄存器地址常量进行了定义。
由于不再需要执行其他初始化操作,因此,第一阶段和第二阶段的初始化函数可以为空。
13.4.6 实现通用服务
PCF85063可以提供RTC服务,在提供RTC服务前,需要完成设备中rtc_serv的赋值,其类型为struct awbl_rtc_service,回顾其具体定义,详见程序清单13.64。
程序清单13.64 RTC服务类型定义(awbl_rtc.h)
1. p_next成员赋值
p_next用于系统组织多个RTC服务,对于单个RTC服务的提供者,其值设置为NULL。详见程序清单13.65。
程序清单13.65 p_next成员的赋值
2. p_servinfo成员赋值
p_servinfo用于指向RTC服务信息,RTC服务信息由用户通过设备信息提供,基于此,其值直接设置为指向设备信息中的rtc_servinfo即可,详见程序清单13.66。
程序清单13.66 p_servinfo成员的赋值
3. p_servopts成员赋值
p_servopts是实现RTC服务的核心,其定义了RTC抽象方法,驱动需要实现这些抽象方法, struct awbl_rtc_servopts类型的定义详见程序清单13.57,其中定义了三个抽象方法:
time_get:获取时间
time_set:设置时间
dev_ctrl:控制函数,当前未使用,保留给后续扩展,设置为NULL即可在具体实现前,可以先搭建好软件结构,详见程序清单13.67。
程序清单13.67 实现RTC服务中定义的抽象方法结构性代码
其中,__g_pcf85063_servopts的地址即可作为RTC服务中p_servopts的值。接下来,需要具体实现时间获取和时间设置函数。
在PCF85063中,地址0x04 ~ 0x0A的寄存器存储了时间信息,详见表13.10。对这些寄存器的读写即可完成时间信息的获取和设置。
表13.10 时间信息相关寄存器
注意,在寄存器中,数值的存储形式是BCD格式,即数值的十位和个位分别使用4位二进制数(一位十六进制数)进行表示。例如,秒值为23,则十位2使用4位二进制表示,即0010,个位3使用4位二进制表示,即0011,最终的结果即为0010 0011。对于秒值,由于十位的最大值为5,需要使用3位二进制表示,因此,秒值占用的实际有效位为7位(十位占用3位,个位占用4位)。不同秒值对应的寄存器值详见表13.11。
表13.11 秒值对应的寄存器值
分值与秒值的有效范围相同,占用7位有效位;对于小时值,PCF85063支持24小时制(默认)和12小时制,但在AWorks平台中,细分时间统一使用了24小时制,基于此,PCF85063也仅使用默认的24小时制,此时,小时值的有效范围为0 ~ 23,由于十位的最大值为2,需要使用2位二进制表示,因此,小时值占用的实际有效位为6位(十位占用2位,个位占用4位);对于日期值,其有效范围为1 ~ 31,十位最大值为3,需要使用2位二进制表示,因此,日期值占用的实际有效位为6位(十位占用2位,个位占用4位);对于星期值,其有效范围为0 ~ 6,仅包含个位,且最大值为6,只需要使用3位二进制数即可表示,因此,星期值占用的实际有效位为3位(仅个位占用3位);对于月份值,其有效范围为1 ~ 12,十位最大值为1,需要使用1位二进制表示,因此,月份值占用的实际有效位为5位(十位占用1位,个位占用4位);对于年份值,8位寄存器值全部用于表示年份值,十位和个位均占用4位,对于BCD码,使用4位二进制表示一位十进制数,个位和十位的最大值均为9,因此,年份值的有效范围为0 ~ 99。
为便于BCD码数据和实际数值之间相互转换,在AWorks中,定义了两个宏辅助宏,详见程序清单13.68。
程序清单13.68 BCD码转换辅助宏(aw_common.h)
对于获取时间,可以读取出各个寄存器的值,然后为p_tm细分时间结构体中的各个成员赋值,范例程序详见程序清单13.69。
程序清单13.69 时间获取函数的实现范例
程序中,将p_cookie强制转换为指向设备自身的指针。这是由于在为RTC服务中的p_cookie成员赋值时,往往将其赋值为指向设备自身的指针,下一小节将详细介绍。
读取时间信息时,直接从秒寄存器开始,连续读取了7个寄存器的值,以便一次性读取出所有时间信息。读取的时间值为BCD码,在为细分时间赋值前需要将其转换为实际数值。特别地,在细分时间中,tm_year是从1900年开始计算的,而PCF85063的年值有效范围为0 ~ 99,实际年份的表示范围则为1900 ~ 1999,满足不了实际需求。为了扩大表示范围,当tm_year小于70时(即PCF85063中年值寄存器的值小于70时),将tm_year的值增加100。如此一来,当年值寄存器的值为0 ~ 69时,实际表示的年值为100 ~ 169,当值为70 ~ 99时,表示的年值依旧就是70 ~ 99,使得年值的范围扩大到了70 ~ 169,对应的年份范围即为1970 ~ 2069,1970也是很多操作系统中的时间起点。
时间设置是一个相反的过程,即将细分时间中的值设置到PCF85063的相应寄存器中,范例程序详见程序清单13.70。
程序清单13.70 时间设置函数的实现范例
程序中,首先将细分时间值依次存储到data数组中,然后一次性写入所有时间信息。值得注意的是,在细分时间中,tm_mon表示月份,其值为实际月份减一(有效值为0 ~ 11)。而在PCF85063中,月份寄存器中的有效值为1 ~ 12,表示的是实际月份,因此,在将细分时间值写入PCF85063的寄存器时,需要作加1操作,以将tm_mon转换为实际月份。特别地,在驱动中,将tm_year的范围限制在了70 ~ 169,以表示年份1970 ~ 2069。若tm_year的值超过该范围,则表示是无效时间。年值寄存器的有效范围为0 ~ 99,根据规则(小于70时加上100),年值为100 ~ 169时,寄存器的值应为0 ~ 69 ;年值为70 ~ 99时,寄存器的值保持不变,同样为70 ~ 99。年值寄存器的值不能超过100,大于100时,应该减去100,程序中,巧妙的将tm_year的值对100取余作为最终年值寄存器的值,完成了这一操作。
4. p_cookie成员赋值
p_cookie用于系统在调用设备实现的抽象方法时,“原封不动”的传递给各个抽象方法的p_cookie参数。这样一来,传入抽象方法中的p_cookie与RTC服务中的p_cookie是完全相同的。通常情况下,p_cookie都起到一个p_this的作用,用于指向设备自身,基于此,直接将RTC服务中p_cookie设置为p_this,详见程序清单13.71。
程序清单13.71 p_cookie成员的赋值
正因为如此,在程序清单13.69和程序清单13.70所示的RTC抽象方法的实现中,可以直接将p_cookie强制转换为指向设备自身的指针。
至此,清楚了RTC服务中各成员应该设置的具体值,可以在系统获取RTC服务时,再进行相关成员的赋值。
13.4.7 定义Method对象
已知获取RTC服务的Method类型为:awbl_rtcserv_get。为了使PCF85063可以向系统提供RTC服务,需要使用该类型定义Method对象,核心需要实现一个用于系统获取RTC服务的入口函数,范例程序详见程序清单13.72。
程序清单13.72 获取RTC服务的入口函数实现范例
基于此,可以完成一个Method对象的定义,即:
一个驱动提供的所有Method对象应该存放在一个列表中,由于PCF85063设备仅能提供RTC服务,因此,Method对象列表中仅包含一个用于获取RTC服务的Method对象,详见程序清单13.73。
程序清单13.73 PCF85063设备驱动Method对象列表定义
其中,__g_pcf85063_dev_methods即可作为基础驱动信息中p_methods的值。
13.4.8 定义驱动结构体常量,实现驱动注册函数
驱动信息常量的实际类型与设备所处的总线类型相关。PCF85063设备挂在I²C总线上,I²C总线上的所有设备驱动对应的信息结构体类型为awbl_i2c_drvinfo_t,其是直接从基础驱动信息类型派生而来的,具体定义详见程序清单13.74。
程序清单13.74 awbl_i2c_drvinfo_t类型定义(awbl_i2cbus.h)
由此可见,其并未扩展任何其它新的成员,和基础驱动信息是完全一样的,可以定义用于描述PCF85063驱动的信息常量,详见程序清单13.75。
程序清单13.75 定义描述PCF85063驱动的信息常量
用户若需使用该驱动,还需要将驱动注册到系统中,可以提供一个用于注册PCF85063驱动的专用函数,其实现详见程序清单13.76。
程序清单13.76 注册PCF85063驱动的专用函数
为便于查阅,PCF85063完整的驱动文件内容详见程序清单13.77和程序清单13.78。
程序清单13.77 PCF85063驱动头文件(awbl_pcf85063.h)
程序清单13.78 PCF85063驱动源文件(awbl_pcf85063.c)
全部0条评论
快来发表一下你的评论吧 !