Chinaunix首页 | 论坛 | 博客
  • 博客访问: 439510
  • 博文数量: 123
  • 博客积分: 2686
  • 博客等级: 少校
  • 技术积分: 1349
  • 用 户 组: 普通用户
  • 注册时间: 2009-12-23 22:11
文章分类
文章存档

2012年(3)

2011年(10)

2010年(100)

2009年(10)

我的朋友

分类: LINUX

2010-03-30 10:30:39

前言:还有一个月就到杭州华三了,现在特无聊,想想到了华三基本就告别硬件了,还真舍不得。所以,在这个月就好好玩玩驱动吧。这个教程自己在动手实验的过程中写的,所以所有程序都在我的开发板正常运行。由于水平有限,难免要参考很多资料,有时候疏忽忘记标明出处,还望大家指出。最后,非常希望有人指出我的错误!
 
实验环境:英蓓特EduKit系列嵌入式教学系统平台
内核版本:linux-2.6.32.2
 
STEP 1:分析电路图。要想驱动LED,就得明白LED是由哪些信号控制,一般而言,不同的开发板,控制LED的信号是不同的。我的开发板是经过CPLD扩展过的。具体可以等价于:在地址0x21180000是选通控制LED的CPLD芯片。往这个地址上面对应的锁存写入数据,就可以控制LED了。这个地址对应的数据的低4位控制着4个LED灯。
 
STEP 2:编写驱动程序dri.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/arch/regs-gpio.h>
#include <asm/hardware.h>
#include <asm-arm/arch-s3c2410/io.h>/*必须添加这个头文件否则inb用不了*/

#include <linux/config.h>
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/init.h>

#include <linux/errno.h>    /* error codes */
#include <linux/types.h>    /* size_t */
#include <linux/proc_fs.h>
#include <linux/fcntl.h>    /* O_ACCMODE */
#include <linux/seq_file.h>
#include <linux/cdev.h>

#include <asm/system.h>        /* cli(), *_flags */
#include <asm/uaccess.h>    /* copy_*_user */

MODULE_LICENSE("BSD/GPL");
#define DEVICE_NAME "led" /*设备名称*/
#define LED_MAJOR 231     /*主设备号*/
#define LED_BASE (0xE1180000) /*控制LED的寄存器的基地址对应的内核虚拟地址*/
static int led_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg)
{
  unsigned char status;
 
 switch(cmd) {
 case 0:
 case 1:
    if (arg > 4) {
   return -EINVAL;
  }
  status = inb(LED_BASE); /*读取LED_BASE寄存器的值*/
  if(0 == cmd){
      status &= ~(0x1 << arg);
    }else if(1 == cmd){
      status |= (0x1 << arg);
  }
  outb(status, LED_BASE); /*写入LED_BASE寄存器*/
   
  return 0;
 default:
  return -EINVAL;
 }
}
/*这是ISO标准的结构体初始化方法*/ 

/*定义了一个led_fops的文件操作集结构体*/
static struct file_operations led_fops = {
 .owner = THIS_MODULE,
 .ioctl = led_ioctl,
};
/*模块被加载后首先执行led_init函数*/ 
static int __init led_init(void)
{ 
  int result;
  dev_t dev=0;
   unsigned char status;
  if(LED_MAJOR){
     dev=MKDEV(LED_MAJOR,0);/*如果定义了主设备号,则用宏MKDEV产生一个主设备号为LED_MAJOR,次设备号为0的设备号*/
     result=register_chrdev_region(dev,1,"led");/*这个函数用于向系统请求分配一个或者多个设备编号,dev是要分配的设备编号范围的起始值,1表示所请求的连续设备编号的个数,"led"是和该设备编号范围关联的设备名称/proc/devices和sysfs中*/
  }
  else{
      result=alloc_chrdev_region(&dev,0,1,"led");/*设备编号的动态分配。dev用于输出的参数,0是第一个请求的次设备号,其他一样*/
      //LED_MAJOR=MAJOR(dev);
  }
  if(result<0){
     printk(KERN_WARNING "scull:can't get major %d\n",LED_MAJOR);
     return result;
  }    
  
  struct cdev led_dev;/*定义一个字符设备类型的设备led_dev*/
  
  cdev_init(&led_dev,&led_fops);/*初始化字符设备led_dev*、
  //led_dev.owner=ThIS_MODULE;

  led_dev.ops=&led_fops;/*用文件操作集结构体类型led_fops初始化字符设备led_dev的操作集*/
  result=cdev_add(&led_dev,LED_MAJOR,1);/*向内核注册该字符设备led_dev,即,把这个结构体变量添加到内核相应的数据结构中去*/
  if(result)
     printk(KERN_NOTICE "Error %d adding led",result);
 
  status = inb(LED_BASE);
  outb(status & 0xf0,LED_BASE); 
  printk(DEVICE_NAME " initialized\n");
 return 0;
}
static void __exit led_exit(void) /*内核卸载模块时执行这个函数*/
{
  unsigned char status; 
     status = inb(LED_BASE);
     outb(status | 0x0f,LED_BASE); 
     printk(DEVICE_NAME " remove\n"); 
     cdev_del(DEVICE_NAME); /*从系统中移除一个字符设备*/
     unregister_chrdev(LED_MAJOR, DEVICE_NAME); /*注销系统分配的设备号*/
}
 
