首页 > 代码库 > mini2440驱动奇谭——LED驱动与测试(动态加载)
mini2440驱动奇谭——LED驱动与测试(动态加载)
我的博客:http://blog.csdn.net/muyang_ren
实现功能:开发板动态加载led驱动模块并能通过测试程序
系统:Ubuntu 14.04
驱动交叉编译内核:linux-2.6.32.2 //建立交叉编译
开发板:mini2440 (128M nandflash) //关于怎么烧写linux到开发板请点击,Linux RootFs 选择rootfs_rtm_2440.img (光盘目录:image/linux/rtm )
开发所需工具:NFS网络文件 minicom vim
linux文件目录:/opt/FriendlyARM/mini2440/linux-2.6.32.2
自己驱动目录: /home/lianghuiyong/my2440drivers
注意:我的开发板内核被我裁剪了led驱动部分,包括之后要写的一些驱动,裁剪内核很简单,make menuconfig菜单后对应的驱动空格为空就行了
给大家推荐一个很好用的vim插件:ma6174,因为我用的是NFS网络挂载,使用了路由器上网,当大家不想用到路由器,用的是无线网的时候还想使用NFS网络挂载文件,其他没变,只是将网线修改下,我们上网的网线一般是按B接线排的,记得老师说过,只接局域网不上网的话只需要网线的一端是A排法,另一端是B排法。直接连开发板和电脑就行了。
(一)丶LED原理分析
盗图可耻啊,我还是盗图了,原谅我吧。。。
从图中可知:led一端是直接接着芯片上的引脚,另一端怎么接了个电阻?没电阻降压的话电流一接通,就led这么点阻值,芯片估计光荣了。要点亮led,电阻那端的电流是没法改变的,而且电阻那端是高电平,led没亮的情况下,是因为芯片那端也是提供高电平,要点亮led只需将芯片对应的芯片控制寄存器设置为输出,并将数据寄存器写入0 , 0为低电平,电流通过,led点亮!
(二)丶代码部分
驱动模块:
/************************************************************************* > File Name:LHY_led.c > Author:梁惠涌 > Mail: > Created Time: 2014年09月29日 星期一 20时34分50秒 ************************************************************************/ //在 linux-2.6.32.2/arch/arm/mach-s3c2410/include/mach 目录下 #include<mach/regs-gpio.h> // 和GPIO相关的宏定义 #include<mach/hardware.h> //S3C2410_gpio_cfgpin //S3C2410_gpio_setpin等gpio函数定义 //在 linux-2.6.32.2/include/linux 目录下 #include<linux/miscdevice.h> //注册 miscdevide 结构体成员变量 #include<linux/delay.h> //这个应该是时延函数了 #include<linux/kernel.h> //内核,不用说 #include<linux/module.h> //驱动加载卸载函数定义 #include<linux/init.h> //初始化头文件 #include<linux/mm.h> #include<linux/fs.h> //注册file_operations结构体变量 #include<linux/types.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<linux/gpio.h> //gpio口定义文件(虚拟地址) //在 linux-2.6.32.2/arch/arm/include/asm 目录下 #include<asm/irq.h> #include<asm/uaccess.h> #include<asm/atomic.h> #include<asm/unistd.h> #define DEVICE_NAME "leds" static unsigned long led_table[]={ S3C2410_GPB(5), //在gpio-nrs.h(是gpio.h包含的头文件)中定义了虚拟地址 S3C2410_GPB(6), S3C2410_GPB(7), S3C2410_GPB(8), }; static unsigned int led_cfg_table[]={ S3C2410_GPIO_OUTPUT, //定义在regs-gpio.h头文件 S3C2410_GPIO_OUTPUT, S3C2410_GPIO_OUTPUT, S3C2410_GPIO_OUTPUT, }; static int sbc2440_leds_ioctl( struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg ){ switch(cmd){ case 0: case 1: if(arg>4){ return -EINVAL; } s3c2410_gpio_setpin(led_table[arg],!cmd); return 0; default: return -EINVAL; } } //文件结构体 static struct file_operations dev_fops={ .owner= THIS_MODULE, //THIS_MODULE防止使用过程中被卸载 .ioctl = sbc2440_leds_ioctl, //.ioctl 指向 sbc2440_leds_ioctl 函数 }; //混杂设备结构体 static struct miscdevice misc ={ .minor = MISC_DYNAMIC_MINOR, //动态分配次设备号(驱动编号) .name = DEVICE_NAME, //驱动名为定义的 DEVICE_NAME .fops = &dev_fops, //文件操作指针指向 dev_fops的地址 }; //模块加载并初始化函数 static int __init dev_init(void){ int ret; int i; printk(KERN_ALERT "Hello,mini2440 led is installed!\n"); for(i=0;i<4;i++){ //配置led 引脚功能,led_table[i]为上面定义的led虚拟地址; //led_cfg_table[i]为上面定义的功能(输出) s3c2410_gpio_cfgpin(led_table[i],led_cfg_table[i]); //设置led状态,0为低电平,led点亮 s3c2410_gpio_setpin(led_table[i],0); } ret =misc_register(&misc); printk(DEVICE_NAME"\t Lianghuiyong-leds\n"); return ret; } static void __exit dev_exit(void){ printk(KERN_ALERT"Good-bye,mini2440 led is removed!\n"); misc_deregister(&misc); } module_init(dev_init); module_exit(dev_exit); MODULE_LICENSE("GPL"); //GPL协议 MODULE_AUTHOR("Lianghuiyong Inc.");
驱动makefile文件
PWD = $(shell pwd) KDIR =/opt/FriendlyARM/mini2440/linux-2.6.32.2/ obj-m:= LHY_led.o all: $(MAKE) -C $(KDIR) M=$(PWD) CONFIG_DEBUG_SECTION_MISMATCH=y clean: rm -rf *.o *~core.depend. *.cmd *.ko *.mod.c .tmp_versions rm -rf *.order Module.* insmod: insmod LHY_led.ko rmmod: rmmod LHY_led active: echo -e "$(MAKE) \n" $(MAKE) -C $(KDIR) M=$(PWD)
测试模块:
/************************************************************************* > File Name: ledceshi.c > Author:梁惠涌 > Mail: > Created Time: 2014年10月02日 星期日 01时10分03秒 ************************************************************************/ #include<stdio.h> #include<stdlib.h> #include<unistd.h> #include<sys/ioctl.h> int main(int argc, char **argv) { int on; int led_on; int fd; /*检查两个参数,如果不符合则退出*/ if(argc !=3 ||sscanf(argv[1],"%d",&led_on)!=1 || sscanf(argv[2],"%d",&on)!=1 || on <0 || on>1 || led_on<0 ||led_on>3){ fprintf(stderr,"Usage: leds led_on 0|1 \n"); exit(1); } fd = open("/dev/leds0", 0); if(fd<0){ fd = open("/dev/leds",0); } if(fd<0){ perror("open device leds"); exit(1); } /*通过ioctl调用输入的led_on和on控制led*/ ioctl(fd,on,led_on); close(fd); return 0; }
测试文件曾用过官方的makefile文件来make,但是开发板运行时出现syntax error: word unexpected (expecting ")")错误,直接使用gcc吧
命令:arm-linux-gcc -g ledceshi.c(c文件) -o ledceshi(产生的可执行文件名)
开发板上的操作截图(这个绝不是盗图了。。。。。。)
(三)丶函数调用笔记
(一)S3C2410_GPB(5)
头文件中的宏定义
#define S3C2410_GPB(_nr) (S3C2410_GPIO_B_START + (_nr)) S3C2410_GPIO_B_START = S3C2410_GPIO_NEXT(S3C2410_GPIO_A), #define S3C2410_GPIO_NEXT(__gpio) ((__gpio##_START) + (__gpio##_NR) + CONFIG_S3C_GPIO_SPACE + 0) #define S3C2410_GPIO_B_NR (32) S3C2410_GPIO_A_START = 0,
我们只用到 S3C2410_GPB(_nr)这个宏定义,而这个虚拟地址怎么算?宏定义(第一行)和等式(第二行)很容易理解,
S3C2410_GPIO_NEXT(S3C2410_GPIO_A)这个呢?
S3C2410_GPIO_NEXT(S3C2410_GPIO_A)=S3C2410_GPIO_A_START(0)+S3C2410_GPIO_A_NR(32)+CONFIG_S3C_GPIO_SPACE+0,
而CONFIG_S3C_GPIO_SPACE的值?根据
#if CONFIG_S3C_GPIO_SPACE != 0 #error CONFIG_S3C_GPIO_SPACE cannot be zero at the moment #endif
可知 CONFIG_S3C_GPIO_SPACE 只能为0才会讨论虚拟地址是多少,反推过去就知道 S3C2410_GPB(_nr)=32+ _nr 。
总结:S3C2410_GPn(_nr) ,n为 A、B、C、D等,S3C2410_GPn(_nr)=n×32+_nr (A时n为0,B时为1,C为2 .......)
(二) s3c2410_gpio_cfgpin(led_table[i],led_cfg_table[i]); 函数以S3C2410_GPB(5),S3C2410_GPIO_OUTPUT 实参分析
该函数的具体定义:
gpio-nrs.h 用到的定义 --- S3C2410_GPIO_BANKB
regs-gpio.h --- S3C2410_GPIO_OUTPUT , S3C2410_GPIO_BANKB, S3C24XX_GPIO_BASE(x), S3C2410_GPIO_OFFSET(pin)
--- S3C2410_GPIO_BASE(pin)
void s3c2410_gpio_cfgpin(unsigned int pin, unsigned int function) //根据上面的计算,可知pin=37 ,function 可根据宏定义 #define S3C2410_GPIO_OUTPUT (0xFFFFFFF1) { void __iomem *base = S3C24XX_GPIO_BASE(pin); //#define S3C24XX_GPIO_BASE(x) S3C2410_GPIO_BASE(x) /*S3C24XX_GPIO_BASE(pin)的计算: (以下定义是在 /linux-2.6.32.2/arch/arm/mach-s3c2410/include/mach/map.h /linux-2.6.32.2/arch/arm/plat-s3c24xx/include/plat/map.h /linux-2.6.32.2/arch/arm/plat-s3c/include/plat/Map-base.h下) #define S3C2410_GPIO_BASE(pin) ((((pin) & ~31) >> 1) + S3C24XX_VA_GPIO) #define S3C24XX_VA_GPIO ((S3C24XX_PA_GPIO - S3C24XX_PA_UART) + S3C24XX_VA_UART) #define S3C24XX_PA_GPIO S3C2410_PA_GPIO #define S3C24XX_PA_UART S3C2410_PA_UART #define S3C24XX_VA_UART S3C_VA_UART #define S3C2410_PA_GPIO (0x56000000) #define S3C2410_PA_UART (0x50000000) #define S3C_VA_UART S3C_ADDR(0x01000000) #define S3C_ADDR(x) (S3C_ADDR_BASE + (x)) #define S3C_ADDR_BASE (0xF4000000) S3C24XX_GPIO_BASE(pin) = S3C2410_GPIO_BASE(x) = ((((pin) & ~31) >> 1) + S3C24XX_VA_GPIO) = ((((pin) & ~31) >> 1) + (S3C24XX_PA_GPIO - S3C24XX_PA_UART) + S3C24XX_VA_UART) = ((((pin) & ~31) >> 1) + (0x56000000- 0x50000000) + S3C_VA_UART) = ((((pin) & ~31) >> 1) + (0x56000000- 0x50000000) + (S3C_ADDR_BASE + (0x01000000)) = ((((pin) & ~31) >> 1) + (0x56000000- 0x50000000) + ((0xF4000000) + (0x01000000)) = ((((pin) & ~31) >> 1) + (0xFB000000) = 0xFB000010 //((((pin) & ~31) >> 1)运算:((pin) & ~31) =100000,右移一位=10000,也就是十六进制0x10。 //base变量存储的是0xFB000010虚拟地址 */ unsigned long mask; unsigned long con; unsigned long flags; if (pin < S3C2410_GPIO_BANKB) { // #define S3C2410_GPIO_BANKB (32*1) ,GPB虚拟地址的开始, // 成立则pin是指向GPA,否则指向GPA之外地址 mask = 1 << S3C2410_GPIO_OFFSET(pin); // #define S3C2410_GPIO_OFFSET(pin) ((pin) & 31) , ((pin) & 31) 也就是将第六位(包括)以上的数清零 } else { mask = 3 << S3C2410_GPIO_OFFSET(pin)*2; //S3C2410_GPIO_OFFSET(37)=(( 37 ) & 31 ) *2= 00101*2=10 ,mask=3<<10 } //根据我的理解上面这个if语句,1 和 3分别代表了寄存器控制位的初始状态,S3C2410_GPIO_OFFSET(pin)*2 和 S3C2410_GPIO_OFFSET(pin) //只是求出控制寄存器的具体位,那么1 << S3C2410_GPIO_OFFSET(pin) 和3 << S3C2410_GPIO_OFFSET(pin)*2 就是将寄存器的某一控制位赋 //予初始状态,之所以要分1 和 3、 S3C2410_GPIO_OFFSET(pin);和S3C2410_GPIO_OFFSET(pin)*2; 主要是因为只有GBA的控制状态位只要一 //位表示就够了(两个功能),而其他的寄存器需要两位(四个功能)来表示。 switch (function) { case S3C2410_GPIO_LEAVE: //是否是最后一个基(虚拟)地址 mask = 0; function = 0; break; case S3C2410_GPIO_INPUT: case S3C2410_GPIO_OUTPUT: case S3C2410_GPIO_SFN2: case S3C2410_GPIO_SFN3: if (pin < S3C2410_GPIO_BANKB) { //这个if和我上面分析的一样 function - = 1; function &= 1; function <<= S3C2410_GPIO_OFFSET(pin); } else { //GPB的pin属于下面的情况 function &= 3; //取 function 后两位,其余位清零 , function=0xFFFFFFF1 ,相与后=01 function <<= S3C2410_GPIO_OFFSET(pin)*2; //通过上面的if分析可以知道function=01<<10 } } /* modify the specified register wwith IRQs off */ local_irq_save(flags); // 关中断,中断标志位存储于flags中,定义在<asm/system.h>文件中 con = __raw_readl(base + 0x00);//base是上面的指针变量,存储的是0xFB000010 基地址,没找到函数的定义,con应该是读取控制寄存器的值 con &= ~mask; //con对应位[10,11]置零,~mask = ~(3<<10) con |= function; //con对应位设置[10,11]为function后两位 01,function = 01 << 10 __raw_writel(con, base + 0x00); //将改变后的con写入寄存器 local_irq_restore(flags); //开寄存器中断 }s3c2410_gpio_setpin(led_table[i],0); 函数 和 s3c2410_gpio_cfgpin(led_table[i],led_cfg_table[i]); 大同小异,就不分析了。
盗用下网友的虚拟地址映射图(这样这张图才有意义吧,O(∩_∩)O哈哈~)
错误笔记(2.6.32.2内核):
1、/home/nfsshare/root_qtopia/Drive/LED/mini2440_leds.c:29: error: ‘S3C2410_GPB5‘ undeclared here (not in a function)
解决方法:这是因为内核版本问题,gpio-nrs.h定义和手册的例子上用的有所不同:S3C2410_GPB5改为 S3C2410_GPB(5)
2、/home/lianghuiyong/my2440drivers/LED/LHY_led.c:40: error: ‘S3C2410_GPB5_OUTP‘ undeclared here (not in a function)
解决方法:同样原因,在arch/arm/mach-s3c2410/include/mach/regs-gpio.h文件下 可以看到
47行 #define S3C2410_GPIO_OUTPUT (0xFFFFFFF1)
在S3C2410_GPB5_OUTP 改为 S3C2410_GPIO_OUTPUT
3、error: ‘misc‘ undeclared (first use in this function)
error: variable ‘dev_fops‘ has initializer but incomplete type
error: unknown field ‘owner‘ specified in initializer
这都是没注意,写错了代码:file_operations写错了,misc也写的上下不一致,MISC_DYNAMIC_MINOR漏写了字母
4、 line 1: syntax error: word unexpected (expecting ")") 错误
直接使用gcc编译
命令:arm-linux-gcc -g ledceshi.c(c文件) -o ledceshi(产生的可执行文件名)
mini2440驱动奇谭——LED驱动与测试(动态加载)