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         // 和GPIO相关的宏定义 
#include          //S3C2410_gpio_cfgpin
                                   //S3C2410_gpio_setpin等gpio函数定义

//在 linux-2.6.32.2/include/linux 目录下
#include        //注册 miscdevide 结构体成员变量
#include             //这个应该是时延函数了
#include            //内核,不用说
#include            //驱动加载卸载函数定义
#include              //初始化头文件
#include
#include                //注册file_operations结构体变量
#include
#include
#include
#include              
#include
#include
#include
#include
#include
#include              //gpio口定义文件(虚拟地址)

//在 linux-2.6.32.2/arch/arm/include/asm 目录下
#include
#include
#include
#include

#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
#include
#include
#include

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(产生的可执行文件名)


开发板上的操作截图(这个绝不是盗图了。。。。。。)

mini2440驱动奇谭——LED驱动与测试(动态加载)_第1张图片


(三)丶函数调用笔记

(一)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中,定义在文件中 
             
    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哈哈~)

mini2440驱动奇谭——LED驱动与测试(动态加载)_第2张图片


错误笔记(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(产生的可执行文件名)             


你可能感兴趣的:(Ubuntu-Linux之旅,linux驱动开发)