mini2440驱动奇谭——LED驱动与测试(动态加载)
实现功能:开发板动态加载led驱动模块并能通过测试程序系统:Ubuntu 14.04驱动交叉编译内核:linux-2.6.32.2//建立交叉编译开发板:mini2440 (128M nandflash)//关于怎么烧写linux到开发板请点击,Linux RootFs 选择rootfs_rtm_2440.img(光盘目录:image/linux/rtm )开发所需工具:NFS网络文件minico
我的博客: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 )
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(产生的可执行文件名)
更多推荐
所有评论(0)