关键词:android
电池关机充电
androidboot.mode charger 关机充电 充电画面显示
平台信息:
内核:linux2.6/linux3.0
系统:android/android4.0
平台:S5PV310(samsungexynos 4210)
电池的基本原理;
android关机充电流程、充电画面显示;
Android开机充电流程,电池电量、低电信息是怎么处理的;
上一篇我们讲了锂电池的充放电的流程和电池的一些特性,这一节我们重点说一下android关机充电是怎么、充电画面显示是怎么实现的,这个在工作中也比较有用,我们开始做这一块的时候也走了不少的弯路。我记得我们做adnroid2.3的时候,关机状态和充电logo显示是在uboot中做的。应该是有两种做法,回头我再看下uboot中做画面显示那一块是怎么做的,这一节我们重点说系统中的充电logo显示。
一、android正常开机流程、关机充电流程
在写这篇文章之前我们先看两个流程:正常开机流程,关机充电系统启动流程
1、正常开机流程,按开机键。
可大致分成三部分
(1)、OS_level:UBOOT、kenrel、init这三步完成系统启动;
(2)、Android_level:这部分完成android部的初始化;
(3)、Home Screen:这部分就是我们看到的launcher部分。
2、关机充电系统启动流程
与前面相比,这个流程只走到init这一部分,就没有往后走了,这部分我们会在后面的代码中分析。
二、关机充电逻辑硬件逻辑
1、插入DC,charger IC从硬件上唤醒系统,相当于长按开机键开机。
下面这部分是charger IC连接系统的控制部分。
三、软件逻辑。
DC插入,其实相当于关机状态下“按开机键”开机。第一步要走UBOOT、kernel 、android init这一流程。
1、UBOOT
UBOOT启动代码我们不在这里详细分析,这里我们只要注意二个问题:
a:如何判断是DC插入;
b:设定setenv("bootargs", "androidboot.mode=charger"),androidboot.mode这个参数相当重要,这个参数决定系统是正常启动、还是关机充电状态。
Uboot/board/samsung/smdk4212/smkd4212.c
-
int
board_late_init (void)
-
{
-
int keystate = 0;
-
printf("check start
mode\n");
-
if ((*(int
*)0x10020800==0x19721212) || (*(int
*)0x10020804==0x19721212) -
|| (*(int
*)0x10020808==0x19721212)) //(1)、检查是否有DC插入;
-
{
-
setenv ("bootargs",
"");//(2)、没有DC插入;
-
}
else {//DC插入
-
int tmp=*(int
*)0x11000c08; -
*(int
*)0x10020800=*(int
*)0x10020804=0x19721212; -
*(int
*)0x11000c08=(tmp&(~0xc000))|0xc000; -
udelay(10000);
-
if ((*(int
*)0x11000c04 & 0x80)!=0x80 && INF_REG4_REG != 0xf) { -
setenv ("bootargs",
"androidboot.mode=charger");//(3)、设定bootargs为charger状态
-
printf("charger
mode\n");
-
}
else {
-
setenv ("bootargs",
"");
-
}
-
*(int
*)0x11000c08=tmp; -
}
-
#ifdef CONFIG_CPU_EXYNOS4X12
-
int charge_status=CheckBatteryLow();//(4)、检查电池电量;
-
keystate=board_key_check();//(5)、检查按键状态;
-
// fuse bootloader
-
if(second_boot_info != 0) {
-
boot_symbol=1;
-
INF_REG2_REG =0x8;
-
run_command(CONFIG_BOOTCMD_FUSE_BOOTLOADER, NULL);
-
}
-
if((INF_REG4_REG == 0xd)) {
-
// reboot default
-
char buf[10];
-
sprintf(buf,
"%d", CONFIG_BOOTDELAY);
-
setenv ("bootdelay",
buf); -
setenv ("reserved",
NULL); -
saveenv();
-
}
else
if((INF_REG4_REG
== 0xe) || keystate == (0x1 | 0x2)) {//(6)、按键进入fastboot模式;
-
// reboot bootloader
-
boot_symbol=1;
-
INF_REG2_REG =0x8;
-
printf("BOOTLOADER
- FASTBOOT\n");
-
setenv ("reserved",
"fastboot");
-
setenv ("bootdelay",
"0");
-
}
else
if((INF_REG4_REG
== 0xf) || keystate == (0x1 | 0x2 | 0x4)) {//(7)、按键进入recovery模式;
-
// reboot recovery
-
printf("BOOTLOADER
- RECOVERY\n");
-
boot_symbol=1;
-
INF_REG2_REG =0x8;
-
setenv ("reserved",
CONFIG_BOOTCMD_RECOVERY); -
setenv ("bootdelay",
"0");
-
}
else
-
if(keystate == (0x1 | 0x4) || second_boot_info != 0 || partition_check()) {//(8)、按键进入卡升级模式;
-
// 2nd boot
-
printf("BOOTLOADER
- 2ND BOOT DEVICE\n");
-
boot_symbol=1;
-
INF_REG2_REG =0x8;
-
setenv ("bootcmd",
CONFIG_BOOTCOMMAND); -
setenv ("reserved",
CONFIG_BOOTCMD_FUSE_RELEASE); -
setenv ("bootdelay",
"0");
-
}
else {//(9)、正常启动;
-
// normal case
-
char buf[10];
-
sprintf(buf,
"%d", CONFIG_BOOTDELAY);
-
setenv ("bootdelay",
buf); -
}
-
INF_REG4_REG = 0;
-
return 0;
-
}
(1)、检查是否有DC插入;
-
if
((*(int
*)0x10020800==0x19721212) || (*(int
*)0x10020804==0x19721212) -
(*(int
*)0x10020808==0x19721212))
这部分检查寄存器的值。
(2)、没有DC插入;
(3)、设定bootargs为charger状态
-
if
((*(int
*)0x11000c04 & 0x80)!=0x80 && INF_REG4_REG != 0xf) { -
setenv ("bootargs",
"androidboot.mode=charger");
这是这部分的重点,如果能过寄存器判断是DC插入,把androidboot.mode设定为charger状态。
以下这部分根据需要加入,通过判断不同的情况进入不同的功能,如fastboot\revovery…………,这部分不做详细解释。
(4)、检查电池电量;
这个在正常开机状态下,如果检测电量太低,则不开机,这部分代码就不做分析。
(5)、检查按键状态;
我们这个平台有几种模式:fastboot\recovery\卡升级等……
(6)、按键进入fastboot模式;
(7)、按键进入recovery模式;
(8)、按键进入卡升级模式
(9)、正常启动;
2、kernel
这部分和正常启动是一样的。
3、init
前面所有的描述其实只有一点和正常启动不太一样,那就是在UBOOT中把androidboot.mode设定为charger状态,内核正常流程启动,然后到init时要对charger这种状态处理。
system\core\init\init.c
-
int
main(int
argc, char
**argv) -
{
-
………………
-
action_for_each_trigger("early-init",
action_add_queue_tail); -
-
queue_builtin_action(wait_for_coldboot_done_action,
"wait_for_coldboot_done");
-
queue_builtin_action(property_init_action,
"property_init");
-
queue_builtin_action(keychord_init_action,
"keychord_init");
-
queue_builtin_action(console_init_action,
"console_init");
//(1)、显示initlogo.rle,也就是android第二张图片;
-
queue_builtin_action(set_init_properties_action,
"set_init_properties");
-
-
/* execute all the boot actions to get us started */
-
action_for_each_trigger("init",
action_add_queue_tail); -
-
/* skip mounting filesystems in charger mode */
-
if (strcmp(bootmode,
"charger") != 0) {//(2)、这里就是UBOOT中设定的bootmode,如果是charger模式,跳过下面初始化;
-
action_for_each_trigger("early-fs",
action_add_queue_tail); -
action_for_each_trigger("fs",
action_add_queue_tail); -
action_for_each_trigger("post-fs",
action_add_queue_tail); -
action_for_each_trigger("post-fs-data",
action_add_queue_tail); -
}
-
-
queue_builtin_action(property_service_init_action,
"property_service_init");
-
queue_builtin_action(signal_init_action,
"signal_init");
-
queue_builtin_action(check_startup_action,
"check_startup");
-
-
if (!strcmp(bootmode,
"charger")) {//(3)、如果为charger,则调用charger.c。
-
action_for_each_trigger("charger",
action_add_queue_tail); -
}
else {
-
action_for_each_trigger("early-boot",
action_add_queue_tail); -
action_for_each_trigger("boot",
action_add_queue_tail); -
}
-
……………………
-
}
(1)、显示initlogo.rle,也就是android第二张图片;
queue_builtin_action(console_init_action,"console_init");调用console_init_action
-
static
int
console_init_action(int
nargs, char
**args) -
{
-
int fd;
-
char tmp[PROP_VALUE_MAX];
-
if (console[0]) {
-
snprintf(tmp,
sizeof(tmp),
"/dev/%s", console);
-
console_name = strdup(tmp);
-
}
-
fd = open(console_name, O_RDWR);
-
if (fd >= 0)
-
have_console = 1;
-
close(fd);
-
if( load_565rle_image(INIT_IMAGE_FILE) ) {//这里定义rle文件的名称#define
INIT_IMAGE_FILE "/initlogo.rle"
-
fd = open("/dev/tty0",
O_WRONLY); -
if (fd >= 0) {//如果没有这张图片,就显示android字样,在屏幕左上角;
-
const
char
*msg; -
msg =
"\n"
-
"\n"
-
"\n"
// console is 40 cols x 30 lines
-
"\n"
-
"\n"
-
"\n"
-
"\n"
-
"\n"
-
"\n"
-
"\n"
-
" A N D R O I D ";
-
write(fd, msg, strlen(msg));
-
close(fd);
-
}
-
}
-
return 0;
-
}
(2)、这里就是UBOOT中设定的bootmode,如果是charger模式,跳过下面初始化;
-
/*
skip mounting filesystems in charger mode */
-
if
(strcmp(bootmode, "charger")
!= 0) { -
action_for_each_trigger("early-fs",
action_add_queue_tail); -
action_for_each_trigger("fs",
action_add_queue_tail); -
action_for_each_trigger("post-fs",
action_add_queue_tail); -
action_for_each_trigger("post-fs-data",
action_add_queue_tail); -
}
(3)、如果为charger,则调用charger.c
-
action_for_each_trigger("charger",
action_add_queue_tail);
我们在后面细分charger这部分。
4、charger.c
这部分就是我们充电部分,充电画面显示的实现。
system\core\charger\charger.c
-
int
main(int
argc, char
**argv) -
{
-
………………
-
klog_set_level(CHARGER_KLOG_LEVEL);
-
dump_last_kmsg();
-
LOGI("---------------
STARTING CHARGER MODE ---------------\n");
-
-
gr_init();
-
gr_font_size(&char_width, &char_height);
//(1)、初始化graphics,包括buf大小;
-
-
ev_init(input_callback, charger);//(2)初始化按键;
-
-
fd = uevent_open_socket(64*1024,
true);
-
if (fd >= 0) {
-
fcntl(fd, F_SETFL, O_NONBLOCK);
-
ev_add_fd(fd, uevent_callback, charger);
-
}
-
-
charger->uevent_fd = fd;
-
coldboot(charger,
"/sys/class/power_supply",
"add");//(3)、创建/sys/class/power_supply结点,把socket信息通知应用层;
-
-
ret = res_create_surface("charger/battery_fail",
&charger->surf_unknown); -
if (ret < 0) {
-
LOGE("Cannot
load image\n");
-
charger->surf_unknown = NULL;
-
}
-
for (i = 0; i < charger->batt_anim->num_frames; i++) {//(4)、这里是显示charger
logo,res_create_surface显示图片函数;
-
struct frame *frame = &charger->batt_anim->frames[i];
-
ret = res_create_surface(frame->name, &frame->surface);
-
if (ret < 0) {
-
LOGE("Cannot
load image %s\n", frame->name);
-
/* TODO: free the already allocated surfaces... */
-
charger->batt_anim->num_frames = 0;
-
charger->batt_anim->num_cycles = 1;
-
break;
-
}
-
}
-
ev_sync_key_state(set_key_callback, charger);
-
gr_fb_blank(true);
-
-
charger->next_screen_transition = now - 1;
-
charger->next_key_check = -1;
-
charger->next_pwr_check = -1;
-
reset_animation(charger->batt_anim);
-
kick_animation(charger->batt_anim);
-
event_loop(charger);//(5)、event_loop循环,电池状态,检测按键是否按下;
-
return 0;
-
-
}
(1)、初始化graphics,包括buf大小
android/bootable/recovery/minui/graphics.c
gr_init():minui/graphics.c[settty0 to graphic mode, open fb0],设制tty0为图形模式,打开fb0;
-
int
gr_init(void)
-
{
-
gglInit(&gr_context);
-
GGLContext *gl = gr_context;
-
gr_init_font();
-
gr_vt_fd = open("/dev/tty0",
O_RDWR | O_SYNC); -
if (gr_vt_fd < 0) {
-
// This is non-fatal; post-Cupcake kernels don't have tty0.
-
perror("can't
open /dev/tty0");
-
-
}
else
if
(ioctl(gr_vt_fd, KDSETMODE, (void*)
KD_GRAPHICS)) { -
// However, if we do open tty0, we expect the ioctl to work.
-
perror("failed
KDSETMODE to KD_GRAPHICS on tty0");
-
gr_exit();
-
return -1;
-
}
-
gr_fb_fd = get_framebuffer(gr_framebuffer);
-
if (gr_fb_fd < 0) {
-
gr_exit();
-
return -1;
-
}
-
get_memory_surface(&gr_mem_surface);
-
fprintf(stderr,
"framebuffer: fd %d (%d x %d)\n",
-
gr_fb_fd, gr_framebuffer[0].width, gr_framebuffer[0].height);
-
/* start with 0 as front (displayed) and 1 as back (drawing) */
-
gr_active_fb = 0;
-
set_active_framebuffer(0);
-
gl->colorBuffer(gl, &gr_mem_surface);
-
gl->activeTexture(gl, 0);
-
gl->enable(gl, GGL_BLEND);
-
gl->blendFunc(gl, GGL_SRC_ALPHA, GGL_ONE_MINUS_SRC_ALPHA);
-
gr_fb_blank(true);
-
gr_fb_blank(false);
-
return 0;
-
-
}
(2)android/bootable/recovery/minui/events.c
ev_init():minui/events.c[open /dev/input/event*]打开 /dev/input/event*
这部分是在,充电状态下,按键操作的初始化,比如:短按显示充电logo,长按开机,初始化代码如下。
-
int
ev_init(ev_callback input_cb, void
*data) -
{
-
DIR *dir;
-
struct dirent *de;
-
int fd;
-
dir = opendir("/dev/input");//打开驱动结点;
-
if(dir != 0) {
-
while((de = readdir(dir))) {
-
unsigned
long ev_bits[BITS_TO_LONGS(EV_MAX)];
-
//
fprintf(stderr,"/dev/input/%s\n", de->d_name);
-
if(strncmp(de->d_name,"event",5))
continue;
-
fd = openat(dirfd(dir), de->d_name, O_RDONLY);
-
if(fd < 0)
continue;
-
/* read the evbits of the input device */
-
if (ioctl(fd, EVIOCGBIT(0,
sizeof(ev_bits)),
ev_bits) < 0) { -
close(fd);
-
continue;
-
}
-
/* TODO: add ability to specify event masks. For now, just assume
-
* that only EV_KEY and EV_REL event types are ever needed. */
-
if (!test_bit(EV_KEY, ev_bits) && !test_bit(EV_REL, ev_bits)) {
-
close(fd);
-
continue;
-
}
-
ev_fds[ev_count].fd = fd;
-
ev_fds[ev_count].events = POLLIN;
-
ev_fdinfo[ev_count].cb = input_cb;
-
ev_fdinfo[ev_count].data = data;
-
ev_count++;
-
ev_dev_count++;
-
if(ev_dev_count == MAX_DEVICES)
break;
-
}
-
}
-
return 0;
-
}
(3)、创建/sys/class/power_supply结点,把socket信息通知应用层
uevent_open_socket这个函数是通过kobject_uevent的方式通知的应用层,就是往一个socket广播一个消息,只需要在应用层打开socket监听NETLINK_KOBJECT_UEVENT组的消息,就可以收到了,主要是创建了socket接口获得uevent的文件描述符,然后触发/sys/class/power_supply目录及其子目录下的uevent,然后接受并创建设备节点,至此设备节点才算创建。
(4)、这里显示charger logo,res_create_surface显示图片函数;
res_create_surface:minui/resource.c[create surfaces for all bitmaps used later, include icons, bmps]
创建surface为所以的位图,包括图标、位图。 这些图片的位置为:system\core\charger\images
(5)、event_loop循环,电池状态,检测按键是否按下;
5、event_loop
这个函数判断按键状态,DC是否插拔。如果长按开机:执行android_reboot(ANDROID_RB_RESTART,0, 0);如果拔出DC:执行android_reboot(ANDROID_RB_POWEROFF,0, 0);
-
static
void
event_loop(struct
charger *charger) -
{
-
int ret;
-
while (true)
{ -
int64_t now = curr_time_ms();//(1)、获得当前时间;
-
LOGV("[%lld]
event_loop()\n", now);
-
handle_input_state(charger, now);//(2)、检查按键状态;
-
handle_power_supply_state(charger, now);//
(3)、检查DC是否拔出;
-
/* do screen update last in case any of the above want to start
-
* screen transitions (animations, etc) -
*/
-
update_screen_state(charger, now);//(4)、对按键时间状态标志位的判断,显示不同电量的充电logo;
-
wait_next_event(charger, now);
-
}
-
}
(1)、获得当前时间;
int64_t now = curr_time_ms();
这个时间来判断,有没有屏幕超时,如果超时关闭屏幕充电logo显示。
(2)、检查按键状态;
-
static
void
handle_input_state(struct
charger *charger, int64_t now) -
{
-
process_key(charger, KEY_POWER, now);
-
if (charger->next_key_check != -1 && now > charger->next_key_check)
-
charger->next_key_check = -1;
-
}
-
我们再看下:process_key(charger, KEY_POWER, now);
-
static
void
process_key(struct
charger *charger, int
code, int64_t now) -
{
-
………………
-
if (code == KEY_POWER) {
-
if (key->down) {
-
int64_t reboot_timeout = key->timestamp + POWER_ON_KEY_TIME;
-
if (now >= reboot_timeout) {//如果长按power键,就重新启动,也就是重启开机;
-
LOGI("[%lld]
rebooting\n", now);
-
android_reboot(ANDROID_RB_RESTART, 0, 0);//重启命令;
-
}
-
………………
-
}
-
-
key->pending =
false;
-
}
(3)、检查DC是否拔出;
handle_power_supply_state(charger, now);
-
static
void
handle_power_supply_state(struct
charger *charger, int64_t now) -
{
-
if (charger->num_supplies_online == 0) {
-
if (charger->next_pwr_check == -1) {
-
charger->next_pwr_check = now + UNPLUGGED_SHUTDOWN_TIME;
-
LOGI("[%lld]
device unplugged: shutting down in %lld (@ %lld)\n",
-
now, UNPLUGGED_SHUTDOWN_TIME, charger->next_pwr_check);
-
}
else
if
(now >= charger->next_pwr_check) { -
LOGI("[%lld]
shutting down\n", now);
-
android_reboot(ANDROID_RB_POWEROFF, 0, 0);//如果DC拔出,则关机;
-
}
-
………………
-
}
(4)、对按键时间状态标志位的判断,显示不同电量的充电logo;
update_screen_state(charger, now);
这个函数比较长了,其实做用就是:我们在状态的过程中,充电logo的电量是要增加的,比如电量是20%时,要从第一格开始闪烁;如果是80%时,则要从第三格开始闪烁,电量显示就是通过这个函数来计算实现的。
-
static
void
update_screen_state(struct
charger *charger, int64_t now) -
{
-
struct animation *batt_anim = charger->batt_anim;
-
int cur_frame;
-
int disp_time;
-
-
if (!batt_anim->run || now < charger->next_screen_transition)
-
return;
-
-
/* animation is over, blank screen and leave */
-
if (batt_anim->cur_cycle == batt_anim->num_cycles) {
-
reset_animation(batt_anim);
-
charger->next_screen_transition = -1;
-
gr_fb_blank(true);
-
LOGV("[%lld]
animation done\n", now);
-
return;
-
}
-
-
disp_time = batt_anim->frames[batt_anim->cur_frame].disp_time;
-
-
/* animation starting, set up the animation */
-
if (batt_anim->cur_frame == 0) {
-
int batt_cap;
-
int ret;
-
-
LOGV("[%lld]
animation starting\n", now);
-
batt_cap = get_battery_capacity(charger);
-
if (batt_cap >= 0 && batt_anim->num_frames != 0) {
-
int i;
-
-
/* find first frame given current capacity */
-
for (i = 1; i < batt_anim->num_frames; i++) {
-
if (batt_cap < batt_anim->frames[i].min_capacity)
-
break;
-
}
-
batt_anim->cur_frame = i - 1;
-
-
/* show the first frame for twice as long */
-
disp_time = batt_anim->frames[batt_anim->cur_frame].disp_time * 2;
-
}
-
-
batt_anim->capacity = batt_cap;
-
}
-
-
/* unblank the screen on first cycle */
-
if (batt_anim->cur_cycle == 0)
-
gr_fb_blank(false);
-
-
/* draw the new frame (@ cur_frame) */
-
redraw_screen(charger);
-
-
/* if we don't have anim frames, we only have one image, so just bump
-
* the cycle counter and exit -
*/
-
if (batt_anim->num_frames == 0 || batt_anim->capacity < 0) {
-
LOGV("[%lld]
animation missing or unknown battery status\n", now);
-
charger->next_screen_transition = now + BATTERY_UNKNOWN_TIME;
-
batt_anim->cur_cycle++;
-
return;
-
}
-
-
/* schedule next screen transition */
-
charger->next_screen_transition = now + disp_time;
-
-
/* advance frame cntr to the next valid frame
-
* if necessary, advance cycle cntr, and reset frame cntr -
*/
-
batt_anim->cur_frame++;
-
-
/* if the frame is used for level-only, that is only show it when it's
-
* the current level, skip it during the animation. -
*/
-
while (batt_anim->cur_frame < batt_anim->num_frames &&
-
batt_anim->frames[batt_anim->cur_frame].level_only)
-
batt_anim->cur_frame++;
-
if (batt_anim->cur_frame >= batt_anim->num_frames) {
-
batt_anim->cur_cycle++;
-
batt_anim->cur_frame = 0;
-
-
/* don't reset the cycle counter, since we use that as a signal
-
* in a test above to check if animation is over -
*/
-
}
-
}
下面是不能容量时显示logo的函数:
-
static
struct
frame batt_anim_frames[] = { -
{
-
.name =
"charger/battery_0",
-
.disp_time = 750,
-
.min_capacity = 0,
-
},
-
{
-
.name =
"charger/battery_1",
-
.disp_time = 750,
-
.min_capacity = 20,
-
},
-
{
-
.name =
"charger/battery_2",
-
.disp_time = 750,
-
.min_capacity = 40,
-
},
-
{
-
.name =
"charger/battery_3",
-
.disp_time = 750,
-
.min_capacity = 60,
-
},
-
{
-
.name =
"charger/battery_4",
-
.disp_time = 750,
-
.min_capacity = 80,
-
.level_only =
true,
-
},
-
{
-
.name =
"charger/battery_5",
-
.disp_time = 750,
-
.min_capacity = BATTERY_FULL_THRESH,
-
},
-
};