Android下点亮LED

手上有一个android开发平台,于是想在底层加个自己的硬件模块然后通过Android的HAL方式向上封装出底层驱动供JAVA APP调用。既然是刚上手,那就用led灯来做为先导开发走走流程吧,至少得把从内核到应用层的流程走通才行。
 
总体思路从底层到上层一共分成四个阶段                            
1: 内核硬件驱动层
2: HAL(硬件抽象层)
3: 框架层(framework,包含JNI和实现硬件服务的JAVA接口)
4: JAVA应用层

我们从第4层往第一层倒着来说

一,应用层(APP)                                                                     
  1,为了方便,使用Eclipse编写一个简单的控制灯的应用,添加四个button,分配控制灯1亮,灯1灭,灯2亮,灯2灭,Activity代码如下
<pre name="code" class="java">package com.android.swtled;

import android.app.Activity;
import android.os.Bundle;
import android.os.ISwtledService;
import android.os.ServiceManager;
import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.os.RemoteException; 

public class swtled extends Activity {
    
    private static final String LOG_TAG = "com.android.swtled";
    private ISwtledService swtledService = null;
    
    private Button setOnLight1Button = null;
    private Button setOffLight1Button = null;
    private Button setOnLight2Button = null;
    private Button setOffLight2Button = null;
    
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        swtledService = ISwtledService.Stub.asInterface(ServiceManager.getService("swtled"));

        setOnLight1Button = (Button)findViewById(R.id.button_seton_light1);
        setOffLight1Button = (Button)findViewById(R.id.button_setoff_light1);
        setOnLight2Button = (Button)findViewById(R.id.button_seton_light2);
        setOffLight2Button = (Button)findViewById(R.id.button_setoff_light2);

        setOnLight1Button.setOnClickListener(new OnClickListener()
        {

            @Override
            public void onClick(View v) {
                // TODO Auto-generated method stub

                try{
                    
                    swtledService.setOn(1);
                }catch(RemoteException e)
                {
                    Log.e(LOG_TAG, "Exception while setOn Light."); 

                }
            }
            
        });
        
        setOffLight1Button.setOnClickListener(new OnClickListener()
        {

            @Override
            public void onClick(View v) {
                // TODO Auto-generated method stub

                try{
                    
                    swtledService.setOff(1);
                }catch(RemoteException e)
                {
                    Log.e(LOG_TAG, "Exception while setOn Light."); 

                }
            }
            
        });
        
        setOnLight2Button.setOnClickListener(new OnClickListener()
        {

            @Override
            public void onClick(View v) {
                // TODO Auto-generated method stub

                try{
                    
                    swtledService.setOn(2);
                }catch(RemoteException e)
                {
                    Log.e(LOG_TAG, "Exception while setOn Light."); 

                }
            }
            
        });
        
        setOffLight2Button.setOnClickListener(new OnClickListener()
        {

            @Override
            public void onClick(View v) {
                // TODO Auto-generated method stub

                try{
                    
                    swtledService.setOff(2);
                }catch(RemoteException e)
                {
                    Log.e(LOG_TAG, "Exception while setOn Light."); 

                }
            }
        });
    }
}


 
 
2, AndroidManifest.xml脚本
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
      package="com.android.swtled"
      android:versionCode="1"
      android:versionName="1.0">


    <application android:icon="@drawable/icon" android:label="@string/app_name">
        <activity android:name=".swtled"
                  android:label="@string/app_name">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>    
    </application>
</manifest

3, 字符串文件res/values/strings.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
    <string name="hello">Hello World, swtled!</STRING>
    <string name="app_name">swtled</string>
    <string name="seton_light1"></string>
    <string name="setoff_light1"></string>
    <string name="seton_light2"></string>
    <string name="setoff_light2"></string>
</resources>

