./app-led 1 回车灯全亮;./app-led 0 回车 灯全熄了。真是太令我激动。这可是在linux下的操作。Linux下的驱动程序。呵呵,以前我学单片机的时候,郭天祥讲的第一个实验就是点灯实验,那个时候也很激动,感觉真是神奇几句程序就能控制灯的亮,熄,就是从单片机的这个点灯实验开始,我也算是入了单片机的门,后面在电子设计大赛中,和帮别人做的一些东西中,获得了一些成就感。随后我又在arm7那块板子上做了裸板的流水灯实验。今天终于在传说中的linux中实现了点灯的实验,到现在我都搞了近一年的嵌入式学习了,今天算了感觉有了点门道,为什么这么说呢,以前是有例程,但是不知道环境怎么搭建,也就没法做实验,学习当然就没有进步,而今天这个环境被搭建起来了,虽然说只是一个点灯实验,但是以后的大量的实验都可以做了,总算有了可以走的路了。

一:看效果

这个实验基于昨天搭建好的环境;minicom,linux内核,NFS起根文件系统,这个环境的搭建也一件相当痛苦的事情。现在在学习驱动程序就不过多的去关注这个东西,这个还得专门作为一个话题来学习。总之现在的环境就是minicom中是开发板上面的linux系统,这里的文件系统是通过NFS起根文件系统,也就是说这个路径是可以和宿主机的进行共享的,在宿舍机上写的程序利用交叉编译工具编译,然后拷贝到NFS根文件系统,就可以在开发板上linux中运行,也就可以去控制开发板了。这里对内核所做的一小点修改就是内核配置时去掉 驱动程序—》字符设备驱动—》LED Support for Mini2440/QQ2440 GPIO LEDs免得它和我写的冲突。

写好的驱动程序,和相应的应用测试程序位于宿主机的/home/532_526下直接make得mini2440_leds_misc.ko驱动模块,然后arm-linux-gcc app-led.c -o app-led。生成app-led的应用程序。将mini2440_leds_misc.ko和app-led拷贝到NFS起根文件系统的tmp目录下。然后换到minicom中的linux。ls tmp就可以看到刚才拷贝过去的两个文件,执行insmod mini2440_leds_misc.ko,按以前还得创建设备文件但是这个程序为我们自动创建了设备文件/dev/leds。然后./app-led 1 回车灯全亮;./app-led 0 回车 灯全熄了。

二:点灯实验代码分析:

(1)所用到的知识

   首先来看一下这个驱动程序中所用到一些知识。

A:IOCTL方法 这个在前面已经学习过了,它的实现主要分为两步:定义命令,实现命令。定义命令放在了memdev.h这个头文件中。如下代码:

/* 定义幻数 */

#define MEMDEV_IOC_MAGIC  'k'

 

/* 定义命令 */

#define MEMDEV_IOCON   _IO(MEMDEV_IOC_MAGIC, 1)

#define MEMDEV_IOCOFF _IO(MEMDEV_IOC_MAGIC, 2)

 

#define MEMDEV_IOC_MAXNR 2

由上面可知定义了两个命令,这也是这个驱动程序的整体功能,四个灯的全亮,和全灭。

实现命令如下:

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

{

int i = 0;

    /* 检测命令的有效性 */

  if (_IOC_TYPE(cmd) != MEMDEV_IOC_MAGIC) 

      return -EINVAL;

  if (_IOC_NR(cmd) > MEMDEV_IOC_MAXNR) 

      return -EINVAL;

      

  /* 根据命令,执行相应的操作 */

switch(cmd) {

case MEMDEV_IOCOFF:

/*灯全灭*/

cmd = 1;

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

s3c2410_gpio_setpin(led_table[i], cmd);

return 0;

 

case MEMDEV_IOCON:

/*灯全亮*/

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

s3c2410_gpio_setpin(led_table[i], !cmd);

return 0;

default:

return -EINVAL;

}

}

这里就是将定义的两个操作实现了。

/*文件操作结构体*/

static struct file_operations dev_fops = {

.owner = THIS_MODULE,

.ioctl = sbc2440_leds_ioctl,

};

然后将实现的操作赋值给file_operations的成员.ioctl

B:混杂字符设备。

  所有的驱动程序都应该对应一个具体的设备,这个LED驱动当然设备应该是LED。但是linux将它分成了一类叫做混杂设备。这类设备共享一个主设备号,但次设备号不同所有混杂设备形成一个链表,要访问一个设备时根据次设备号来查找相应的miscdevice。linux中用struct miscdevice来描述一个混杂设备:

struct miscdevice{

int minor;//次设备号

const char *name;//设备名

const struct file_operations *fops;//文件操作集

struct list_head list;

struct device *parent;

struct device *this_device;

}

  int misc_register(struct miscdevice)//注册一个混杂设备

C:关于LED的一些操作

  在这个驱动程序当中对led的操作就用到了两个函数。

   int i;
 /*设置GPIO控制寄存器,GPIO设置为输出模式,默认下灯全灭*/
 for (i = 0; i < 4; i++) {
  s3c2410_gpio_cfgpin(led_table[i], led_cfg_table[i]);
  s3c2410_gpio_setpin(led_table[i], 1);
 }

led_table和led_cfg_table定义如下:

static unsigned long led_table [] = {
 S3C2410_GPB5,
 S3C2410_GPB6,
 S3C2410_GPB7,
 S3C2410_GPB8,
};

static unsigned int led_cfg_table [] = {
 S3C2410_GPB5_OUTP,
 S3C2410_GPB6_OUTP,
 S3C2410_GPB7_OUTP,
 S3C2410_GPB8_OUTP,
};

 s3c2410_gpio_cfgpin(led_table[i], led_cfg_table[i]);
  s3c2410_gpio_setpin(led_table[i], 1);
