微波EDA网,见证研发工程师的成长!
首页 > 研发问答 > 嵌入式设计讨论 > 嵌入式系统设计讨论 > 嵌入式Linux字符驱动LED灯设计

嵌入式Linux字符驱动LED灯设计

时间:10-02 整理:3721RD 点击:

嵌入式Linux字符驱动LED灯设计

嵌入式Linux字符设备驱动LED驱动编写



一.任务要求

完成一个字符IO口驱动,在开发板上该IO口对应LED灯。该驱动程序通过控制IO口的高低电平来控制亮灭。同时要写一个应用层的测试程序,用来测试驱动程序。我的测试程序为myled_test.c,要求在shell下能够通过该测试程序来控制LED灯的亮灭。如:

./myled_test on   表示灯全亮;

./myled_test off   表示灯全灭;

二.流程图设计

图1.应用层访问设备的流程图


三.    字符IO口驱动程序的设计流程


1)Linux内核的模块机制

在Linux下,驱动程序都是以模块存在的,模块是向内核动态的增加功能,每个模块都包括module_init和module_exit两个函数,分别在向系统插入模块和移除模块时被调用。框架如下:


#include <linux/module.h>

#include <linux/init.h>

static int hellomodule_init(void)

{


       printk("hello word\n");

       return 0;


}

static void hellomodule_exit(void)

{


       printk("goodbye word\n");

       return;


}


module_init(hellomodule_init);

module_exit(hellomodule_exit);


MODULE_LICENSE("GPL");


<hellomodule_init 和hellomodule_exit通过module_init和 module_exit两个宏注册到内核,这样在通过insmod和rmmod命令往内核增加移除时候就会调用。>


2)Linux字符IO驱动设计

步骤如下:

1.      定义描述字符IO设备的结构体

在Linux中,每个设备都有一个结构体来描述的,该结构体包含了该设备的所有信息。如下:


struct cdev

{

        struct kobject kobj;

        struct module *owner;

        const struct file_operations *ops;

        struct list_head list;

        dev_t dev;

        unsigned int count;

};


2.      定义设备结构体变量

用设备结构体来定义一个变量,在内核中该变量就代表对应的设备。如下:

struct cdev myled_cdev;


3.      定义设备的操作接口函数

设备都是有一些操作的,应用程序就通过这些接口操作函数来使用驱动程序对设备的控制。如下:


static struct file_operations led_ops={

.open = myled_open,

.ioctl = myled_ioctl,

.release = myled_close,

};


4.      设备的操作函数

根据设备的操作接口函数完成操作函数,如下:


int myled_open(struct inode *inode,struct file *file)

{


int value = 0;

value = (1<<10)|(1<<12)|(1<<16)|(1<<20);/*将GPB5,6,8,10设为输出功能*/

writel(value,S3C2410_GPBCON);

value = 0x0;/*引脚都为低电平,灯全亮*/

writel(value,S3C2410_GPBUP);

value = 0xfffffffe;/*引脚都为高电平,灯全灭蜂鸣器不响*/

writel(value,S3C2410_GPBDAT);

return 0;

}

<在应用程序执行open()系统调用时,myled_open函数将被调用。将LED的GPIO引脚设为输出功能。>


int myled_ioctl(struct inode *inode,struct file *file,unsigned int cmd,unsigned long arg)

{


switch(cmd)

{

        case LED_ON:/*设置引脚为输出低电平0*/

        writel(0x0,S3C2410_GPBDAT);

        break;

        case LED_OFF: /*设置引脚为输出高电平1*/

        writel(0xfffffffe,S3C2410_GPBDAT);

        break;

        default:

        break;

}     


return 0;

}


<当应用程序执行系统调用ioctl(fd,cmd)时,(fd为open()系统调用返回的文件句柄,)myled_ioctl函数将被调用。>


int myled_close(struct inode *inode,struct file *file)

{


printk("bye");

return 0;

}


5.      字符驱动模块的初始化和退出函数

这两个函数是模块的框架,定义如下:


