Android灯光系统-class和HAL

本文详细介绍了如何使用Android系统命令操作LED,包括点亮、闪烁和关闭。通过分析源码,展示了LED类程序的编写过程,包括分配led_classdev、设置参数和注册。此外,还阐述了Android灯光系统的HAL层实现,包括Java层、JNI层和Hal层的代码编写,并提供了修改Android.mk和内核驱动的步骤。最后,提到了通过logcat监控亮度变化的方法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

首先分析下如何使用android系统自带的命令去操作单板上的led,可以使用以下命令去验证:

点亮LED及查看亮度
echo 255 > /sys/class/leds/led1/brightness
cat /sys/class/leds/led1/brightness
cat /sys/class/leds/led1/max_brightness

闪烁
echo timer > /sys/class/leds/led1/trigger
echo 100 > /sys/class/leds/led1/delay_on
echo 200 > /sys/class/leds/led1/delay_off

关闭
echo 0 > /sys/class/leds/led1/delay_on

echo 0 > /sys/class/leds/led1/brightness

这里根据源码去分析一下闪烁的过程:

echo timer > /sys/class/leds/led1/trigger  // timer对应 ledtrig-timer.c

led_trigger_store // 1. 从trigger_list找出名为"timer"的trigger
    list_for_each_entry(trig, &trigger_list, next_trig) {
        if (!strcmp(trigger_name, trig->name)) {
            // 2. 调用
            led_trigger_set(led_cdev, trig);
                // 3. 把trigger放入led_classdev的trig_list链表里
                list_add_tail(&led_cdev->trig_list, &trigger->led_cdevs);
                led_cdev->trigger = trigger;
                // 4. 
                trigger->activate(led_cdev);
                   // 5. 对于"timer"
                   timer_trig_activate
                       // 6. 创建2个文件: delay_on, delay_off
                       device_create_file
                       device_create_file
                       led_blink_set // 让LED闪烁
                            led_set_software_blink


        }
    }

echo 100 > /sys/class/leds/led1/delay_on
led_delay_on_store
  state = simple_strtoul(buf, &after, 10);
    led_blink_set  // // 让LED闪烁
    led_cdev->blink_delay_on = state;

echo 200 > /sys/class/leds/led1/delay_off
led_delay_off_store
    state = simple_strtoul(buf, &after, 10);
    led_blink_set // 让LED闪烁
    led_cdev->blink_delay_off = state

编写led_class程序的过程如下:

  1. 分配led_classdev

  2. 设置 :
    led_cdev->max_brightness
    led_cdev->brightness_set
    led_cdev->flags
    led_cdev->brightness
    led_cdev->name

  3. 注册 : led_classdev_register

把 编写好的leds_nanoT3.c 放到drivers/leds
修改 drivers/leds/Makefile:
obj-y += leds_nanoT3.o

然后:make menuconfig

根据Makefile需要添加以下选项
CONFIG_LEDS_CLASS
CONFIG_LEDS_TRIGGERS
CONFIG_LEDS_TRIGGER_TIMER

-> Device Drivers
-> LED Support
[*] LED Class Support
[*] LED Trigger support
<*> LED Timer Trigger

make uImage


按照上面这个流程编写代码如下:

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <mach/gpio.h>
#include <linux/miscdevice.h>
#include <linux/device.h>
#include <linux/platform_device.h>
#include <asm/io.h>
#include <linux/regulator/consumer.h>
#include <linux/delay.h>
#include <linux/types.h>
#include <linux/gpio.h>
#include <linux/leds.h>
#include <linux/init.h>
#include <asm/irq.h>
#include <linux/of.h>
#include <linux/of_platform.h>
#include <linux/of_gpio.h>
#include <asm/uaccess.h>
#include <linux/slab.h>


/* 自定义一个结构体 */
struct led_classdev_nanoT3{
  struct led_classdev cdev;
  int gpio;
};