module_init(led_init);
module_exit(led_exit);


STEP 3:Makefile的编写
 

obj-m := dri.o #把dri.o以模块的形式编译

KERNELBUILD := /usr/src/kernels/linux-2.6.14#指定dri.c编译的目录环境,在2.6中,模块的编译要放到整个内核代码的环境中去编译,在linux-2.6.14中要配置好.config,可以复制arch/arm/configs/s3c2410_defaultconfig到内核根目录下,并更名为.config. 然后再执行make menuconfig进行配置*/

default:
    make -C $(KERNELBUILD) M=$(shell pwd) modules #-C 的作用是改变目录到 $(KERNELBUILD)指定的位置;M=的作用是让该makefile在构造#modules之前返回到模块源代码目录,即dri.c所在的目录;modules为make的目标,记得编译内核时有一步是 #make modules。总结,make modules发生的地方在${KERNELBUILD},在make moddules之 #前make编译的地方在${shell pwd}
clean:
    rm -rf *.o .*.cmd *.ko *.mod.c .tmp_versions


STEP 4: make。make后可以看见一个dri.ko,这就是我们需要加载到开发板的模块了。
 
STEP 5:写程序测试模块,看模块能不能正常工作.testdri.c如下:
 

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/select.h>
#include <sys/time.h>
 
static int led_fd;
 
int main(void)
{
  // open device

  led_fd = open("/dev/led", 0); /*打开设备文件*/
  if (led_fd < 0) {
  perror("open device led");
  exit(1);
 }
  
  printf("Please look at the leds\n");
 
  // led all off

  ioctl(led_fd, 1, 0); /*对led设备进行操作*/
  ioctl(led_fd, 1, 1);
  ioctl(led_fd, 1, 2);
  ioctl(led_fd, 1, 3);
 
 for(;;)
 {
    // led on one by one

  ioctl(led_fd, 0, 0);
  usleep(500000);
  ioctl(led_fd, 0, 1);
  usleep(500000);
  ioctl(led_fd, 0, 2);
  usleep(500000);
  ioctl(led_fd, 0, 3);
  usleep(500000);
 
    // led off one by one

  usleep(500000);
  ioctl(led_fd, 1, 1);
  usleep(500000);
  ioctl(led_fd, 1, 2);
  usleep(500000);
  ioctl(led_fd, 1, 3);
  usleep(500000);
 
  // all led on

  ioctl(led_fd, 0, 0);
  ioctl(led_fd, 0, 1);
  ioctl(led_fd, 0, 2);
  ioctl(led_fd, 0, 3);
  usleep(500000);
 
  // all led off

  ioctl(led_fd, 1, 0);
  ioctl(led_fd, 1, 1);
  ioctl(led_fd, 1, 2);
  ioctl(led_fd, 1, 3);
  usleep(500000);
 }
 close(led_fd);
 return 0;
}

 

后记:EduKit的实例代码是按照2.4写的,本文代码均是按照linu device driver 3rd中的方法重新改写的,并能在开发板上正确运行。

参考资料:

《linux device driver 》

《英蓓特EduKit文档》

阅读(895) | 评论(0) | 转发(1) |
给主人留下些什么吧!~~