【Linux驱动】TQ2440 LED驱动程序

★总体介绍

LED驱动程序主要实现了TQ2440开发板上的4个LED灯的硬件驱动,实现了对引脚GPIOB5、GPIOB6、GPIOB7、GPIOB8的高低电平设置(common-smdk.c中已经实现了对引脚的配置),利用测试程序调用该驱动程序,通过命令控制LED灯的亮灭。

★详细介绍

1、驱动程序代码:My_led.c

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#define DEVICE_NAME  "My_led" /**加载模块后执行cat/proc/devices中看到的设备名称**/
#define Led_MAJOR         103         /**主设备号**/
#define LED_ON        1
#define LED_OFF       0 

/**Led的控制引脚**/
static unsigned long led_table[ ] =
{
    S3C2410_GPB5,
    S3C2410_GPB6,	
	S3C2410_GPB7,
	S3C2410_GPB8,
};

static int My_led_open(struct inode *inode,struct file *file)
{
    printk("My_led  open\n");
    return 0;
}

static int My_led_ioctl(struct inode * inode, struct file * file,unsigned int cmd,unsigned long arg)
{
	if(arg > 4)
	{
		return -1;             
       }
	switch(cmd)
	{
     		case LED_ON:
				s3c2410_gpio_setpin(led_table[arg], 0);//设置指定引脚为输出电平为0
  				return 0;
	        case LED_OFF:
	            s3c2410_gpio_setpin(led_table[arg], 1);//设置指定引脚为输出电平为1
	            return 0;
   		default:
				return  -1;
	}
}

static struct file_operations My_led_fops =
{
	.owner = THIS_MODULE,
	.open = My_led_open,
	.ioctl = My_led_ioctl,
};

static struct class *led_class;

static int __init My_led_init(void)
{
	int ret;
	printk("My_led start\n");

	 /**注册字符设备驱动程序**/
	 /**参数为主设备号、设备名字、file_operations结构**/
	 /**这样主设备号就与file_operations联系起来**/
	 ret = register_chrdev(Led_MAJOR, DEVICE_NAME, &My_led_fops);
	 if(ret < 0)
	 {
		printk("can't register major number\n");
		return ret;
	 }

	//注册一个类,使mdev可以在"/dev/目录下建立设备节点"
	led_class = class_create(THIS_MODULE, DEVICE_NAME);
	if(IS_ERR(led_class))
	{
		printk("failed in My_led class.\n");
		return -1;
	}
	device_create(led_class, NULL, MKDEV(Led_MAJOR,0), NULL, DEVICE_NAME);
	printk(DEVICE_NAME "initialized\n");
	return 0;

}

static void __exit My_led_exit(void)
{
    unregister_chrdev(Led_MAJOR, DEVICE_NAME);
    device_destroy(led_class, MKDEV(Led_MAJOR,0));//注销设备节点
   class_destroy(led_class);//注销类
}

module_init(My_led_init);
module_exit(My_led_exit);

MODULE_LICENSE("GPL");

2、宏定义

#define DEVICE_NAME  "My_led" //加载模块后执行cat/proc/devices中看到的设备名称
#define Led_MAJOR         103         //主设备号
#define LED_ON        1
#define LED_OFF       0 My_led_ioctl函数中要输入的参数命令,LED_ON会执行打开灯的命令、LED_OFF执行关闭灯的命令

3、Led灯的引脚控制

定义一个led_table[ ] 数组,在后面调用时要方便些。

详细说明一下S3C2410_GPB5是什么意思:




如上图所示,是关于S3C2410_GPB5相关的所有代码。通过上面的代码可以得出S3C2410_GPB5为37。实际上S3C2410_GPB5就是端口的编号,bank是分组的基号码,offset是组内的偏移量。这样算出来的S3c2410_GPB5 的值为37,他的作用是代表S3c2440中B组端口的第五个引脚。

4、My_led_ioctl()函数

此函数主要负责响应应用程序的相关命令,然后根据命令执行相关的代码。cmd命令包括宏定义的LED_ON、LED_OFF。

s3c2410_gpio_setpin()函数

