嵌入式linux开发の字符设备驱动程序的开发流程

本篇文章以编写电位器驱动程序为例,详细介绍并总结下设备驱动的开发流程

硬件:am3354(TI)

系统内核:linux3.2

我们在有了板子和选定好使用的内核后,在开始编写驱动之前要查看原理图,即外设使用的那几个引脚。我们还需要在板子文件中(arch/arm/根据厂商芯片名.c)把引脚设置好。因为可能你使用的这几个引脚被用于别的功能了。

在确定好使用那几个引脚后,我们要确定是使用何种框架编写(是按照杂项设备(特殊的字符设备,可以节省设备号),平台设备,字符设备),一般我是习惯用字符设备的那一套流程写

在初始化函数里,我们需要做的事情:确定并申请设备号,把设备和操作函数关联起来,创建设备节点。

      
static int major =250;//定义主设备号 
static int minior=0;
int number_of_device= 1;
struct cdev cdev;
dev_t dev =0;
struct class *myclass;
// 模块加载函数
static int light_init(void)
{
        int ret;
	int error;
	
	led_init(); //设备引脚方向

	dev=MKDEV(major,minior);
	ret = register_chrdev_region(dev,number_of_device,"res");//申请设备号
	if(ret<0) 
        { 
         printk("unable to register  driver!\n"); 
         return ret; 
        } 
	//生成设备节点
	cdev_init(&cdev,&light_fops);//初始化cdev设备结构体
	cdev.owner=THIS_MODULE;
	cdev.ops=&light_fops;
	error = cdev_add(&cdev,dev,1);//把设备结构体加入到系统中
	if(error) 
        { 
        printk("unable to add driver!\n"); 
    
        }
	 myclass=class_create(THIS_MODULE,"res");//创建类节点
	 
	 if(IS_ERR(myclass))
	 {
         printk("Err: failed in creating class.\n");
         return 0;
      }
    printk (KERN_INFO "char device registered\n");
	
    device_create(myclass,NULL, dev, NULL,"res");//创建设备节点
               
 
    return 0;
	
}
 
 

里面用到的几个函数详细解释下

(1)

dev=MKDEV(major,minior);

cdev结构体成员dev_t成员定义了设备号,为32位,使用上面的宏可以通过主设备号和次设备号生成dev_t

(2)

ret = register_chrdev_region(dev,number_of_device,"res");

该函数的作用是向系统申请设备号,参数:设备号,几个设备,设备名字。该函数用于已知起始设备号的情况。成功会返回一个非负整数

(3)

cdev_init(&cdev,&light_fops);//初始化cdev设备结构体

error = cdev_add(&cdev,dev,1);//把设备结构体加入到系统中
这几个函数是要连在一块用,在内核中所有的字符设备都会记录在一个叫kobj_map结构的cdev_map变量中,这个变量包含一个散列表来快速存取所有对象。kobj_map()函数就是把字符设备编号和cdev变量一起保存到散列表里,当后续需要打开一个字符设备文件时,内核通过调用kobj_lookup()函数,根据设备编号就可以找到cdev变量

(4)

       myclass=class_create(THIS_MODULE,"res");//创建类节点
	 
	 if(IS_ERR(myclass))
	 {
         printk("Err: failed in creating class.\n");
         return 0;
      }
    printk (KERN_INFO "char device registered\n");
	
    device_create(myclass,NULL, dev, NULL,"res");//创建设备节点

内核中定义了struct class 结构体,同时内核提供了class_create()函数,可以用来创建一个类,这个类存放在sysfs下面,一旦创建号这个类,在调用device_create()函数来创建设备节点。

在初始化完成后,我们需要完善结构体中的读,写,控制操作函数,方便上层应用的调用

struct file_operations light_fops = 
{
    .owner = THIS_MODULE,
    //.unlocked_ioctl = light_ioctl,
    .open = light_open,
    .write =light_write,
    .release = light_release,
};

通过对代码的剖析,我们知道编写字符驱动的框架其实很简单,掌握了本质,事情就变得简单。