static int __init LED_INIT(void)

{      

int re = 0;

int ret = 0;

myled_no = MKDEV(MAINLEDNO,MINLEDNO);

ret = register_chrdev_region(myled_no,1,"myled");

if(ret<0)

{

        printk("cant regist");

        return ret;

}

printk("reg ok");

cdev_init(&myled_cdev,&led_ops);

myled_cdev.owner = THIS_MODULE;


re = cdev_add(&myled_cdev,myled_no,1);

if(re<0)

{

        printk("add error");

        return re;

}


printk("add ok");

return 0;

}


<LED_INIT主要完成3件事:调用register_chrdev_region询问内核该设备号是否有设备占用,如果没有就继续下面操做,初始化设备结构体变量cdev和向内核注册字符IO设备,分别调用cdev_init和 cdev_add。>


static void __exit LED_EXIT(void)

{

cdev_del(&myled_cdev);

unregister_chrdev_region(myled_no,1);

printk("exit ok");

}


       <字符模块退出函数主要是删除内核中的字符设备并卸载驱动程序,分别调用cdev_del 和unregister_chrdev_region>   。


         最后向内核申明myled_init和myled_exit函数以及LICENSE。如下:


module_init(LED_INIT);

module_exit(LED_EXIT);

MODULE_LICENSE("GPL");


6.      设备驱动的编译

Linux驱动模块的编译是通过Linux顶层下的Makefile文件来实现的,如下:


obj-m:=myled.o

KDIR=/home/neo/linux-2.6.30.9-EL-20101126


all:

make -C $(KDIR) SUBDIRS=$(shell pwd) modules

arm-linux-gcc -o myled_test myled_test.c


<myled.o是我们需要编译的模块,KDIR是内核目录,SUNDIRS是驱动源码所在目录,arm-linux-gcc是交叉编译器。>


四.    字符IO驱动测试程序设计流程

为了测试IO驱动是否正常,在应用层编写一个LED灯的程序,主要完成打开,关闭功能。如下:


int main(int argc,char **argv)

{

int fd;                        

fd = open("/dev/ledS0",0);/*打开设备*/

if(!strcmp(argv[1],"on"))

{

ioctl(fd,LED_ON);/*点亮*/

}

else if(!strcmp(argv[1],"off"))

{

ioctl(fd,LED_OFF);/*熄灭*/

}

return 0;

}


<其中的open,ioctl最终会调用驱动程序中的myled_open,myled_ioctl函数。>


五.    编译及下载流程


1.动态加载

a)        将myled.c和myled_test.c用make进行编译,得到模块myled.ko和执行文件myled_test

b)        更新S3C2440的内核和文件系统。启动开发板的LINUX系统。

c)        往开发板内核中添加驱动模块

在shell下执行insmod myled.ko

d)        创建设备文件节点

在shell下执行mknod /dev/ledS0 c 108 0

<c:设备类型

108:主设备号

0:次设备号>

e)        将执行文件myled_test添加到开发板上,并执行:

./myled_test on

./myled_test off


2.静态加载


a)        打开内核源码文件包,将myled.c文件拷到driver文件夹下,打开Kconfig文件,在其中添加如下代码:


config LINETECH_LED

bool "config myled"

default y

help

myled driver test


b)        同时打开Makefile文件,添加如下代码:

   obj-$(CONFIG_LINETECH_LED)      +=myled.o

c)        在终端输入make menuconfig进行内核的配置,如图:


最后一个选项即为我要选得myled配置。

d) 将内核编译得到zImage,下载到开发板中,创建设备文件节点

在shell下执行mknod /dev/ledS0 c 108 0

<c:设备类型

108:主设备号

0:次设备号>\

e) 将执行文件myled_test添加到开发板上,并执行:

./myled_test on

./myled_test off


三.  总结