自认为这是本驱动程序最重要的一个函数,它的作用是:将指定的引脚设置为低电平或者高电平。本驱动程序中只是用了内核提供的这一个接口来实现对引脚的控制。现在来分析一下这个函数。

◇函数的作用

    这个函数是设置s3c2440中引脚的高低电平的。它的第一个参数是linux系统中关于引脚定义的一些宏。例如s3c2410_GPB5、s3c2410_GPB6等代表芯片的端口引脚;另一个参数就是一个值to,这个值代表0或者1。

◇此函数是如何设置高低电平的

    首先贴出此函数的定义在内核版本Linux2.6.30.4/arch/arm/plat-s3c24xx/gpio.c中

    

void s3c2410_gpio_setpin(unsigned int pin, unsigned int to)
{
	void __iomem *base = S3C24XX_GPIO_BASE(pin);
	unsigned long offs = S3C2410_GPIO_OFFSET(pin);
	unsigned long flags;
	unsigned long dat;

	local_irq_save(flags);

	dat = __raw_readl(base + 0x04);
	dat &= ~(1 << offs);
	dat |= to << offs;
	__raw_writel(dat, base + 0x04);

	local_irq_restore(flags);
}
先来分析第一个函数S3C24XX_GPIO_BASE(pin)


5、注册一个类

如果不注册这个类,那么当在开发板上挂载驱动之后,还要做的一件事是手动“生成设备节点”,就是利用mknod命令。但是驱动程序都是随操作系统的运行而开始工作的,因此生成设备节点要让它自动生成,而并非手动。所以才会注册这样一个类,在挂载驱动之后,自动将设备节点生成。注册类的核心代码:

led_class = class_create(THIS_MODULE, DEVICE_NAME);
	if(IS_ERR(led_class))
	{
		printk("failed in My_led class.\n");
		return -1;
	}
	device_create(led_class, NULL, MKDEV(Led_MAJOR,0), NULL, DEVICE_NAME);

6、测试程序

#include    
#include    
#include       
#include      
#include    
#include      
  

int main()  
{  
    int fd,i,cmd;      
    fd=open("/dev/My_led",0); 
    if (fd<0)  
    {  
        printf("open led_driver error");  
        exit(1);  
    }
    while(1)
    {
	    scanf("%d",&cmd);
	    switch(cmd)
	    {
		case 0:
		    printf("All off\n");
		    for(i = 0;i < 4;i ++)
		    ioctl(fd,0,i); 
		    break;
		case 1:
		    printf("light first led\n");
		    ioctl(fd,1,0); 
		    break;
		case 2:
		    printf("light second led\n");
                    ioctl(fd,0,0);
		    ioctl(fd,1,1); 
		    break;
		case 3:
		    printf("light third led\n");
                    ioctl(fd,0,1);
		    ioctl(fd,1,2); 
		    break;
		case 4:
		    printf("light fourth led\n");
                    ioctl(fd,0,2);
		    ioctl(fd,1,3); 
		    break;
		case 5:
		    printf("All light \n");
		    for(i = 0;i < 4;i ++)
		    ioctl(fd,1,i); 
		    break;
		default: 
                    i = 10;
		    break;
	    
	    }
            if (i == 10)
                break;
    }
    
    return 0;  
 }  

★遇到的错误

1、找不到文件

这个错误的主要原因很可能是内核的版本不同,不同内核的头文件存放的路径不相同。因此,当编译驱动程序报错“找不到文件”时,检查头文件的路径是否正确

2、Makefile文件指定交叉编译器

下面是驱动程序的Makefile文件

#LED_makefile

KERNELDIR := /home/xg/linux_arm/linux-2.6.30.4/
PWD :=$(shell pwd)

all:
	make -C $(KERNELDIR) M=$(PWD) modules ARCH=arm CROSS_COMPILE=/opt/opt/EmbedSky/4.3.3/bin/arm-linux-
test:
	/opt/opt/EmbedSky/4.3.3/bin/arm-linux-gcc -o test test.c
clean:
	rm -rf *.o *ko

obj-m :=My_led.o
当时出的错误就是编译器的路径没有搞清楚。



你可能感兴趣的:(Linux驱动)