这两个函数的意思也很明显cfg就是将相应的管脚设置为输出功能,set就是设置相应管脚的值是0还是1、这样就能控制灯的亮还是灭。

(2)代码注释

memdev.h中的代码

#ifndef _MEMDEV_H_
#define _MEMDEV_H_

#include <linux/ioctl.h>

/* 定义幻数 */
#define MEMDEV_IOC_MAGIC  'k'

/* 定义命令 */
#define MEMDEV_IOCON   _IO(MEMDEV_IOC_MAGIC, 1)
#define MEMDEV_IOCOFF _IO(MEMDEV_IOC_MAGIC, 2)

#define MEMDEV_IOC_MAXNR 2

#endif /* _MEMDEV_H_ */

在这个memdev.h中就定义了ioctl的两个命令。

mini2440_leds_misc.c

#include <linux/miscdevice.h>
#include <linux/delay.h>
#include <asm/irq.h>
#include <mach/regs-gpio.h>
#include <mach/hardware.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/mm.h>
#include <linux/fs.h>
#include <linux/types.h>
#include <linux/delay.h>
#include <linux/moduleparam.h>
#include <linux/slab.h>
#include <linux/errno.h>
#include <linux/ioctl.h>
#include <linux/cdev.h>
#include <linux/string.h>
#include <linux/list.h>
#include <linux/pci.h>
#include <asm/uaccess.h>
#include <asm/atomic.h>
#include <asm/unistd.h>
#include <asm/io.h>
#include <asm/system.h>
#include <asm/uaccess.h>

前面这一大堆的头文件真是有点令人头疼。

#include "memdev.h"

#define DEVICE_NAME "leds"

static unsigned long led_table [] = {
 S3C2410_GPB5,
 S3C2410_GPB6,
 S3C2410_GPB7,
 S3C2410_GPB8,
};

上面查看原理图这是四个灯所接的芯片管脚。

下面的功能控制都整成了宏,看起来很方便。

static unsigned int led_cfg_table [] = {
 S3C2410_GPB5_OUTP,
 S3C2410_GPB6_OUTP,
 S3C2410_GPB7_OUTP,
 S3C2410_GPB8_OUTP,
};

下面是我前面分析过的控制函数,还是比较简单。当应用层的ioctl(fd, cmd, arg)被调用时,系统将处理它能识别的命令;

static int sbc2440_leds_ioctl(
 struct inode *inode,
 struct file *file,
 unsigned int cmd,
 unsigned long arg)
{
 int i = 0;
     /* 检测命令的有效性 */
  if (_IOC_TYPE(cmd) != MEMDEV_IOC_MAGIC)
      return -EINVAL;
  if (_IOC_NR(cmd) > MEMDEV_IOC_MAXNR)
      return -EINVAL;
     
  /* 根据命令,执行相应的操作 */
 switch(cmd) {
 case MEMDEV_IOCOFF:
  /*灯全灭*/
  cmd = 1;
  for(i=0; i<4; i++)
   s3c2410_gpio_setpin(led_table[i], cmd);
  return 0;
  
 case MEMDEV_IOCON:
  /*灯全亮*/
  for(i=0; i<4; i++)
   s3c2410_gpio_setpin(led_table[i], !cmd);
  return 0;
 default:
  return -EINVAL;
 }
}
/*文件操作结构体*/
static struct file_operations dev_fops = {
 .owner = THIS_MODULE,
 .ioctl = sbc2440_leds_ioctl,//将上面的ioctl操作传给file_operations
};

static struct miscdevice misc = {
 .minor = MISC_DYNAMIC_MINOR,
 .name = DEVICE_NAME,
 .fops = &dev_fops,//将file_operations传给混杂设备的成员,这样就将混杂设备与具体的led设备关联起来了。
};

static int __init dev_init(void)
{
 int ret;

 int i;
 /*设置GPIO控制寄存器,GPIO设置为输出模式,默认下灯全灭*/
 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 dev_exit(void)
{
 /*注销混杂型字符设备驱动*/
 misc_deregister(&misc);
}

module_init(dev_init);
module_exit(dev_exit);

MODULE_AUTHOR("David Xie");
MODULE_LICENSE("GPL");

 三:深入分析

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

对led的操作就是通过这两个函数完成了,那肯定得去看看这两个函数是具体怎么实现的。

记得以前做单片机的时候,或是做裸板程序的时候,先是看原理图,看led灯和哪个管脚相连。然后看那个管脚相关的寄存器,有数据寄存器和控制寄存器。将那个管脚通过设置控制寄存器配置成为输出端口。然后去往数据寄存器里面写0或1就开始控制灯的亮灭了。我相信这里也是这样做的。但是linux为了方便,统一,它做了很多的工作不只是为你这一个led驱动程序服务,对于所有的io口的操作它都统一起来了现在就深入的去看一下它到底是怎么实现的。

本想好好的在源代码中去跟踪一下这两个函数,但我上网查了一天有一些资料但看起来真是费劲,然后自己去跟踪了一下太吃力了,就一句话就得绕几十个圈。下面这个链接讲的还行

http://blog.tianya.cn/blogger/post_show.asp?idWriter=0&Key=0&BlogID=2573752&PostID=21377321

还是等再多做几个程序在来分析吧。现在知道的就是对于i0口来说就要对其做两个工作:一个是先选择它的功能,另外一个就是给它赋值。

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

不管是怎样的方法,总之,你明白了什么没有。。。

如果没有,就好好揣摩吧。今天不多说什么。