static struct led_classdev_nanoT3 *led_devs;

static void brightness_set_nano(struct led_classdev *led_cdev,
          enum led_brightness brightness)
{
  struct led_classdev_nanoT3 *dev = (struct led_classdev_nanoT3 *)led_cdev;

  led_cdev->blink_brightness = brightness;//设置亮度

  //这里只做一个亮和灭的处理
  if(brightness != LED_OFF)
    gpio_set_value(dev->gpio, 0);
  else
    gpio_set_value(dev->gpio, 1);

}

static int leds_nano_init(void)
{
  int i,ret;

  /* 1. alloc led_classdev */
  led_devs = kzalloc(sizeof(struct led_classdev_nanoT3) * 2, GFP_KERNEL);
  if(led_devs == NULL){
    printk("No memory for device\n");
    return -ENOMEM;
  }

  /* 设置为输出,并输出高电平(默认熄灭状态) */
  gpio_direction_output(32 + 12, 1);
  gpio_direction_output(32 + 11, 1);

  /* 对应的IO口 */
  led_devs[0].gpio = 32 + 11;
  led_devs[1].gpio = 32 + 12;

  /* class的名字 */
  led_devs[0].cdev.name = "led1";
  led_devs[1].cdev.name = "led2";    

  for(i=0; i<2 ;i++)//对每一个灯都做这些
  {

    /* 2. set */
    led_devs[i].cdev.max_brightness = LED_FULL; //255
    led_devs[i].cdev.brightness_set = brightness_set_nano;//设置LED函数
    led_devs[i].cdev.flags = LED_CORE_SUSPENDRESUME;
    led_devs[i].cdev.brightness = LED_OFF;//默认是0

    /* 3. led_classdev_register */
    ret = led_classdev_register(NULL, &led_devs[i].cdev);
    if(ret)
    {
      /* 错误的话需要把之前成功的驱逐掉 */
      i--;
      while(i >= 0)
      {
        led_classdev_unregister(&led_devs[i].cdev);
        i--;
      }
      kfree(led_devs);//释放内存
      return -EIO;
    }
  }

  return 0;
}

static void leds_nano_exit(void)
{
  int i;

  for(i=0; i<2; i++){
    led_classdev_unregister(&led_devs[i].cdev);
  }
  kfree(led_devs);//释放内存
}



module_init(leds_nano_init);
module_exit(leds_nano_exit);
/*"GPL" 是指明了 这是GNU General Public License的任意版本*/
MODULE_LICENSE("GPL");

写android灯光系统的hal程序

Java: frameworks/base/services/core/java/com/android/server/lights/LightsService.java
JNI: frameworks/base/services/core/jni/com_android_server_lights_LightsService.cpp
Hal: lights.c

默认配色:frameworks/base/core/res/res/values/config.xml
电池灯:frameworks/base/services/core/java/com/android/server/BatteryService.java
通知灯:frameworks/base/services/core/java/com/android/server/notification/NotificationManagerService.java

怎么写Lights Hal

  1. 实现一个名为HMI的hw_module_t结构体
  2. 实现一个open函数, 它会根据name返回一个light_device_t结构体
  3. 实现多个light_device_t结构体,每一个对应一个DEVICE
    light_device_t结构体里第1个成员是hw_device_t结构体, 紧接着一个set_light函数
struct light_device_t {
    struct hw_device_t common;
    int (*set_light)(struct light_device_t* dev,
            struct light_state_t const* state);
};

Android源代码里面有很多lights.c,但是都写的不好,从google上可以搜到索尼在某些手机上用的hal文件,我们可以基于这段代码上进行修改,代码我都做了注解,看起来比较简单,附上代码如下:

/*
 * Copyright (C) 2008 The Android Open Source Project
 * Copyright (C) 2011 Diogo Ferreira <defer@cyanogenmod.com>
 * Copyright (C) 2012 Andreas Makris <andreas.makris@gmail.com>
 * Copyright (C) 2012 The CyanogenMod Project <http://www.cyanogenmod.com>
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#define LOG_NDEBUG 0
#define LOG_TAG "lights"
#include <cutils/log.h>
#include <stdint.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <fcntl.h>
#include <pthread.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <hardware/lights.h>

/* LED驱动的路径 */
/* 我的单板上只有两个LED灯 */
char const*const RED_LED_FILE           = "/sys/class/leds/led1/brightness";
char const*const GREEN_LED_FILE         = "/sys/class/leds/led2/brightness";

char const*const RED_LED_FILE_TRIGGER   = "/sys/class/leds/led1/trigger";
char const*const GREEN_LED_FILE_TRIGGER = "/sys/class/leds/led2/trigger";

char const*const RED_LED_FILE_DELAYON   = "/sys/class/leds/led1/delay_on";
char const*const GREEN_LED_FILE_DELAYON = "/sys/class/leds/led2/delay_on";

char const*const RED_LED_FILE_DELAYOFF  = "/sys/class/leds/led1/delay_off";
char const*const GREEN_LED_FILE_DELAYOFF= "/sys/class/leds/led2/delay_off";

/* 背光驱动的路径 */
char const*const LCD_BACKLIGHT_FILE     = "/dev/backlight-1wire"; 

/* Synchronization primities */
static pthread_once_t g_init = PTHREAD_ONCE_INIT;
static pthread_mutex_t g_lock = PTHREAD_MUTEX_INITIALIZER;
/* Mini-led state machine */
static struct light_state_t g_notification;
static struct light_state_t g_battery;

/* 对驱动文件写入整数值 */
static int write_int (const char *path, int value) {
    int fd;
    static int already_warned = 0;
    fd = open(path, O_RDWR);
    if (fd < 0) {
        if (already_warned == 0) {
            ALOGE("write_int failed to open %s\n", path);
            already_warned = 1;
        }
        return -errno;
    }
    char buffer[20];
    int bytes = snprintf(buffer, sizeof(buffer), "%d\n", value);
    int written = write (fd, buffer, bytes);
    close(fd);
    return written == -1 ? -errno : 0;
}

/* 对驱动文件写入字符串 */
static int write_string (const char *path, const char *value) {
    int fd;
    static int already_warned = 0;
    fd = open(path, O_RDWR);
    if (fd < 0) {
        if (already_warned == 0) {
            ALOGE("write_string failed to open %s\n", path);
            already_warned = 1;
        }
        return -errno;
    }
    char buffer[20];
    int bytes = snprintf(buffer, sizeof(buffer), "%s\n", value);
    int written = write (fd, buffer, bytes);
    close(fd);
    return written == -1 ? -errno : 0;
}

/* Color tools */
/* 判断灯的颜色 */ 
static int is_lit (struct light_state_t const* state) {
    return state->color & 0x00ffffff;
}

/* 根据参数计算亮度值 */
static int rgb_to_brightness (struct light_state_t const* state) {
    int color = state->color & 0x00ffffff;
    return ((77*((color>>16)&0x00ff))
            + (150*((color>>8)&0x00ff)) + (29*(color&0x00ff))) >> 8;
}

/* The actual lights controlling section */
static int set_light_backlight (struct light_device_t *dev, struct light_state_t const *state) {

  /* 用这个状态(state)里面的RGB值用rgb_to_brightness函数里面的公式算出一个亮度值 */
    int brightness = rgb_to_brightness(state);

  /* 打印调试信息 */
    ALOGV("%s brightness=%d color=0x%08x", __func__,brightness,state->color);
    pthread_mutex_lock(&g_lock);

    /* brightness 0-255 */
    /* LCD_BACKLIGHT_FILE能接收是0-127,所以后面需除以2 */
    /* 把亮度(brightness)写入文件(LCD_BACKLIGHT_FILE) */
    write_int (LCD_BACKLIGHT_FILE, brightness/2);

    pthread_mutex_unlock(&g_lock);
    return 0;
}