附:代码

/**********************

* Copyright (C) Enbo.Tech

* Date:         2018-05-15

* Author:       Liwx

* Vision:       V1.0 

* Introduction: this is a simple digital res driver

***********************/
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/platform_device.h>
#include <linux/leds.h>
#include <linux/of_platform.h>
#include <linux/of_gpio.h>
#include <linux/slab.h>
#include <linux/workqueue.h>
#include <linux/module.h>
#include <linux/gpio.h>
#include <asm/gpio.h>
#include <linux/types.h>
#include <linux/fs.h>
#include <linux/delay.h>
#include <linux/cdev.h>
#include<linux/device.h>
#include <asm/uaccess.h>

/*******************************************/

#define GPIO_TO_PIN(bank, gpio) (32 * (bank) + (gpio))

static int major =250;//定义主设备号 
static int minior=0;
int number_of_device= 1;
struct cdev cdev;
dev_t dev =0;
struct class *myclass;
char write_data[10];

/*******************************************/
void led_up(void)
{ 
     
	 printk (KERN_INFO" chip select\n");
	 gpio_set_value(GPIO_TO_PIN(1,15), 0); // select chip
	 
	 gpio_set_value(GPIO_TO_PIN(1,9), 1); // set up mode
	 
	 
}
void create_pulse(unsigned char n)
{
	 int num=0;
	  printk (KERN_INFO" chip pulse is %d\n",n);
	 for(num=0;num < n;num++)
     {
     gpio_set_value(GPIO_TO_PIN(1,8), 1);
	 mdelay(10);
	 gpio_set_value(GPIO_TO_PIN(1,8), 0);
	 mdelay(10);
	 }
}

void led_down(void)
{ 
     gpio_set_value(GPIO_TO_PIN(1,15), 0); //select chip
	 
	 gpio_set_value(GPIO_TO_PIN(1,9), 0); // set down mode
	 printk (KERN_INFO" chip select\n");
}

void led_init(void)
{ 
        int result;
		 printk(KERN_INFO"gpio init\n");
		 
         /* Allocating GPIOs and setting direction */
        result = gpio_request(GPIO_TO_PIN(1,8), "TTS_INC");
        if (result != 0)
                printk(KERN_INFO"gpio_request(1_8) failed!\n");
		 result = gpio_request(GPIO_TO_PIN(1,9), "TTS_U/D");
        if (result != 0)
                printk(KERN_INFO"gpio_request(1_9) failed!\n");
		 result = gpio_request(GPIO_TO_PIN(1,15), "TTS_CS");
        if (result != 0)
                printk(KERN_INFO"gpio_request(1_15) failed!\n");
			
			
		  result = gpio_direction_output(GPIO_TO_PIN(1,8), 1);
        if (result != 0)
                printk(KERN_INFO"gpio_direction(1_8) failed!\n"); 
			   result = gpio_direction_output(GPIO_TO_PIN(1,9), 1);
        if (result != 0)
                printk(KERN_INFO"gpio_direction(1_9) failed!\n"); 
			   result = gpio_direction_output(GPIO_TO_PIN(1,15), 1);
        if (result != 0)
                printk(KERN_INFO"gpio_direction(1_15) failed!\n");	
        
}

MODULE_AUTHOR("Liwx");
MODULE_LICENSE("Dual BSD/GPL");

// 打开和关闭函数
int light_open(struct inode *inode,struct file *filp)
{
  
	printk (KERN_INFO "open device success!\n");
    return 0;
}

int light_release(struct inode *inode,struct file *filp)
{
    
	return 0; 
}

