嵌入式Linux字符设备入门之--LED驱动详解

来源:互联网 发布:ubuntu打开ssh服务 编辑:程序博客网 时间:2024/04/29 02:21

一、实验环境

PC机:Redhat9.0  内核版本:Linux2.4

开发板:FL2440     内核版本:Linux2.6      CPUs3c2440

二、实验步骤

1、硬件连接。

       FL2440有四个可编程控制的共阳极LED,分别与CPUGPB5,GPB6,GPB8,GPB10相连。

2、驱动程序

       由于是刚刚接触驱动,实在是不知道怎么写,只好用开发板自带的led驱动了(手动分配设备号),我还写了一个自动分配设备号的程序。我所做的就是分析这个程序,争取把它给读懂。下面是开发板led驱动的源程序。

程序里的注释是我参考网上的资料自己加的,主要参考的是mini2440led驱动程序的注释内容,不知道对不对!

a:手动分配设备号

/*-----------------s3c2440_leds.c--------------------------------*/

#include <linux/config.h>

#include <linux/module.h>

#include <linux/kernel.h>

#include <linux/fs.h>

#include <linux/init.h>

#include <linux/devfs_fs_kernel.h>//?????????????????

#include <linux/miscdevice.h>//???????????????

#include <linux/delay.h>

#include <asm/irq.h>

#include <asm/arch/regs-gpio.h>

#include <asm/hardware.h>

 

#define DEVICE_NAME       "leds"    //设备名

#define LED_MAJOR 231           //主设备号

 

 

/*----------------------------------------------------------------------------------*/

//定义引脚的寄存器数组(无符号长整形,对应于引脚的地址)

 

static unsigned long led_table [] = {  

       S3C2410_GPB5,

       S3C2410_GPB6,

       S3C2410_GPB8,

       S3C2410_GPB10,

      

};

 

/*---------------------------------------------------------------------------------*/

//定义引脚功能,为输出(无符号整形)

 

static unsigned int led_cfg_table [] = {      

 

       S3C2410_GPB5_OUTP,            //0x01<<10 defined in  refg-gpio.h

       S3C2410_GPB6_OUTP,

       S3C2410_GPB8_OUTP,

       S3C2410_GPB10_OUTP,

};

 

/*-------------------------------------------------------------------------------*/

//测试程序调用此函数,实现对led的控制

//设备节点,文件描述符,LED灯状态,LED灯编号四个命令参数

 

static int s3c2440_leds_ioctl(

                                       struct inode *inode,

                                       struct file *file,

                                       unsigned int cmd,

                                       unsigned long arg)

{                                   

       switch(cmd) {

       case 0:

       case 1:

              if (arg > 4)

              {

                     return -EINVAL; /* Invalid argument,非法参数 */

 

              }

 

             

               //低电平有效,用户程序传来的cmd取反

 

              s3c2410_gpio_setpin(led_table[arg], !cmd);//设置数据寄存器gpbdatcmd的非

              return 0;

       default:

              return -EINVAL;

       }

}

/*----------------------------------------------------------------------------*/

 

 

static struct file_operations s3c2440_leds_fops = {

       .owner    =     THIS_MODULE,

       .ioctl       =     s3c2440_leds_ioctl,

};

/*---------------------------------------------------------------------------*/

//初始化函数

static int __init s3c2440_leds_init(void)