static void set_shared_light_locked (struct light_device_t *dev, struct light_state_t *state) {
    int r, g, b;
    int delayOn,delayOff;

    /* 提取RGB值 */
    r = (state->color >> 16) & 0xFF;
    g = (state->color >> 8) & 0xFF;
    b = (state->color) & 0xFF;

  delayOn = state->flashOnMS;
    delayOff = state->flashOffMS;

    if (state->flashMode != LIGHT_FLASH_NONE) { /* 不相等则需要闪烁 */
        write_string (RED_LED_FILE_TRIGGER, "timer");
        write_string (GREEN_LED_FILE_TRIGGER, "timer");

        write_int (RED_LED_FILE_DELAYON, delayOn);
        write_int (GREEN_LED_FILE_DELAYON, delayOn);

        write_int (RED_LED_FILE_DELAYOFF, delayOff);
        write_int (GREEN_LED_FILE_DELAYOFF, delayOff);

    } 
    else { /* 不需要闪烁 */
        write_string (RED_LED_FILE_TRIGGER, "none");
        write_string (GREEN_LED_FILE_TRIGGER, "none");

    }
    write_int (RED_LED_FILE, r);
    write_int (GREEN_LED_FILE, g);
}

static void handle_shared_battery_locked (struct light_device_t *dev) {
    if (is_lit (&g_notification)) { /* 判断是否需要设置通知灯(is_lit里面会判断灯的颜色,如果为0就不需要设置,不为0就需要设置) */
        set_shared_light_locked (dev, &g_notification); /* 用通知灯来设置LED */
    } else { 
        set_shared_light_locked (dev, &g_battery); /* 否则才用电池灯来设置LED */
    }
}

static int set_light_battery (struct light_device_t *dev, struct light_state_t const* state) {

  /* 打印调试信息 */
    ALOGV("%s flashMode=%d onMS = %d offMS = %d color=0x%08x", __func__,state->flashMode,state->flashOnMS,state->flashOffMS,state->color);

  /* pthread是为了能互斥的操作LED */
    pthread_mutex_lock (&g_lock);
    g_battery = *state;
    handle_shared_battery_locked(dev);
    pthread_mutex_unlock (&g_lock);
    return 0;
}

/* 通知灯和电池灯共用一个硬件, 通知灯的优先级大于电池灯 */
static int set_light_notifications (struct light_device_t *dev, struct light_state_t const* state) {
    ALOGV("%s flashMode=%d onMS = %d offMS = %d color=0x%08x", __func__,state->flashMode,state->flashOnMS,state->flashOffMS,state->color);
    pthread_mutex_lock (&g_lock);
    g_notification = *state; /* 把状态值保存到全局变量里面 */
    handle_shared_battery_locked(dev); /* 设置灯状态 */
    pthread_mutex_unlock (&g_lock);
    return 0;
}

/* Initializations */
void init_globals () {
    pthread_mutex_init (&g_lock, NULL);
}

/* Glueing boilerplate */
static int close_lights (struct light_device_t *dev) {
    if (dev)
        free(dev);
    return 0;
}

static int open_lights (const struct hw_module_t* module, char const* name,
                        struct hw_device_t** device) {

    /* 定义一个函数指针用于指定调用哪个灯光函数 */                  
    int (*set_light)(struct light_device_t* dev,
                     struct light_state_t const *state);

    /* 根据JNI传入的名字判断调用哪个灯光函数 */               
    if (0 == strcmp(LIGHT_ID_BACKLIGHT, name)) {
        set_light = set_light_backlight;
    }
    else if (0 == strcmp(LIGHT_ID_BATTERY, name)) {
        set_light = set_light_battery;
    }
    else if (0 == strcmp(LIGHT_ID_NOTIFICATIONS, name)) {
        set_light = set_light_notifications;
    }
    else {
        return -EINVAL;
    }

    pthread_once (&g_init, init_globals);
    struct light_device_t *dev = malloc(sizeof (struct light_device_t));
    memset(dev, 0, sizeof(*dev));
    dev->common.tag     = HARDWARE_DEVICE_TAG;
    dev->common.version = 0;
    dev->common.module  = (struct hw_module_t*)module;
    dev->common.close   = (int (*)(struct hw_device_t*))close_lights;
    dev->set_light      = set_light;
    *device = (struct hw_device_t*)dev;
    return 0;
}