/*文件的读操作,上层对此设备调用read时会执行*/  
static ssize_t light_write(struct file *filp, char *buf, size_t count, loff_t *ppos)  
{    
        ssize_t ret =0;
        if (copy_from_user (write_data, buf, count)) {  
        ret = -EFAULT;  
    }
	    printk (KERN_INFO"Received \n"); 

		if(write_data[0]==1)
		{
			printk (KERN_INFO" res up\n"); 
			led_up(); 
		    create_pulse(write_data[1]);
			
		}
	    else
		{
		    printk (KERN_INFO" res down\n"); 
			led_down(); 
		    create_pulse(write_data[1]);	
		}
		return count;
		
			
}  
struct file_operations light_fops = 
{
    .owner = THIS_MODULE,
    //.unlocked_ioctl = light_ioctl,
    .open = light_open,
	.write =light_write,
    .release = light_release,
};


// 模块加载函数
static int light_init(void)
{
    int ret;
	int error;
	
	led_init();

	dev=MKDEV(major,minior);
	ret = register_chrdev_region(dev,number_of_device,"res");
	if(ret<0) 
    { 
      printk("unable to register  driver!\n"); 
      return ret; 
     } 
	//生成设备节点
	cdev_init(&cdev,&light_fops);
	cdev.owner=THIS_MODULE;
	cdev.ops=&light_fops;
	error = cdev_add(&cdev,dev,1);
	if(error) 
    { 
      printk("unable to add driver!\n"); 
    
     }
	 myclass=class_create(THIS_MODULE,"res");
	 
	 if(IS_ERR(myclass))
	 {
         printk("Err: failed in creating class.\n");
         return 0;
      }
    printk (KERN_INFO "char device registered\n");
	
    device_create(myclass,NULL, dev, NULL,"res");
               
 
    return 0;
	
}

// 模块卸载函数
static void  light_exit(void)
{
     dev_t devno = MKDEV(major,minior);
     cdev_del(&cdev);
     unregister_chrdev_region (devno, number_of_device);
     device_destroy(myclass, devno);
     class_destroy(myclass);
	
     printk("Goodbye,cruel world!\n"); 
}

module_init(light_init);
module_exit(light_exit);

 
 

应用程序

#include<stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <linux/input.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <string.h>
#include <sys/ioctl.h>  
#include <fcntl.h>
#include <linux/ioctl.h>
 
char cmd_data[2];
int main(int argc, char **argv)
{
    int i, n, fd;
    int oc;
	extern int optind;
	const char *count_s;
	const char *dir_s;
	int count=0;
	int dir;
	
    fd = open("/dev/res", O_RDWR);
	
    if (fd < 0)
    {
       printf("can't open /dev/res!\n");
       exit(1);
    }
    
  
	 count=atoi(argv[1]);
	 if(count <=100)
	 printf("pulse's number is  %d\n",count);
     else
	 {
		 printf("please input number is less than 100\n");
		 return 0;
	 }
	 dir=atoi(argv[2]);
	 if(dir == 1)
		 printf("u/d is high level\n");
	 else
	     printf("u/d is low level\n");
	 
	 cmd_data[0]=dir;
	 
	 cmd_data[1]=count;
	 
    int byte=write(fd,cmd_data,2);
	 
	 if(byte < 0)
		 printf("write error\n");
		 
     return 0;
}

makefile

#!/bin/bash
#通知编译器我们要编译模块的哪些源码
#这里是编译itop4412_hello.c这个文件编译成中间文件itop4412_hello.o
obj-m += res_driver_xd.o 

#源码目录变量,这里用户需要根据实际情况选择路径
#作者是将Linux的源码拷贝到目录/home/topeet/android4.0下并解压的
KDIR := /home/liwx/src_xd/kernel-3.2

#当前目录变量
PWD ?= $(shell pwd)

#make命名默认寻找第一个目标
#make -C就是指调用执行的路径
#$(KDIR)Linux源码目录,作者这里指的是/home/topeet/android4.0/iTop4412_Kernel_3.0
#$(PWD)当前目录变量
#modules要执行的操作
all:
	make -C $(KDIR) M=$(PWD) modules
		
#make clean执行的操作是删除后缀为o的文件
clean:
	rm -rf *.o

猜你喜欢

转载自blog.csdn.net/Colin_xuan/article/details/80610510