4,布局文件脚本main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    >
    <TextView 
    android:layout_width="fill_parent" 
    android:layout_height="wrap_content" 
    android:text="@string/hello"
    />
    <LinearLayout
          android:layout_width="fill_parent"
          android:layout_height="wrap_content"
          android:orientation="horizontal" 
          android:gravity="center">
    
     <Button
         android:id="@+id/button_seton_light1"
             android:layout_width = "wrap_content"
             android:layout_height = "wrap_content"
             android:text = "@string/seton_light1"
     />
     
     <Button
     android:id = "@+id/button_setoff_light1"
         android:layout_width = "wrap_content"
         android:layout_height = "wrap_content"
         android:text = "@string/setoff_light1"
     />
     
     <Button
         android:id="@+id/button_seton_light2"
             android:layout_width = "wrap_content"
             android:layout_height = "wrap_content"
             android:text = "@string/seton_light2"
     />
     
     <Button
     android:id = "@+id/button_setoff_light2"
         android:layout_width = "wrap_content"
         android:layout_height = "wrap_content"
         android:text = "@string/setoff_light2"
     />
     </LinearLayout>
</LinearLayout>

5,由于是编译器是Eclipse,但实际编译却是要在android源代码环境下编译,于是,将工程目录的三个文件
  src,res以及AndroidManifest.xml复制到gingerbread源码目录的package/experimental/swtled下,其中
  swtled文件是需要自己新建的,复制完成之后,进入编译阶段
 
6,编译app并生成apk文件
  (1) mmm package/experimental/swtled
  编译成功后,便可以在out/target/product/generic/system/app目录下看到swtled.apk文件
  (2) 重新打包系统镜像文件system.img
   make snod
 
7,开机测试
 
 
8,注意事项:
在工程中需要import两个包是
 import android.os.ISwtledService;
 import android.os.ServiceManager;

程序通过ServiceManager.getService("swtled")来获得SwtledService,
接着通过ISwtledService.Stub.asInterface函数转换为ISwtledService接口。
其中,服务名字“swtled”是系统启动时加载SwtledService时指定的,
而ISwtledService接口定义在android.os.ISwtledService中
需要注意的是,虽然用Eclipse编译这个应用,但是无法用Eclipse编译,最后还是需要将工程中res,src以及AndroidManifest.xml三个文件copy到gingerbread/package/experimental/swtled,swtled是自己再新建的文件夹, 然后使用 mmm package/experimental/swtled编译,如果正确,最后会生成.apk文件。
 
二,框架层(framework)                                                                    

 这里主要实现两部分