static struct hw_module_methods_t lights_module_methods = {
    .open = open_lights,
};

struct hw_module_t HAL_MODULE_INFO_SYM = {
    .tag = HARDWARE_MODULE_TAG,
    .version_major = 1,
    .version_minor = 0,
    .id = LIGHTS_HARDWARE_MODULE_ID,
    .name = "Sony lights module",
    .author = "Diogo Ferreira <defer@cyanogenmod.com>, Andreas Makris <Andreas.Makris@gmail.com>",
    .methods = &lights_module_methods,
};

将代码烧写进系统:

HAL: lights.c
把新文件上传到服务器, 所在目录(lights子目录需要自己创建):
hardware/libhardware/modules/lights/lights.c
hardware/libhardware/modules/lights/Android.mk

Android.mk内容如下:

LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := lights.tiny4412
LOCAL_MODULE_RELATIVE_PATH := hw
LOCAL_C_INCLUDES := hardware/libhardware
LOCAL_SRC_FILES := lights.c
LOCAL_SHARED_LIBRARIES := liblog
LOCAL_MODULE_TAGS := eng
include $(BUILD_SHARED_LIBRARY)

修改,去掉原来的
vi vendor/friendly-arm/tiny4412/device-tiny4412.mk

ifeq ($(BOARD_USES_PWMLIGHTS),false)
#PRODUCT_COPY_FILES += \
#       $(VENDOR_PATH)/proprietary/lights.tiny4412.so:system/lib/hw/lights.tiny4412.so
endif

编译:

$ mmm hardware/libhardware/modules/lights
$ make snod
$ ./gen-img.sh

内核也需要修改:
1. drivers/leds/led-class.c: 0644 改为 0666

static struct device_attribute led_class_attrs[] = {
    __ATTR(brightness, 0666, led_brightness_show, led_brightness_store),
    __ATTR(max_brightness, 0444, led_max_brightness_show, NULL),
#ifdef CONFIG_LEDS_TRIGGERS
    __ATTR(trigger, 0666, led_trigger_show, led_trigger_store),
#endif
    __ATTR_NULL,
};

2.drivers/leds/ledtrig-timer.c: 0644 改为 0666

#if defined(CONFIG_MACH_IPCAM)
static DEVICE_ATTR(delay_on, 0666, led_delay_on_show, led_delay_on_store);
static DEVICE_ATTR(delay_off, 0666, led_delay_off_show, led_delay_off_store);
#else
static DEVICE_ATTR(delay_on, 0666, led_delay_on_show, led_delay_on_store);
static DEVICE_ATTR(delay_off, 0666, led_delay_off_show, led_delay_off_store);
#endif

make zImage

用这个命令确保我们提供的lights.c已经编进system.img:

diff
vendor/friendly-arm/tiny4412/proprietary/lights.tiny4412.so out/target/product/tiny4412/system/lib/hw/lights.tiny4412.so

查看亮度变化的指令:

logcat lights:V *:S


总结:
首先应用程序会通过系统自带的JNI来访问我们编写的HAL文件,HAL文件里面会根据JNI传输过来的控制指令和状态决定控制哪一个灯,从而调用不同的灯文件,这些灯文件即内核驱动,驱动里面使用注册class的方式进行编写,并提供最简单的亮度及亮灭灯的功能。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值