{

       int ret;

       int i;

 

       ret = register_chrdev(LED_MAJOR, DEVICE_NAME, &s3c2440_leds_fops);//注册设备

       if (ret < 0)

       {

         printk(DEVICE_NAME " can't register major number/n");

         return ret;

       }

 

       devfs_mk_cdev(MKDEV(LED_MAJOR, 0), S_IFCHR | S_IRUSR | S_IWUSR | S_IRGRP, DEVICE_NAME);

       //上句的作用是干甚??????

 

       for (i = 0; i < 4 ; i++)

       {

        /*-----------------------------------------------------------------------*/    

              //设置GPIO对应的配置寄存器GPIOCON为输出状态,在头文件

              //linux-2.6.12/include/asm-arm/arch-s3c2410/hardware.h中的函数原型为:

        //extern void s3c2410_gpio_cfgpin(unsigned int pin, unsigned int function);

          

              s3c2410_gpio_cfgpin(led_table[i], led_cfg_table[i]);

 

        /*-----------------------------------------------------------------------*/

        //设置GPIO对应的数据寄存器GPIODAT为高电平

              //在模块加载结束后,四个LED应该是全部都是不发光状态

 

              s3c2410_gpio_setpin(led_table[i], 1);

 

              /*-----------------------------------------------------------------------*/

       }

 

       printk(DEVICE_NAME " initialized/n");

       return 0;

}

/*------------------------------------------------------------------------------------*/

//注销设备

static void __exit s3c2440_leds_exit(void)

{

       devfs_remove(DEVICE_NAME);

       unregister_chrdev(LED_MAJOR, DEVICE_NAME);

}

 

/*------------------------------------------------------------------------------------*/

module_init(s3c2440_leds_init);//执行insmod是调用该函数

module_exit(s3c2440_leds_exit);//执行rmmod是调用该函数

/*-----------------------------------------------------------------------------------*/

 

b:自动分配设备号

#include <linux/config.h>

#include <linux/module.h>

#include <linux/kernel.h>

#include <linux/fs.h>

#include <linux/init.h>

#include <linux/devfs_fs_kernel.h>

#include <linux/miscdevice.h>

#include <linux/delay.h>

#include <asm/irq.h>

#include <asm/arch/regs-gpio.h>

#include <asm/hardware.h>

 

#define DEVICE_NAME "leds"

 

static unsigned long led_table [] = {

                     S3C2410_GPB5,

                     S3C2410_GPB6,

                     S3C2410_GPB8,

                     S3C2410_GPB10,

};

 

static unsigned int led_cfg_table [] = {

                     S3C2410_GPB5_OUTP,

                     S3C2410_GPB6_OUTP,

                     S3C2410_GPB8_OUTP,

                     S3C2410_GPB10_OUTP,

};

 

static int s3c2440_leds_ioctl(

                     struct inode *inode,

                     struct file *file,

                     unsigned int cmd,

                     unsigned long arg)

{

switch(cmd) {

              case 0:

              case 1:

       if (arg > 4)

              {

                         return -EINVAL;

            }    

     s3c2410_gpio_setpin(led_table[arg], !cmd);

     return 0;

       default:

            return -EINVAL;

              }

              }

                    

static struct file_operations s3c2440_leds_fops = {

              .owner = THIS_MODULE,

              .ioctl = s3c2440_leds_ioctl,

};

 

static struct miscdevice misc = {

              .minor = MISC_DYNAMIC_MINOR,

              .name = DEVICE_NAME,

              .fops = &s3c2440_leds_fops,

};

 

static int __init s3c2440_leds_init(void)

{

       int ret;

       int i;

 

       for (i = 0; i < 4; i++)

       {

       s3c2410_gpio_cfgpin(led_table[i], led_cfg_table[i]);

       s3c2410_gpio_setpin(led_table[i], 1);

       }

       ret = misc_register(&misc);

 

       printk (DEVICE_NAME"/tinitialized/n");

       return ret;

}

 

static void __exit s3c2440_leds_exit(void)

{

       misc_deregister(&misc);

}

module_init(s3c2440_leds_init);

module_exit(s3c2440_leds_exit);

这个程序我没有做注释,参考上一个程序吧!不过这里有些需要说明的,用insmod命令能加载到内核,用lsmod命令也可以查到,但是运行测试程序时却出不来该有的现象,并且提说找不到文件。(下面做了解释)

3、编译驱动程序

写个Makefile文件编译,2.6版本的内核好像只能这么编译驱动,不能在命令行里一步步的通过命令编译,不过2.4内核的可以。(不知道是不是这么回事?)

