1 led驱动的实现原理
Linux驱动不直接与硬件打交道,而是通过i/o内存作为中介,具体关系如下图
2 编写led驱动
2.1创建led驱动的设备文件
在统计单词数量驱动中使用misc_register创建设备文件,该函数只能创建设备号为10的设备文件
所以想要创建其他设备号的设备文件,就要使用:
cdev_init 初始化cdev
查看cdev结构体的定义在内核源代码的/include/linux/cdev.h文件中定义,内容如下:
struct cdev {
struct kobject kobj; //封装设备文件的对象
struct module *owner;//指向内核模块的指针
const struct file_operations *ops; //file_operation结构体指针
struct list_head list; //指向上一个下一个cdev结构体的指针
dev_t dev;//dev_t 是int数据类型,表示设备号,前12位表示主设备号,后20位表示次
设备号
unsigned int count; //请求的链接设备的编号的范围
};
Cdev_init()函数位于linux/fs/char_dev.c文件中,代码内容如下:
/**
* cdev_init() - initialize a cdev structure
* @cdev: the structure to initialize
* @fops: the file_operations for this device
*
* Initializes @cdev, remembering @fops, making it ready to add to the
* system with cdev_add().
*/
void cdev_init(struct cdev *cdev, const struct file_operations *fops)
{
//将结构体中成员变量清零
memset(cdev, 0, sizeof *cdev);
//初始化首尾指针
INIT_LIST_HEAD(&cdev->list);
//初始化设备对象
kobject_init(&cdev->kobj, &ktype_cdev_default);
//关链cdev和file_operation
cdev->ops = fops;
}
指定设备号
设备号分为主设备号和次设备号,分配方式有以下两种方法:
直接在代码中指定,使用register_chrdev_region()函数定义在inux/fs/char_dev.c文件中,内容如下
/**
* register_chrdev_region() - register a range of device numbers 静态指定设备号
* @from: the first in the desired range of device numbers; must include
* the major number. 设备号
* @count: the number of consecutive device numbers required 表示次设备号范围
* @name: the name of the device or driver. 表示设备文件名称
*
* Return value is zero on success, a negative error code on failure.
*/
int register_chrdev_region(dev_t from, unsigned count, const char *name)
{
struct char_device_struct *cd;
dev_t to = from + count;
dev_t n, next;
for (n = from; n < to; n = next) {
next = MKDEV(MAJOR(n)+1, 0); //将主设备号和次设备号合成设备号
if (next > to)
next = to;
//MAJOR主设备号,MINOR次设备号
cd = __register_chrdev_region(MAJOR(n), MINOR(n),
next - n, name);
if (IS_ERR(cd))
goto fail;
}
return 0;
fail:
to = n;
for (n = from; n < to; n = next) {
next = MKDEV(MAJOR(n)+1, 0);
kfree(__unregister_chrdev_region(MAJOR(n), MINOR(n), next - n));
}
return PTR_ERR(cd);
}
动态分配 使用alloc_chrdev_region函数,定义在inux/fs/char_dev.c文件中,内容如下:
/**
* alloc_chrdev_region() - register a range of char device numbers 动态分配设备编号
* @dev: output parameter for first assigned number 设备号指针
* @baseminor: first of the requested range of minor numbers 次设备号
* @count: the number of minor numbers required 表示分配的次设备号范围
* @name: the name of the associated device or driver 表示设备文件名称
*
* Allocates a range of char device numbers. The major number will be
* chosen dynamically, and returned (along with the first minor number)
* in @dev. Returns zero or a negative error code.
*/
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count,
const char *name)
{
struct char_device_struct *cd;
cd = __register_chrdev_region(0, baseminor, count, name);
if (IS_ERR(cd))
return PTR_ERR(cd);
*dev = MKDEV(cd->major, cd->baseminor);
return 0;
}
Cdev_add 将字符设备添加到内核中的字符设备组中
函数定义在inux/fs/char_dev.c文件中,内容如下:
/**
* cdev_add() - add a char device to the system 将字符设备添加到内核 字符设备数组中
* @p: the cdev structure for the device 设备文件指针
* @dev: the first device number for which this device is responsible 设备号
* @count: the number of consecutive minor numbers corresponding to this 设备文件数量
* device
*
* cdev_add() adds the device represented by @p to the system, making it
* live immediately. A negative error code is returned on failure.
*/
int cdev_add(struct cdev *p, dev_t dev, unsigned count)
{
p->dev = dev;
p->count = count;
//将字符设备添加到probes数组中,该函数定义在linux/drivers/base/map.c
return kobj_map(cdev_map, dev, count, NULL, exact_match, exact_lock, p);
}
class_create 该宏用于创建struct_class,实际上调用的是__create_class()函数定义在drivers/base/class.c文件中
/* This is a #define to keep the compiler from merging different
* instances of the __key variable */
#define class_create(owner, name) \
({ \
static struct lock_class_key __key; \
__class_create(owner, name, &__key);\
})
device_create 创建设备文件
函数定义在drivers/base/core.c文件中,内容如下:
struct device *device_create(struct class *class, struct device *parent,
dev_t devt, void *drvdata, const char *fmt, ...)
{
va_list vargs;
struct device *dev;
va_start(vargs, fmt);
dev = device_create_vargs(class, parent, devt, drvdata, fmt, vargs);
va_end(vargs);
return dev;
}
创建设备文件的示例代码如下:
//定义设备文件名称
#define DEVICE_NAME "mini6410_leds"
//创建设备文件的数量
#define DEVICE_COUNT 1
//默认主设备号
#define MINI6410_LEDS_MAJOR 0
//默认次设备号
#define MINI6410_LEDS_MINOR 234
//主设备号
static int major=MINI6410_LEDS_MAJOR;
//次设备号
static int minor=MINI6410_LEDS_MINOR;
//设备号
static dev_t dev_number;
//struct class
static struct class *leds_class=NULL;
//设备文件回调指针
static struct file_operations dev_fops={
.ower=THIS_MODULE,
.unlocked_ioctl=mini6410_leds_ioctl,
.write=mini6410_leds_write
};
//描述字符设备的struct cdev
static struct cdev leds_cdev;
//创建设备文件
static int leds_create_device(void){
int ret =0;
int err=0;
//初始化cdev成员
cdev_init(&leds_cdev,&dev_fops);
//创建的设备文件属于当前的驱动模块
leds_cdev.owner=THIS_MODULE;
//主设备号大于0,通过指定设备号的方式注册字符设备
if(major>0){
//获取设备号
dev_number=MKDEV(major,minor);
//通过指定设备号的方式注册字符设备区域
err=register_chrdev_region(dev_number,DEVICE_COUNT,DEVICE_NAME);
//注册失败
if(err<0){
printk(KERN_WARNING "register_chrdev_region() failed\n");
return err;
}
}
else{//主设备号为0,自动分配主设备号和次设备号
//通过自动分配主设备号和次设备号的方式
//10表示起始次设备号
err=alloc_chrdev_region(&leds_cdev.dev,10,DEVICE_COUNT,DEVICE_NAME);
//注册失败
if(err<0){
printk(KERN_WARNING "alloc_chrdev_region() failed\n");
return err;
}
//获取主设备号
major=MAJOR(leds_cdev.dev);
//获取次设备号
minor=MINOR(leds_cdev.dev);
//自动分配的设备号
dev_number= leds_cdev.dev;
}
//将字符设备添加到内核中的字符设备数组中
cdev_add(&leds_cdev,dev_number,DEVICE_COUNT);
//创建struct class
leds_class=class_create(THIS_MODULE,DEVICE_NAME);
//创建设备文件
device_create(leds_class,NULL,dev_number,NULL,DEVICE_NAME);
return ret;
}
LED驱动的初始化函数
Static int leds_init(void){
Int ret;
//创建设备文件
Ret=leds_create_device();
Printk(DEVICE_NAME”\t initialized \n”);
Return ret;
}
Module_init(leds_init);
卸载led驱动的设备文件
Device_destory //函数原型如下,代码位于drivers/base/core.c文件中
/**
* device_destroy - removes a device that was created with device_create() 移除建立的字符设备
* @class: pointer to the struct class that this device was registered with
* @devt: the dev_t of the device that was previously registered
*
* This call unregisters and cleans up a device that was created with a
* call to device_create().
*/
void device_destroy(struct class *class, dev_t devt)
{
struct device *dev;
dev = class_find_device(class, NULL, &devt, __match_devt);
if (dev) {
put_device(dev);
device_unregister(dev);
}
}
Class_destory //销毁sturct class,函数定义在drivers/base/class.c文件中
/**
* class_destroy - destroys a struct class structure 销毁sturct class
* @cls: pointer to the struct class that is to be destroyed
*
* Note, the pointer to be destroyed must have been created with a call
* to class_create().
*/
void class_destroy(struct class *cls)
{
if ((cls == NULL) || (IS_ERR(cls)))
return;
class_unregister(cls);
}
Unregister_chrdev_region //注销字符设备区域,函数位于linux/fs/char_dev.c文件中
/**
* unregister_chrdev_region() - return a range of device numbers 注销字符设备区域
* @from: the first in the range of numbers to unregister
* @count: the number of device numbers to unregister
*
* This function will unregister a range of @count device numbers,
* starting with @from. The caller should normally be the one who
* allocated those numbers in the first place...
*/
void unregister_chrdev_region(dev_t from, unsigned count)
{
dev_t to = from + count;
dev_t n, next;
for (n = from; n < to; n = next) {
next = MKDEV(MAJOR(n)+1, 0);
if (next > to)
next = to;
kfree(__unregister_chrdev_region(MAJOR(n), MINOR(n), next - n));
}
}
示例代码如下
//移除设备文件
Static void leds_destroy_device(void){
//移除创建的字符设备
Device_destroy(leds_class,dev_number);
//销毁struct class
If(leds_class){
Class_destroy(leds_class);
}
//注销字符设备区域
Unretister_chrdev_retion(dev_number,DEVICE_NAME);
Return ;
}
Static void leds_exit(void){
//卸载led设备驱动
Leds_destroy();
Printk(DEVICE_NAME”\t esit! \n”);
}
Module_exit(leds_exit);
设置寄存器与初始化LED驱动
设置led引角的状态,打开或禁止上拉电路来控制led的亮或灭
Led有两个引角,gpb0和gpb1,一个引角连接arm处理器的gpio接口,另一个经过一个限流电阻连接到电源,当gpio端为低电平时,led亮
控制led需要三个寄存器来完成:
查看芯片手册得到其值
gpkcon(端口配置寄存器) 0x7f008800
gpkdat(数据端口寄存器) 0x7f008808
gpkpud(端口上拉电路寄存器)0x7f00880c
每个寄存器使用四个字节,也就是一个int整型数据
将pgkcon 4567 设为output
将gpkdat 0表示亮,1表示灭
将gpkpud 10表示打开上拉电路
寄存器虚拟地址对应的宏
定义在 linux/arch/arm/mach-s3c64xx/include/mach/gpio-bank-k.h
文件中,示例代码如下:
#define S3C64XX_GPKCON (S3C64XX_GPK_BASE + 0x00)
#define S3C64XX_GPKCON1 (S3C64XX_GPK_BASE + 0x04)
#define S3C64XX_GPKDAT (S3C64XX_GPK_BASE + 0x08)
#define S3C64XX_GPKPUD (S3C64XX_GPK_BASE + 0x0c)
linux/arch/arm/plat-s3c64xx/include/mach/regs-gpio.h
#define S3C64XX_GPK_BASE S3C64XX_GPIOREG(0x0800)
#define S3C64XX_GPIOREG(reg) (S3C64XX_VA_GPIO + (reg))
linux/arch/arm/mach-s3c6400/include/mach/map.h
#define S3C64XX_VA_GPIO S3C_ADDR_CPU(0x00000000)
linux/arch/arm/plat-samsung/include/plat/map-base.h
#define S3C_ADDR_CPU(x) S3C_ADDR(0x00500000 + (x))
#ifndef __ASSEMBLY__
#define S3C_ADDR(x) ((void __iomem __force *)S3C_ADDR_BASE + (x))
#else
#define S3C_ADDR(x) (S3C_ADDR_BASE + (x))
#define S3C_ADDR_BASE 0xF6000000
初始化寄存器的函数如下:
static void leds_init_gpk(int leds_default){
int tmp=0;
//------------初始化gpkcon寄存器----------
tmp=ioread32(S3C64XX_GPKCON);
//保留低16位的值,高16位清零
tmp &= (~0xFFFF0000);
//将低16位设为1111
tmp |=0x11110000;
//向gpmcon写入数据
iowrite32(tmp,S3C64XX_GPKCON);
//----------初始化gpkpud寄存器-----------
//读取gpkpud寄存器的当前值
tmp=ioread32(S3C64XX_GPKPUD);
//4567位清零
tmp&=(~0xFF00);
//打开led上拉电路
tmp|=0xAA00;
//向gpkpud写入数据
iowrite32(tmp,S3C64XX_GPKPUD);
//----------初始化gpkdat寄存器-----------
//读取gpkdat寄存器的当前值
tmp=ioread32(S3C64XX_GPKDAT);
//将4567位清零
tmp&=(~0xF0);
//设置四个led默认的亮灭状态
tmp|=leds_default;
//向gpkdat寄存器写入数据
iowrite32(tmp,S3C64XX_GPKDAT);
}
在leds_init()中调用该函数即可
static int leds_init(void){
int ret;
ret=leds_create_device();
//初始化寄存器
//1100 0000 两亮两灭,0表示亮,1表示灭
leds_init_gpk(0xC0);
printk(DEVICE_NAME"\t initialiaed \n");
return ret;
}
控制led
可以通过以下两种方式控制led :
通过字符串控制led 需要使用file_operation.write
通过I/O命令控制led 需要使用fiel_operation.ioctl
接收数据的函数如下mini6410_leds_write():
示例代码如下:
//保存四个led的设置状态
static unsigned char mem[4];
//接收向设备写入的字符串
static ssize_t mini6410_leds_write(struct file *file,const char _user *buf,size_t count,loff_t *ppos){
unsigned tmp=count;
unsigned long i=0;
//初始化mem数组的值
memset(mem,0,4);
//最多写入四个字符,多余字符将忽略
if(count > 4){
tmp=4;
}
//从用户空间向内核空间写入数据
if(copy_from_user(mem,buf,tmp)){
return -EFAULT;
}else{
//依次控制四个led
for(i=0;i<4;i++){
//读取gpkdat寄存器的当前值
tmp=ioread32(S3C64XX_GPKDAT);
//如果字符为1则点亮当前的led
if(mem[i]=='1'){
tmp&=(~(1<<(i+4)));
}else{//如果字符为0则灭掉当前的led
tmp|=(1<<(i+4));
}
iowrite32(tmp,S3C64XX_GPKDAT);
}
return count;
}
}
可以通过以下命令控制led的亮灭
Adb shell ‘echo 1100 >/dev/mini6410_leds’
接收命令和参数的函数
//filp表示led驱动的设备文件
//cmd表示开关
//arg表示哪个led
static long mini6410_leds_ioctl(struct file *filp,unsigned int cmd,unsigned long arg){
//命令只能是0/1
switch(cmd){
unsigned tmp;
case 0:
case 1:
if(arg>4){
return -EINVAL;
}
//读取gpkdat寄存器的当前值
tmp=ioread32(S3C64XX_GPKDAT);
if(cmd==1){//开灯
tmp&=(~(1<<(arg+4)));
}else{//关灯
tmp|=(1<<(arg+4));
}
//向gpkdat写入数据
iowrite32(tmp,S3C64XX_GPKDAT);
return 0;
default:
return -EINVAL;
}
}
LED驱动的模块参数
如果相要在装载led驱动时指定默认状态,就要使用模块参数
为linux指定一个参数需要使用module_param(name,//参数名
type,//参数类型
perm)//表示读写权限
使用宏指定参数后,会在sys/module目录下生成设备文件同名的文件目录,即
/sys/module/mini6410_leds/parameters,如果有多个参数,会在该目录下生成param*个文件
为led添加一个模块参数,示例代码如下:
//保存模块参数值的变量
static int leds_state=1;
//初始化led驱动
static int leds_init(void){
int ret;
ret=leds_create_device();
//初始化寄存器
leds_init_gpk(~leds_state);
printk(DEVICE_NAME"\t initialized \n");
return ret;
}
//指定模块参数
module_param(leds_stat,int,S_IRUGO | S_IWUSR);
LED驱动的完整代码如下:
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/pci.h>
#include <asm/uaccess.h>
#include <mach/map.h>
#include <mach/regs-gpio.h>
#include <mach/gpio-bank-k.h>
//定义设备文件名称
#define DEVICE_NAME "mini6410_leds"
//创建设备文件的数量
#define DEVICE_COUNT 1
//默认主设备号
#define MINI6410_LEDS_MAJOR 0
//默认次设备号
#define MINI6410_LEDS_MINOR 234
//定义数组类型的模块参数值个数
#define PARAM_SIZE 3
//保存四个led设置状态的数组
static unsigned char mem[4];
//主设备号
static int major=MINI6410_LEDS_MAJOR;
//次设备号
static int minor=MINI6410_LEDS_MINOR;
//设备号
static dev_t dev_number;
//当前led的状态,通过模块参数传入
static int leds_state=1;
//数组类型模块参数的默认值
static char *params[]={"string1","string2","string3"};
//数组类型模块参数值的个数
static int param_size=PARAM_SIZE;
//struct class表示led字符设备的结构体
static struct class *leds_class=NULL;
//描述字符设备的struct cdev
static struct cdev leds_cdev;
//------------接收命令和参数的函数
//filp表示led驱动的设备文件
//cmd表示开关
//arg表示哪个led
static long mini6410_leds_ioctl(struct file *filp,unsigned int cmd,unsigned long arg){
//命令只能是0/1
switch(cmd){
unsigned tmp;
case 0:
case 1:
if(arg>4){
return -EINVAL;
}
//读取gpkdat寄存器的当前值
tmp=ioread32(S3C64XX_GPKDAT);
if(cmd==1){//开灯
tmp&=(~(1<<(arg+4)));
}else{//关灯
tmp|=(1<<(arg+4));
}
//向gpkdat写入数据
iowrite32(tmp,S3C64XX_GPKDAT);
return 0;
default:
return -EINVAL;
}
}
//-----------接收向设备写入的字符串
static ssize_t mini6410_leds_write(struct file *file,const char __user *buf,size_t count,loff_t *ppos){
unsigned tmp=count;
unsigned long i=0;
//初始化mem数组的值
memset(mem,0,4);
//最多写入四个字符,多余字符将忽略
if(count > 4){
tmp=4;
}
//从用户空间向内核空间写入数据
if(copy_from_user(mem,buf,tmp)){
return -EFAULT;
}else{
//依次控制四个led
for(i=0;i<4;i++){
//读取gpkdat寄存器的当前值
tmp=ioread32(S3C64XX_GPKDAT);
//如果字符为1则点亮当前的led
if(mem[i]=='1'){
tmp&=(~(1<<(i+4)));
}else{//如果字符为0则灭掉当前的led
tmp|=(1<<(i+4));
}
iowrite32(tmp,S3C64XX_GPKDAT);
}
return count;
}
}
//定义file_operation结构体
//设备文件回调指针
static struct file_operations dev_fops={
.owner = THIS_MODULE,
.unlocked_ioctl = mini6410_leds_ioctl,
.write = mini6410_leds_write
};
//-------创建设备文件
static int leds_create_device(void){
int ret =0;
int err=0;
//初始化cdev成员
cdev_init(&leds_cdev,&dev_fops);
//创建的设备文件属于当前的驱动模块
leds_cdev.owner=THIS_MODULE;
//主设备号大于0,通过指定设备号的方式注册字符设备
if(major>0){
//获取设备号
dev_number=MKDEV(major,minor);
//通过指定设备号的方式注册字符设备区域
err=register_chrdev_region(dev_number,DEVICE_COUNT,DEVICE_NAME);
//注册失败
if(err<0){
printk(KERN_WARNING "register_chrdev_region() failed\n");
return err;
}
}
else{//主设备号为0,自动分配主设备号和次设备号
//通过自动分配主设备号和次设备号的方式
//10表示起始次设备号
err=alloc_chrdev_region(&leds_cdev.dev,10,DEVICE_COUNT,DEVICE_NAME);
//注册失败
if(err<0){
printk(KERN_WARNING "alloc_chrdev_region() failed\n");
return err;
}
//获取主设备号
major=MAJOR(leds_cdev.dev);
//获取次设备号
minor=MINOR(leds_cdev.dev);
//自动分配的设备号
dev_number= leds_cdev.dev;
}
//将字符设备添加到内核中的字符设备数组中
cdev_add(&leds_cdev,dev_number,DEVICE_COUNT);
//创建struct class
leds_class=class_create(THIS_MODULE,DEVICE_NAME);
//创建设备文件
device_create(leds_class,NULL,dev_number,NULL,DEVICE_NAME);
return ret;
}
//----------初始化寄存器
static void leds_init_gpk(int leds_default){
int tmp=0;
//------------初始化gpkcon寄存器----------
tmp=ioread32(S3C64XX_GPKCON);
//保留低16位的值,高16位清零
tmp &= (~0xFFFF0000);
//将低16位设为1111
tmp |=0x11110000;
//向gpmcon写入数据
iowrite32(tmp,S3C64XX_GPKCON);
//----------初始化gpkpud寄存器-----------
//读取gpkpud寄存器的当前值
tmp=ioread32(S3C64XX_GPKPUD);
//4567位清零
tmp&=(~0xFF00);
//打开led上拉电路
tmp|=0xAA00;
//向gpkpud写入数据
iowrite32(tmp,S3C64XX_GPKPUD);
//----------初始化gpkdat寄存器-----------
//读取gpkdat寄存器的当前值
tmp=ioread32(S3C64XX_GPKDAT);
//将4567位清零
tmp&=(~0xF0);
//设置四个led默认的亮灭状态
tmp|=leds_default;
//向gpkdat寄存器写入数据
iowrite32(tmp,S3C64XX_GPKDAT);
}
//销毁字符设备
static void leds_destroy_device(void){
//销毁字符设备
device_destroy(leds_class,dev_number);
//销毁class结构体
if(leds_class){
class_destroy(leds_class);
}
//注销字符设备区
unregister_chrdev_region(dev_number,DEVICE_COUNT);
return;
}
//-----------------------------------------------------
//初始化led驱动
static int leds_init(void){
int ret;
ret=leds_create_device();
leds_init_gpk(~leds_state);
printk(DEVICE_NAME"\t initialized \n");
printk("param0\t %s \n",params[0]);
printk("param1\t %s \n",params[1]);
printk("param2\t %s \n",params[2]);
return ret;
}
//卸载led驱动
static int leds_exit(void){
leds_destroy_device();
printk(DEVICE_NAME"\t exit! \n");
}
//指定初始化函数
module_init(leds_init);
//指定卸载函数
module_exit(leds_exit);
//指定int类型的模块参数
module_param(leds_state,int ,S_IRUGO | S_IWUSR);
//指定数组类型的模块参数
module_param_array(params,charp,¶m_size,S_IRUGO | S_IWUSR);
//指定开源协议
MODULE_LICENSE("GPL");
//指定驱动作者
MODULE_AUTHOR("retacn_yue");
编译
A 编译成动态驱动模块进行测试
脚本build.sh文件内容如下:
source ./build_mini6410.sh
脚本build_mini6410.sh文件的内容如下:
# build_mini6410.sh
source /opt/linux/driver/common.sh
make -C $MINI6410_ANDROID_KERNEL_PATH M=${PWD}
find_devices
if [ "$selected_device" == "" ]; then
exit
else
adb -s $selected_device push ${PWD}/mini6410_leds.ko /data/local
testing=$(adb -s $selected_device shell lsmod | grep "mini6410_leds")
if [ "$testing" != "" ]; then
adb -s $selected_device shell rmmod mini6410_leds
fi
adb -s $selected_device shell "insmod /data/local/mini6410_leds.ko"
adb -s $selected_device shell "chmod 777 /dev/mini6410_leds"
fi
Makefile文件内容如下:
obj-m := mini6410_leds.o
编译自动将.ko文件上传到开发板
直接使用adb shell进行测试
Adb shell “echo ‘1010’ >/dev/mini6410_leds”
使用test_leds.sh进行测试(向led设备发送字符串来测试),示例代码如下:
#test_leds.sh
for((i=0;i<16;i=i+1))
do
#将十进制转换为二进制形式
n=$(echo "obase=2;$i" | bc)
echo $n
echo $n > temp
#反转二进制数
n=$(rev temp)
#向led设备发送控制字符串
adb shell "echo $n > /dev/mini6410_leds"
#延迟一秒钟
sleep 1
done
B 也可以将驱动程序像之前一样编译进android内核,然后进行测试
将动驱程序编译进android系统的linux内核进行测试
将mini6410_leds.c放到linux内核代码中(由于android内核中已经有一个mini6410_leds.c文件,将原文件改名为mini6410_leds_old.c)
root@vm:/opt/kernel/linux-2.6.36-android/drivers/char# cp /opt/linux/driver/mini6410_leds/mini6410_leds.c ./
修改kconfig文件,在endmenu前添加如下代码:
//源代码中已有不需要再添加
config MINI6410_LEDS
tristate "LED Support for Mini6410 GPIO LEDs"
depends on CPU_S3C6410
default y
help
This option enables support for LEDs connected to GPIO lines
on Mini6410 boards.
C 修改makefile文件,添加如下内容
//源代码中已有所以不需要添加
obj-$(CONFIG_MINI6410_LEDS)+= mini6410_leds.o
obj-$(CONFIG_MINI6410_HELLO_MODULE) += mini6410_hello_module.o
obj-$(CONFIG_MINI6410_BUTTONS) += mini6410_buttons.o
obj-$(CONFIG_MINI6410_BUZZER) += mini6410_pwm.o
obj-$(CONFIG_MINI6410_ADC) += mini6410_adc.o
D 设置.config文件,可以通过make menuconfig来进行配置
Device Drivers ---> Character devices ---> [*] word_count_driver
打开.config文件可以看到该项设置为y
CONFIG_MINI6410_LEDS=y
E 编译linux内核
Make
烧写到开发板进行测试
/ # chmod 777 /dev/mini6410_leds
使用adb shell进行测试
/dev # echo '1010' >/dev/mini6410_leds
测试led驱动
编写测试i/o命令的测试程序
Test_ioctl.c文件内容如下:
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/ioctl.h>
int main(int argc, char **argv)
{
//三个命令行参数
int file_handler = 0;
int cmd = 0;
int arg = 0;
if(argc < 4)//命令行参数至少是三个
{
printf("Usage: ioctl <dev_file> <cmd> <arg>\n");
return 0;
}
//
cmd = atoi(argv[2]);
//
arg = atoi(argv[3]);
//输出命令行参数
printf("dev:%s\n", argv[1]);
printf("cmd:%d\n", cmd);
printf("arg:%d\n", arg);
//打开设备文件
file_handler = open(argv[1], 0);
//发送命令和参数
ioctl(file_handler, cmd, arg);
//关闭设备文件
close(file_handler);
return 0;
}
编译有两种方法:
1 在android源码环境下编译,未测试????????????
2 使用交叉编译工具进行测试
#使用交叉编译器进行编译
#执行ioctl命令的格式如下:ioctl <设备文件> <cmd> <arg>
arm_linux_gcc -static -o ioctl ${PWD}/test_ioctl.c
adb push ${PWD}/ioctl /data/local/ioctl
adb shell /data/local/ioctl
测试命令如下:
/data/local # ./ioctl /dev/mini6410_leds 2 2
使用androidNDK进行测试
注:chmod 777 /dev/mini6410_leds
MainActivity文件示例代码如下:
package cn.yue.android.mini6410.leds;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.CheckBox;
import android.app.Activity;
/**
* 测试led驱动程序
*
* @version
*
* @Description:
*
* @author <a href="mailto:[email protected]">Retacn</a>
*
* @since 2014-10-24
*
*/
public class MainActivity extends Activity implements OnClickListener {
private CheckBox[] cb_str = new CheckBox[4];
private CheckBox[] cb_cmd = new CheckBox[4];
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
findView();
}
/**
* 实例代控件
*/
private void findView() {
cb_str[0] = (CheckBox) this.findViewById(R.id.checkbox_str_led1);
cb_str[1] = (CheckBox) this.findViewById(R.id.checkbox_str_led2);
cb_str[2] = (CheckBox) this.findViewById(R.id.checkbox_str_led3);
cb_str[3] = (CheckBox) this.findViewById(R.id.checkbox_str_led4);
cb_cmd[0] = (CheckBox) this.findViewById(R.id.checkbox_cmd_led1);
cb_cmd[1] = (CheckBox) this.findViewById(R.id.checkbox_cmd_led2);
cb_cmd[2] = (CheckBox) this.findViewById(R.id.checkbox_cmd_led3);
cb_cmd[3] = (CheckBox) this.findViewById(R.id.checkbox_cmd_led4);
this.findViewById(R.id.btn_send_str).setOnClickListener(this);
this.findViewById(R.id.btn_send_io).setOnClickListener(this);
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.btn_send_str:
Log.i("tag", "----------sending str...");
String str = "";
for (int i = 0; i < 4; i++) {
Log.i("tag", "cb_str" + i + cb_str[i].isChecked());
if (cb_str[i].isChecked()) {
str += "1";
} else {
str += "0";
}
}
// 发送控制字符串
strLeds(str);
break;
case R.id.btn_send_io:
Log.i("tag", "----------sending cmd...");
for (int i = 0; i < 4; i++) {
Log.i("tag", "cb_cmd" + i + cb_cmd[i].isChecked());
if (cb_str[i].isChecked()) {
cmdLeds(1, i);
} else {
cmdLeds(0, i);
}
}
break;
}
}
// 定义本地方法
public native void strLeds(String str);
public native void cmdLeds(int cmd, int arg);
static {
System.loadLibrary("ndk_test_mini6410_leds");
}
}
#include <string.h>
#include <jni.h>
#include <fcntl.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <stdlib.h>
#include "cn_yue_android_mini6410_leds_MainActivity.h"
char* jstring_to_pchar(JNIEnv* env, jstring str) {
char* pstr = NULL;
jclass clsstring = (*env)->FindClass(env, "java/lang/String");
jstring strencode = (*env)->NewStringUTF(env, "utf-8");
jmethodID mid = (*env)->GetMethodID(env, clsstring, "getBytes",
"(Ljava/lang/String;)[B");
jbyteArray byteArray = (jbyteArray) ((*env)->CallObjectMethod(env, str, mid,
strencode));
jsize size = (*env)->GetArrayLength(env, byteArray);
jbyte* pbyte = (*env)->GetByteArrayElements(env, byteArray, JNI_FALSE);
if (size > 0) {
pstr = (char*) malloc(size);
memcpy(pstr, pbyte, size);
}
return pstr;
}
void Java_cn_yue_android_mini6410_leds_MainActivity_strLeds(JNIEnv* env,
jobject thiz, jstring str) {
int dev;
dev = open("/dev/mini6410_leds", O_WRONLY);
char* pstr = jstring_to_pchar(env, str);
if (pstr != NULL) {
write(dev, pstr, strlen(pstr));
}
close(dev);
}
void Java_cn_yue_android_mini6410_leds_MainActivity_cmdLeds(JNIEnv* env,
jobject thiz, jint cmd, jint arg) {
int dev;
dev = open("/dev/mini6410_leds", O_WRONLY);
ioctl(dev, cmd, arg);
close(dev);
}