首先分析下如何使用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程序的过程如下:
分配led_classdev
设置 :
led_cdev->max_brightness
led_cdev->brightness_set
led_cdev->flags
led_cdev->brightness
led_cdev->name注册 : 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
- 实现一个名为HMI的hw_module_t结构体
- 实现一个open函数, 它会根据name返回一个light_device_t结构体
- 实现多个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的方式进行编写,并提供最简单的亮度及亮灭灯的功能。