当然Makefile我也不会写,用的板子带的那个!

 

#Makefile for s3c2440_leds.c

obj-m :=s3c2440_leds.o

KERNELDIR ?=/usr/linux-2.6.12

PWD := $(shell pwd)

default:

       $(MAKE) -C $(KERNELDIR) M=$(PWD) modules

 

make后生成了四个文件

           

我只知道s3c2440_leds.ko是我想要的,其他三个不道是干嘛的?

4、将驱动加载到内核

       有两种方式:

一是把驱动程序直接编译进内核;细节问题我没有怎么研究,大致上就是写好驱动程序之后,放到内核代码的目录里,当然还得修改几个文件,与内核一同编译。这样内核启动后会自动加载,免去很多麻烦,但要占用资源!

二是动态的将驱动加载进内核;这种方式比较适合实验开发,挺方便的,就是每次用到驱动是都要手动加载,我采用的就是这种方式!不过有这种方式有关前题,就是你的内核必须支持动态的加载模块,这个在你配置内核的时候要选择此项支持!

到你的内核的目录下,make menuconfig,第三项“Loadable module support”,选上“Enable loadable module support”,其他的随便,我就多选了个“Module unloading”。然后,重新编译内核。(参考的别人的,我没弄过!!!)

   下面是加载的过程:

       PC机的根目录挂到开发板的tmp目录下

       #mount 192.168.2.32:/ /tmp

       加载驱动模块

       #insmod s3c2440_led.ko

       可以看看有没有加载上

       #lsmod

                     Module           size                usedby                  tainted:P

                     S3c2440_leds 1664         0            

       Ok了,加载成功,现在可以写个测试程序测试一下了!

       顺便说一下如何卸载驱动模块吧!

       #rmmod s3c2440_leds.ko

 

       注意:如果你加载的是a驱动程序,可以到/dev目录下查看一下,发现在字符设备中有关叫leds的设备,设备号231。但是如果你加载的是b驱动程序,你在/dev目录下是找不到leds的,不过你可以进的一个叫misc的目录,原来leds在这里,我试验内核分给它的设备号是63,不道为啥没有想a程序那样直接出现在/dev目录下。这也带来了麻烦,就是写测试程序时,针对两种不同的驱动程序,测试程序也略有不同!(后面会说,见测试程序)

 

5、测试程序

/*-----------------------led--------------------------------------------*/

/*这个程序实现的是跑马灯*/

#include "stdio.h"

#include "unistd.h"

#include "sys/types.h"

#include "sys/ioctl.h"

#include "stdlib.h"

#include "termios.h"

#include "sys/stat.h"

#include "fcntl.h"

#include "sys/time.h"

int main(void)

{

      

       int on=1;

       int led;

       int fd;

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

/*如果有加载的是b驱动程序模块,这里要写出:fp=open(“/dev/misc/leds”,0)*/

       if(fd<0)

       {

              perror("open device leds");

              exit(1);

       }

       printf("leds test show,press ctrl+c to exit /n");

       while(1)

       {

              for(led=0;led<4;led++)

              {

                     ioctl(fd,on,led);

                     usleep(50000);

              }

              on=!on;

       }

       close(fd);

       return 0;

}

6、编译测试程序,完成测试

将测试程序交叉编译,在板子上执行:

#./led

看效果吧,漂亮!!!!!

三、实验总结:

这个实验其实做了好多天,在做之前也做了很多准备,因为以前也没接触过这些,也没有什么基础,确实挺费劲的!而且我之前看的都是2.4内核的驱动,2.4的驱动与2.6的驱动相比有很多不同的地方,这是值得注意的。我还是觉得2.4内核的驱动更容易理解!

       通过这个实验对驱动的编写有了一些了解,当然这只是入门的入门,还需更加努力。由于才粗学浅,一定有很多不对的地方,当然也有很多不懂得地方,请多多指教!

原创粉丝点击