引言
在嵌入式便携设备中,有些产品带有较大尺寸的显示屏,如无线数码相框、家庭信息终端、多媒体电话等。这些产品普遍都配有800×480分辨率的7寸屏,产品内置电池,未接外部电源时要求能持续工作2小时或更长时间。不同于手机、PDA、MP4等对电源管理要求严格的设备,这类产品只需实现电池电量检测、外接电源检测、充放电指示和低电量提示等功能。
嵌入式Linux操作系统以其开源及功能强大等特点,广泛应用于便携式产品中,但由于Linux操作系统只提供了电源检测驱动接口,实际使用中需针对具体芯片开发其驱动程序。本文为上述产品提供了低成本的电源检测硬件实现方案,阐述了基于Linux2.6.27内核的电源检测驱动设计方法。
1硬件方案
选用飞思卡尔半导体的MX27作为硬件平台。MX27基于ARM926EJS内核,处理器主频为399MHz,内存为128MB的mDDR,总线频率为133MHz,可以流畅地运行Linux操作系统。出于节省硬件成本的考虑,采用简单易用的电池电量检测和电源管理方案。图1为电池电量检测电路。
图1电池电量检测电路
MC34161是一个通用的电源检测芯片,芯片中有8个功能引脚。采用MX27的两个GPIO中断信号来检测电池电量的变化:Voltage_Monitor1,电池电压检测信号1,电池的电压正常工作范围为3.6~4.2V,当电池电压低于3.8V时该信号变为低电平;Voltage_Monitor2,电池电压检测信号2,当电池电压低于3.6V时该信号变为低电平,系统供电不足,应报警。
图2为外接直流电源检测电路。PWRJACK表示电源接口;MHC3216S501PA是磁珠,用来进行电源滤波和防电磁干扰;F1206FA4000V063T是保险丝,用来防止电路短路,从而保护主电路,实现了外部直流电源5V的插入检测功能。EXT_PWR_DET连到MX27的GPIO引脚上,当插入外部电源时,EXT_PWR_DET由低电平变为高电平,产生一个上升沿中断信号,触发MX27的中断。当外部电源拔出时,EXT_PWR_DET由高电平变为低电平,产生一个下降沿中断信号,触发MX27中断处理。
图2外接直流电源检测电路
图3为电池充电检测电路,实现了电池充电检测功能。ISL6294芯片有8个引脚,可实现电池充电检测功能。红色发光二极管LD1指示当前是否处于电池充电状态。Charge_DET连到MX27的GPIO引脚上,是低电平有效的充电检测信号,充电状态下为低电平;充满或拔出外部电源时变为高电平,产生一个上升沿中断信号,触发MX27中断处理。该电路实现简单,虽然不能精确地指示电池剩余电量,但是已能满足家庭使用的数码相框、多媒体电话等产品的电源管理要求。
图3电池充电检测电路
2电源检测驱动程序设计
在Linux内核中,设备驱动程序可使得某个特定的硬件响应一个定义良好的内部编程接口,与此同时它完全隐藏了设备的工作细节,应用程序对所有系统硬件的访问都必须通过驱动程序。从Linux2.6起引入了一套新的驱动管理和注册机制:platform_device和platform_driver。与传统的device driver机制相比, platform机制将设备本身的资源注册进内核,由内核统一管理。驱动程序中使用这些资源时,通过platform_device提供的标准接口进行申请并使用,提高了驱动和资源管理的独立性,拥有更好的可移植性。Linux中的大部分设备驱动都可以使用这套机制,设备用platform_device表示,驱动用platform_driver进行注册。
2.1两个重要的结构
2.1.1platform_device
platform_device结构用来描述设备的名称、资源信息等,在/include/linux/platform_device.h中定义:
struct platform_device {
const char *name;
int id;
struct device dev;
u32 num_resources;
struct resource *resource;
};
其中,name为设备名称,id表示设备编号,dev表示设备的具体状况,num_resources表示设备资源的分组数目,resource表示设备所拥有的资源。该结构用于向系统核心层定义一个设备。
2.1.2platform_driver
为了统一管理系统的外设资源,platfrom_driver结构为驱动程序提供了统一的接口来访问这些资源,这就是结构中的函数指针,它们指向驱动程序中实现特定操作的函数。platform_driver在/include/linux/platform_device.h中定义:
struct platform_driver {
int (*probe)(struct platform_device *);
int (*remove)(struct platform_device *);
void (*shutdown)(struct platform_device *);
int (*suspend)(struct platform_device *, pm_message_t state);
int (*suspend_late)(struct platform_device *, pm_message_t state);
int (*resume_early)(struct platform_device *);
int (*resume)(struct platform_device *);
struct device_driver driver;
};
2.2驱动程序设计
电源检测驱动程序主要实现以下功能:自动检测当前是电池供电或外部直流电源供电;电池电压测量;电池低电量告警;电池充电检测。
2.2.1硬件平台核心层驱动
(1) 定义platform_device
系统使用的内核版本为Linux2.6.27,在arch/arm/machmx27/mx27ads.c中定义电源设备:
static struct platform_device v515_power_device={
.name="v515power",
.id=-1,
.dev={
.platform_data=&power_pdata,
},
};
设备名称为v515power,dev的platform_data指向一个结构power_pdata,其定义如下:
static char *v515_ac_supplied_to[]={ "mainbattery"};
static struct pda_power_pdata power_pdata={
.init=v515_power_init,
.is_ac_online=v515_power_ac_online,
.exit=v515_power_exit,
.supplied_to=v515_ac_supplied_to,
.num_supplicants=ARRAY_SIZE(v515_ac_supplied_to),
.polling_interval=1000,
};
其中,init函数用于初始化4个硬件GPIO信号(向系统申请GPIO资源,设置GPIO类型、信号方向等);exit函数用于释放GPIO资源;is_ac_online函数用于读取EXT_PWR_DET这个GPIO的状态,判断此时有无外部电源插入。
(2) 注册platform_device
定义了platform_device后需将其添加到系统中,在arch/arm/machmx27/mx27ads.c的初始化函数中调用“platform_device_register(&v515_power_device);”向内核注册这个电源设备。
platform_device_register函数原型为:
int platform_device_register(struct platform_device *pdev) {
device_initialize(&pdev﹥dev);
return platform_device_add(pdev);
}
注册一个platform_device,首先初始化这个platform_device,然后将其添加到系统中。输入参数pdev指向一个静态的全局设备。通过以上操作在系统核心层注册一个platform_device。
2.2.2电源设备驱动
(1) 定义和注册platform_driver
注册了一个platform_device后,需定义一个platform_driver来驱动此设备。在drivers/power/v515_power.c中定义一个驱动,并通过pda_power_init函数对其注册。
static struct platform_driver pda_power_pdrv={
.driver={
.name="v515power",},
.probe=pda_power_probe,
.remove=pda_power_remove,
.suspend=pda_power_suspend,
.resume=pda_power_resume,
};
static int __init pda_power_init(void) {
return platform_driver_register(&pda_power_pdrv);
}
内核使用platform_driver结构访问驱动中的函数,因此要将电源检测驱动中用到的函数指针指向具体函数,结构体pda_power_pdrv中定义的每一个接口其作用如下:pda_power_probe,实现中断申请、电源注册;pda_power_remove,实现中断释放、电源注销;pda_power_suspend,实现电源管理,设备休眠进入低功耗状态;pda_power_resume,设备唤醒,进入正常工作状态。
函数pda_power_init在加载驱动时被调用。执行platform_driver_register函数时,系统首先检查pda_power_pdrv的name,判断是否与已注册的v515_power_device同名。若相同,说明驱动实现的设备名已在内核设备列表中,则调用pda_power_pdrv的probe函数。
(2) probe函数的任务
probe是设备驱动程序中重要的入口函数,驱动程序的许多任务及硬件初始化工作都将在该函数里完成。
① 申请中断。设备在I/O请求结束或其他状态发生改变时产生中断,此时系统自动调用中断处理子程序,电源驱动采用中断方式检测系统电源状态。系统通过调用request_irq( ) 函数申请中断,该函数原型定义如下:
int request_irq( unsigned int irq, void ( *handler) (), unsigned long flags, const char *device, void *dev_id);
其中,参数irq 表示申请的硬件中断号,handler 为系统注册的中断处理子程序,flags 表示中断处理子程序的特性,device 为指向设备名称的字符指针,dev_id 用来标识产生中断的设备。
以申请外部电源插入中断为例,函数的调用形式如下:
request_irq(V515_AC_IN_INT,power_changed_isr,0,"ac",&pda_psy_ac);
其中,V515_AC_IN_INT为系统的GPIO中断号;power_changed_isr为中断处理子程序;pda_psy_ac是结构struct power_supply类型的变量,供中断处理子程序使用。
申请中断前,还需设置好该GPIO中断的类型,如:
set_irq_type(V515_AC_IN_INT,IRQT_BOTHEDGE);
其中,IRQT_BOTHEDGE表示这个GPIO中断在信号的上升沿和下降沿都会触发产生中断信号。
② 初始化定时器。定时器是GPIO中断处理的重要帮手,为了使中断处理函数的执行时间尽可能短,可将大部分中断后处理放到timer_hander定时器处理函数中完成。处理GPIO中断采用定时器还出于一个重要考虑,当GPIO按键或其他来源的电平刚产生中断时,容易伴随尖峰或毛刺,信号不稳定,而定时器处理函数至少要在一个系统滴答(jiffies,1个jiffies=10ms)之后才会执行,这个短暂的延时是必要的。可以根据不同的中断设置合适的timeout延时时间,比如外部电源插入中断,可以设置为中断产生500ms之后,执行定时器中断。初始化定时器需调用函数:
setup_timer(&charger_timer, charger_timer_func, 0);
其中,charger_timer是一个timer_list类型的结构体变量,需定义为全局变量;charger_timer_func是定时器处理函数。
③ 向power supply core注册电源设备。内核drivers/power下是power supply class support的代码,标准内核已经包含有power_supply_core.c和power_supply_sysfs.c等文件,电源设备驱动需要向power supply core去注册用户设备,才能在文件系统的/sys/class/power_supply/下生成需要的设备信息文件。
power supply core提供函数power_supply_register供调用。以注册外部电源设备AC为例,调用:power_supply_register(&pdev﹥dev, &pda_psy_ac);
其中,pda_psy_ac是一个已定义并初始化的struct power_supply结构类型的全局变量,代表外部电源AC。执行以上语句后,即可完成AC注册。
3驱动程序测试
完成后的驱动被编译进内核,将压缩的内核zImage烧写到开发板的NAND Flash中,完成后重启系统。首先,通过插拔外接电源进行测试。插拔外接电源时,可以看到/sys/class/power_supply/ac/online文件的内容会变为1或0;在MX27开发板上接7寸800×480的LCD显示屏,并烧写一个Android1.6文件系统,完成后重启系统,可以看到Android主界面右上角的电池图标会变为充电状态或电池供电状态。然后,进行电池供电测试。系统运行一段时间后,可以看到电压检测中断产生,Android右上角的电池图标也会发生变化(由满格变为半格)。此时,连接外部电源,可以检测到充电中断,同时电池图标又变为充电图标。
结语
设备驱动程序是一个函数和数据结构的集合,它的目的是实现一个管理设备的接口,内核利用这个接口请求驱动程序实现对设备的控制操作。在Linux2.6内核下,通过platform机制开发底层驱动的一般流程为:定义 platform_device→注册 platform_device→定义platform_driver→注册 platform_driver。设备驱动程序所提供的函数入口点,由platform_driver向系统说明,用户开发自己的设备驱动程序,就是根据驱动程序的功能实现platform_driver 结构中的函数接口。