通过这次对LINUX字符驱动LED灯的设计让我获益匪浅,该实验让我对驱动程序的设计有了大概的了解。首先,要知道驱动程序必须要有框架,即初始化和退出。然后,每个设备都有与之对应的结构体,而应用层要使用驱动程序,其中必须要有接口操作函数,特别注意下我在myled_open中将LED的寄存器进行配置,而不是在初始化函数中设置,是因为:虽然加载了模块,但是这个模块却不一定会被用到,所以在使用时才去设置。最后在初始化函数中要对cdev结构体变量进行初始化,并把该结构体注册到内核中。

概括为:应用程序在内核的字符设备数组中能够找到主设备号,根据设备号找到该设备的结构体cdev,访问结构体中的变量*ops,而*ops指向file_operation结构体,该结构体中有被应用层调用的函数。


四.    代码

//myled.c


#include <linux/module.h>

#include <linux/init.h>

#include <linux/fs.h>

#include <linux/cdev.h>

#include <linux/types.h>

#include <mach/regs-gpio.h>

#include <asm/io.h>


#define MAINLEDNO 108

#define MINLEDNO 0

#define LED_ON 0x01

#define LED_OFF 0x02


dev_t myled_no = 0;

struct cdev myled_cdev;

````````int myled_open(struct inode *inode,struct file *file)

{


int value = 0;


value = (1<<10)|(1<<12)|(1<<16)|(1<<20);

writel(value,S3C2410_GPBCON);


value = 0x0;

writel(value,S3C2410_GPBUP);


value = 0xfffffffe;

writel(value,S3C2410_GPBDAT);


return 0;

}


int myled_ioctl(struct inode *inode,struct file *file,unsigned int cmd,unsigned long arg)

{


switch(cmd)

{

case LED_ON:

writel(0x0,S3C2410_GPBDAT);

break;

case LED_OFF:

writel(0xfffffffe,S3C2410_GPBDAT);

break;

default:

break;

}                           

return 0;

}


int myled_close(struct inode *inode,struct file *file)

{


printk("bye");

return 0;

}


static struct file_operations led_ops={

.open = myled_open,

.ioctl = myled_ioctl,

.release = myled_close,

};


static int __init LED_INIT(void)

{


int re = 0;

int ret = 0;

myled_no = MKDEV(MAINLEDNO,MINLEDNO);

ret = register_chrdev_region(myled_no,1,"myled");

if(ret<0)

{

printk("cant regist");

return ret;

}


printk("reg ok");


cdev_init(&myled_cdev,&led_ops);

myled_cdev.owner = THIS_MODULE;


re = cdev_add(&myled_cdev,myled_no,1);

if(re<0)

{

printk("add error");

return re;

}


printk("add ok");

return 0;

}


static void __exit LED_EXIT(void)

{


cdev_del(&myled_cdev);

unregister_chrdev_region(myled_no,1);

printk("exit ok");

}


module_init(LED_INIT);

module_exit(LED_EXIT);


MODULE_LICENSE("GPL");


//myled_test.c


#include <stdio.h>

#include <string.h>

#include <stdlib.h>

#include <unistd.h>

#include <fcntl.h>

#define LED_ON 0x01

#define LED_OFF 0x02


int main(int argc,char **argv)

{


int fd;


fd = open("/dev/ledS0",0);

if(!strcmp(argv[1],"on"))

{

ioctl(fd,LED_ON);

}

else if(!strcmp(argv[1],"off"))

{

ioctl(fd,LED_OFF);

}

return 0;


}


Makefile


obj-m:=myled.o

KDIR=/home/neo/linux-2.6.30.9-EL-20101126


all:

make -C $(KDIR) SUBDIRS=$(shell pwd) modules

arm-linux-gcc -o myled_test myled_test.c


南京 英贝得嵌入式学院 ---近几年 首次打入南京市场  最人性化的学习费用  我们教的 是你在书本上所学不到的知识。 以圆满成功的 寒假班迎接我们 “3月10日 的周末班'"4月10日的就业班”  报名既享受更多的优惠 。

顶一个

顶一个

支持一下

好长~

初学者,学习一下

顶一个!

Copyright © 2017-2020 微波EDA网 版权所有

网站地图

Top