(1: 为Android HAL编写JNI方法,以便使得上层的APP能够使用下层提供的硬件服务

(2: 在Android系统的框架层提供Java接口的硬件服务

1,在frameworks/base/services/jni中新建com_android_server_swtled.cpp

#define LOG_TAG "SwtledService"
#include "jni.h"
#include "JNIHelp.h"
#include "android_runtime/AndroidRuntime.h"
#include <utils/misc.h>
#include <utils/Log.h>
#include <hardware/hardware.h>
#include <hardware/swtled.h>
#include <stdio.h>



namespace android
{
    struct swtled_device_t* swtled_device = NULL;


    static void swtled_set_on(JNIEnv* env, jobject clazz,jint number)
    {
        int num = number;
        LOGI("Swtled JNI: select lighton is %d",num);

        if (!swtled_device)
        {
            LOGI("Swtled JNI:device is not open.");
            return;
        }
        swtled_device->set_on(swtled_device,num);
    }

    static void swtled_set_off(JNIEnv* env, jobject clazz,jint number)
    {
         
        int num = number;
        LOGI("Swtled JNI: select lightoff is %d",num);

        if (!swtled_device)
        {
            LOGI("Swtled JNI:device is not open.");
            return;
        }
        swtled_device->set_off(swtled_device,num);
    }

    static inline int swtled_device_open(hw_module_t* module,swtled_device_t** device)
    {
        return module->methods->open(module,SWTLED_HARDWARE_MODULE_ID,(hw_device_t**)device);
    }
    

    static jboolean swtled_init(JNIEnv* env,jclass clazz)
    {
        swtled_module_t* swtledmodule;
        
        LOGI("Swtled JNI: initializing...");

        if (hw_get_module(SWTLED_HARDWARE_MODULE_ID,(const struct hw_module_t**)&swtledmodule)==0)
        {
            LOGI("Swtled JNI: swtled stub be found.");
            if (swtled_device_open(&(swtledmodule->common),&swtled_device) == 0)
            {
                LOGI("Swtled JNI: swtled device open successful.");
                return 0;
            }

            LOGI("Swtled JNI: failed to open swtled device.");
            return -1;
        }
        
        LOGI("Swtled JNI: failed to get swtled stub module.");
        return -1;
     }
    
     static const JNINativeMethod method_table[] =
     {
        {"init_native", "()Z", (void*)swtled_init},
        {"setOn_native","(I)V",(void*)swtled_set_on},
        {"setOff_native","(I)V",(void*)swtled_set_off},
     };

    int register_android_server_SwtledService(JNIEnv* env)
    {
        return jniRegisterNativeMethods(env,"com/android/server/SwtledService",method_table,NELEM(method_table));
    }
}

修改同级目录下的Android.mk和Onload.cpp文件

在Android.mk的LOCAL_SRC_FILES:=下添加

LOCAL_SRC_FILES:= \
....
com_android_server_swtled.cpp \

在Onload.cpp中的namespace android {  下加入

int register_android_server_SwtledService(JNIEnv* env);

同时在JNI_OnLoad函数下加入
register_android_server_SwtledService(env);

2,编译

   (1,执行mmm framework/base/services/jni

   (2,make snod

3,在frameworks/base/core/java/android/os 新建ISwtledService.aidl,代码如下

package android.os;



interface ISwtledService {

    void setOn(int number);

    void setOff(int number);

}

返回到frameworks/base目录,打开Android.mk文件,修改LOCAL_SRC_FILES变量的值,增加   ISwtledService.aidl源文件
LOCAL_SRC_FILES += /
....................................................................
core/java/android/os/IVibratorService.aidl /
core/java/android/os/ISwtledService.aidl /
core/java/android/service/urlrenderer/IUrlRendererService.aidl /

4,编译ISwtledService接口
  mmm framework/base,如果正确,那么会根据ISwtledService.aidl生成对应的ISwtledService.Stub接口
 
5, 进入到frameworks/base/services/java/com/android/server目录,新增ISwtledService.java文件:
package com.android.server;
import android.content.Context;
import android.os.ISwtledService;
import android.util.Slog;


public class SwtledService extends ISwtledService.Stub {

    private static final String TAG = "SwtledService";

    SwtledService()
    {
        init_native();
    }

    public void setOn(int number)
    {
        setOn_native(number);
    }

    public void setOff(int number)
    {
        setOff_native(number);
    }

    private static native boolean init_native();
    private static native void setOn_native(int number);
    private static native void setOff_native(int number);
};
6,修改同目录下的SystemServer.java文件,在ServerThread::run方法里加入

try{
                 
                Slog.i(TAG, "Swtled Service");
                ServiceManager.addService("swtled", new SwtledService());
            } catch (Throwable e) {
                Slog.e(TAG, "Failure starting Swtled Service", e);
            }
7,编译SwtledService.java并打包进system.img
   (1)mmm framework/base/services/java
   (2)make snod

三,硬件抽象层                                                                        

1,在hardware\libhardware\include\hardware下新建swtled.h文件
swtled.h
#ifndef ANDROID_SWTLED_INTERFACE_H
#define ANDROID_SWTLED_INTERFACE_H

#include <hardware/hardware.h>

__BEGIN_DECLS

//module interface
struct swtled_module_t
{
    struct hw_module_t common;
};

//device interface
struct swtled_device_t
{
    struct hw_device_t common;
    int fd;
    int (*set_on)(struct swtled_device_t* dev,int number);
    int (*set_off)(struct swtled_device_t* dev,int number);
};


#define SWTLED_HARDWARE_MODULE_ID "swtled"
__END_DECLS

#endif

2:在hardware/libhardware/module下新建swtled文件夹并新建swtled.c
#define LOG_TAG "swtledStub"

#include <hardware/hardware.h>
#include <hardware/swtled.h>
#include <fcntl.h>
#include <errno.h>
#include <cutils/log.h>
#include <cutils/atomic.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/ioctl.h>


#define LED_ON _IO('k',1)
#define LED_OFF _IO('k',2)

#define DEVICE_NAME "/dev/swt_led"
#define MODULE_NAME "swtled"
#define MODULE_AUTHOR "zc"



//open or close device interface

static int swtled_device_open(const struct hw_module_t* module,const char* name, struct hw_device_t** device);

static int swtled_device_close(struct hw_device_t* device);


//light-on light-off
static int swtled_set_on(struct swtled_device_t* dev,int number);
static int swtled_set_off(struct swtled_device_t* dev,int number);


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

    struct swtled_device_t * dev;
    dev = (struct swtled_device_t*)malloc(sizeof(struct swtled_device_t));
    if (!dev)
    {
     LOGE("Swtled Stub:failed to alloc space");
        return -EFAULT;
            
    }
    memset(dev,0,sizeof(struct swtled_device_t));
    dev->common.tag = HARDWARE_DEVICE_TAG;
    dev->common.version = 0;
    dev->common.module = (hw_module_t*)module;
    dev->common.close = swtled_device_close;
    dev->set_on = swtled_set_on;
    dev->set_off = swtled_set_off;

    if ((dev->fd = open(DEVICE_NAME,O_RDWR)) == -1)
    {
        LOGE("Swtled Stub:failed to open /dev/swt_led -- %s.",strerror(errno));
        free(dev);
        return -EFAULT;
    }

    *device = &(dev->common);
    LOGI("Swtled Stub: open /dev/swt_led successfully");

    return 0;
}

static int swtled_device_close(struct hw_device_t* device)
{
    struct swtled_device_t* swtled_device = (struct swtled_device_t*)device;
    if (swtled_device)
    {
        close(swtled_device->fd);
        free(swtled_device);
    }
    return 0;
}


//------------------------------------------------------------------------------
//number: 1: light one 2:light two
//------------------------------------------------------------------------------
static int swtled_set_on(struct swtled_device_t* device,int number)
{
    ioctl(device->fd,LED_ON,number);
    return 0;
}


//------------------------------------------------------------------------------
//number: 1: light one 2:light two
//------------------------------------------------------------------------------
static int swtled_set_off(struct swtled_device_t* device,int number)
{
    ioctl(device->fd,LED_OFF,number);
    return 0;
}



//module method table
static struct hw_module_methods_t swtled_module_methods =
{
    open: swtled_device_open
};



const struct swtled_module_t HAL_MODULE_INFO_SYM = {

    common: 
    {
        tag: HARDWARE_MODULE_TAG,
        version_major: 1,
        version_minor: 0,
        id: SWTLED_HARDWARE_MODULE_ID,
        name:MODULE_NAME,
        author: MODULE_AUTHOR,
        methods: &swtled_module_methods,        
    }
        
};

这里主要是通过hw_module_t和hw_device_t结构体形式将内核中led的驱动封装一层,
导出set_on和set_off接口,到时供JNI层的CPP调用。由于权限问题,应用层在打开dev时可能提示permission deny,所以需要修改
system\core\rootdir下的ueventd.rc文件,在其中添加一句
/dev/swt_led 0666 root root

3,在同目录下添加Android.mk

LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE_TAGS := optional
LOCAL_PRELINK_MODULE := false
LOCAL_MODULE_PATH := $(TARGET_OUT_SHARED_LIBRARIES)/hw
LOCAL_SHARED_LIBRARIES := liblog
LOCAL_SRC_FILES := swtled.c
LOCAL_MODULE := swtled.default
include $(BUILD_SHARED_LIBRARY)

4,编译
 (1 进入到gingerbread目录下,执行mmm hardware/libhardware/modules/swtled会
    编译成功后,就可以在out/target/product/generic/system/lib/hw目录下看到swtled.default.so文件了
 
 (2 再执行make snod,使system.img镜像文件中包名硬件抽象模块swtled.default.so

四,第一层,内核硬件驱动层                                                               

 在kernel\driver\下新建一个目录swt,在swt下再新建led文件夹,并新建swtled.c swtled.h,通过注册模块方式,实现ioctl,write,read接口。之后通过设备名给上层访问。
 swtled.c部分
#include <linux/kernel.h> 
#include <linux/sched.h> 
#include <linux/timer.h> 
#include <linux/init.h>
#include <linux/module.h> 
#include <linux/uaccess.h>
#include <linux/gpio.h> 
#include <linux/cdev.h> 
#include <linux/fs.h>
#include <linux/device.h> 
#include <linux/miscdevice.h>
#include <linux/watchdog.h>
#include <linux/fs.h>

#include <asm/io.h>
#include <mach/gpio.h>
#include <asm/uaccess.h>


#define LED_ON _IO ('k',1)
#define LED_OFF _IO ('k',2)


struct light_dev 
{ 
    struct cdev cdev;
    
    unsigned char value;
};

static struct light_dev *light_devp; 
static struct class * light_class = NULL; 


int light_major = 250;

#if 0


static ssize_t light_val_show(struct device* dev, struct device_attribute* attr, char* buf)
{
    return 0;
}

static ssize_t light_val_store(struct device* dev, struct device_attribute* attr, const char* buf, size_t count)
{
    return 0;
}


static DEVICE_ATTR(val, S_IRUGO | S_IWUSR, light_val_show, light_val_store); 

#endif

void led_off(int arg)
{
    switch(arg)
    {
        case 1: gpio_set_value(RK29_PIN6_PB3,GPIO_LOW); //GPIO8
        break;

        case 2: gpio_set_value(RK29_PIN6_PC2,GPIO_LOW); //GPIO10
        break;

        default:
        break;
    }
}

void led_on(int arg)
{
    switch(arg)
    {
        case 1: gpio_set_value(RK29_PIN6_PB3,GPIO_HIGH);
        break;

        case 2: gpio_set_value(RK29_PIN6_PC2,GPIO_HIGH);
        break;

        default:
        break;
    }
}


int light_open(struct inode *inode, struct file *filp) 
{ 
    struct light_dev *dev; 

    int ret = 0;

    printk(KERN_ALERT"light_open()");
    

    dev = container_of(inode->i_cdev, struct light_dev, cdev); 

    filp->private_data = dev; 

    ret = gpio_request(RK29_PIN6_PB3, NULL);

        if(ret != 0)
        {
            gpio_free(RK29_PIN6_PB3);
        }
        gpio_direction_output(RK29_PIN6_PB3, 0);

        ret = gpio_request(RK29_PIN6_PC2, NULL);

        if(ret != 0)
        {
            gpio_free(RK29_PIN6_PC2);
        }
        gpio_direction_output(RK29_PIN6_PC2, 0);



    return 0; 
} 

int light_release(struct inode *inode, struct file *filp) 
{
    printk(KERN_ALERT"light_release()");
    
    return 0;
}



ssize_t light_read(struct file *filp, char __user *buf, size_t count, loff_t*f_pos) 
{ 
    struct light_dev *dev = filp->private_data; 

    printk(KERN_ALERT"light_read()");

    if (copy_to_user(buf, &(dev->value), 1)) 
    { 
        return -EFAULT; 
    } 
    
    return 1; 
}

ssize_t light_write(struct file *filp, const char __user *buf, size_t count, loff_t *f_pos) 
{ 
    struct light_dev *dev = filp->private_data; 

    printk(KERN_ALERT"light_write()");
    
    if (copy_from_user(&(dev->value), buf, 1)) 
    { 
        return -EFAULT; 
    } 
    


    if (dev->value == 1)
        printk(KERN_ALERT"LED on");
    else 
        printk(KERN_ALERT"LED off");
    return 1; 
} 


int light_ioctl(struct inode    *inode, struct file *filp, unsigned int cmd, unsigned long arg) 
{ 
    struct light_dev *dev = filp->private_data;

    printk(KERN_ALERT"light_ioctl(cmd = %d)", cmd);
    
    switch (cmd)
        {
            case LED_ON:
                led_on(arg);
                break;
            case LED_OFF:
                led_off(arg);
                break;
            default: break;
        }

    
    return 0; 
} 

struct file_operations light_fops = 
{ 
    .owner = THIS_MODULE, 
    .read = light_read, 
    .write = light_write, 
    .ioctl = light_ioctl, 
    .open = light_open, 
    .release = light_release,
};

static int light_setup_cdev(struct light_dev *dev, int index) 
{ 
    int err, devno = MKDEV(light_major, index); 
    cdev_init(&dev->cdev, &light_fops); 
    dev->cdev.owner = THIS_MODULE; 
    dev->cdev.ops = &light_fops;
    dev->value = 0;
    err = cdev_add(&dev->cdev, devno, 1);

    return err;
} 



int light_init(void) 
{ 
    int result = 0;
    
    struct device * temp = NULL;    

    dev_t dev = MKDEV(light_major, 0); 
        
    printk(KERN_ALERT"light_init()");


    
    if (light_major) {
        result = register_chrdev_region(dev, 1, "LED"); 
    }
    else { 
        result = alloc_chrdev_region(&dev, 0, 1, "LED"); 

        light_major = MAJOR(dev); 
    }
    
    if (result < 0) {
        goto failed;
    }
    

    
    light_devp = kmalloc(sizeof(struct light_dev), GFP_KERNEL);    
    if (!light_devp){    
        result = -ENOMEM;        
        goto unregister; 
    } 
    
    memset(light_devp, 0, sizeof(struct light_dev));
    
    result = light_setup_cdev(light_devp, 0); 
    if(result) { 
        goto cleanup; 
    }


    
    light_class = class_create(THIS_MODULE, "led");
    if(IS_ERR(light_class)) { 
        printk(KERN_ALERT"Failed to create light class./n"); 
        goto destroy_cdev;    
    }         
    

    
    temp = device_create(light_class, NULL, dev, "%s", "swtled"); 
    if(IS_ERR(temp)) {    
        printk(KERN_ALERT"Failed to create hello device."); 
        goto destroy_class;     
    }

#if 0

    
    result = device_create_file(temp, &dev_attr_val); 
    if(result < 0) { 
     printk(KERN_ALERT"Failed to create attribute val."); 
     goto destroy_device; 
    }

    dev_set_drvdata(temp, light_devp);
#endif    
    

    
    return 0;

destroy_device: 
    device_destroy(light_class, dev); 
  
destroy_class:    
    class_destroy(light_class); 
  
destroy_cdev: 
    cdev_del(&(light_devp->cdev)); 

cleanup: 
        kfree(light_devp); 
        
unregister: 

    unregister_chrdev_region(dev, 1); 
    
failed:

    printk(KERN_ALERT"light_init failed.");
    
    
return result; 

} 


void light_cleanup(void) 
{
    printk(KERN_ALERT"light_cleanup()");


    
    if(light_class) { 
        device_destroy(light_class, MKDEV(light_major, 0)); 
        class_destroy(light_class); 
    } 
  

    if(light_devp) { 
        cdev_del(&light_devp->cdev); 
        kfree(light_devp); 
    }
    
    unregister_chrdev_region(MKDEV(light_major, 0), 1);
    
} 

module_init(light_init); 
module_exit(light_cleanup);


MODULE_LICENSE("GPL"); 
MODULE_DESCRIPTION("led driver"); 
MODULE_ALIAS("led module");

led文件夹中Kconfig文件
config SWT_XXX_LED
    bool "swt led"
    default n
    help
    This compiles in support for the led of xxx.

led文件夹中MakeFile文件
obj-$(CONFIG_SWT_XXX_LED)+=swt_led.o
swtled文件中Kconfig文件
menu "swtled Drivers"

source "drivers/swt/led/Kconfig"

endmenu
swtled文件夹中MakeFile文件
obj-y += led/
接下来,就要修改kernel/driver下的MakeFile和Kconfig文件了,在分别其中添加一句
MakeFile文件
obj-y += swt/

Kconfig文件
source "drivers/swt/Kconfig"

保存所有之后,执行make
编译成功后,就可以在swt/led目录下看到swtled.o文件了,这时候编译出来的img已经包含了swtled驱动了


9,结束
以上四篇文章记录了在android平台下使用硬件led灯实现从java应用层控制灯的四个实现阶段,这个主要是记录自己的一些心得,在开发阶段也借鉴参考了一些网友的文章,在此表示感谢。
(转:http://blog.chinaunix.net/uid-21880738-id-3361037.html )






























评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值