【Linux驱动】TQ2440 LED驱动程序
★整体介绍
LED驱动程序主要实现了TQ2440开发板上的4个LED灯的硬件驱动,实现了对引脚GPIOB5、GPIOB6、GPIOB7、GPIOB8的高低电平设置(common-smdk.c中已经实现了对引脚的配置),利用測试程序调用该驱动程序,通过命令控制LED灯的亮灭。
★具体介绍
1、驱动程序代码:My_led.c
#include <linux/module.h> #include <linux/kernel.h> #include <linux/fs.h> #include <linux/init.h> #include <linux/delay.h> #include <asm/irq.h> #include <mach/regs-gpio.h> #include <mach/hardware.h> #include <linux/device.h> #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就是port的编号。bank是分组的基号码,offset是组内的偏移量。
这样算出来的S3c2410_GPB5 的值为37,他的作用是代表S3c2440中B组port的第五个引脚。
4、My_led_ioctl()函数
此函数主要负责响应应用程序的相关命令。然后依据命令运行相关的代码。cmd命令包含宏定义的LED_ON、LED_OFF。
s3c2410_gpio_setpin()函数
自觉得这是本驱动程序最重要的一个函数,它的作用是:将指定的引脚设置为低电平或者高电平。
本驱动程序中仅仅是用了内核提供的这一个接口来实现对引脚的控制。如今来分析一下这个函数。
◇函数的作用
这个函数是设置s3c2440中引脚的高低电平的。
它的第一个參数是linux系统中关于引脚定义的一些宏。比如s3c2410_GPB5、s3c2410_GPB6等代表芯片的port引脚;还有一个參数就是一个值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 <stdio.h> #include <stdlib.h> #include <unistd.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> 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当时出的错误就是编译器的路径没有搞清楚。