开发板上使用GPIO向电磁继电器发送高低电平控制台灯亮灭(我的第一个linux驱动程序开发)

开发板:tiny6410

虚拟机:Fedora 9


本驱动程序是根据tiny6410 开发文档中LED灯驱动程序改写,由于第一次接触驱动程序开发,第一次这么直接的操作硬件, 所以难免会有不准确的地方,希望大家见谅。

首先我来补充一下,驱动程序开发的基础知识。其实也是在开发这个驱动程序时新学的,看的书是北京航空航天大学出版社郑灵翔主编的《嵌入式接口技术与linux驱动程序开发》,似乎写的还不错,暂时我只看了一丢丢。

Linux中设备都是以文件的形式管理的,虽然挺难理解,但是现在就先有这个概念吧,操作一个字符设备,就当做是操作一个/root/目录下的一个普通文件吧(例如hello.c),手写操作hello.c,我们需要打开这个文件,当然字符设备不会像hello.c一样,双击就能打开,稍微有点Linux基础的人都知道,宏观上任何一个动作,实际上都是以命令的形式发送的,至于windows中双击打开一个文件,实际底层实现时也是通过向内核发命令实现的。好了,这就不多说了,在Linux中打开一个字符设备,是通过open函数来实现的。驱动程序的编译分为两种,一种是被编译为动态可加载的内核模块,通过insmod、rmmod命令加载和移除,一般我们采用这种方法。因为编译成内核模块还的重写烧写BIOS,不利于驱动程序的调试。 tiny6410开发文档中使用的是modprobe加载,两者有一定区别,大家可以百度一下,另一种是被静态编译至内核,这样每次使用时就不用自己挂载了。系统启动(实际是内核初始化)时,自动加载驱动。

在Linux2.6之后内核编译引入了kbuild,将外部内核模块的编译同内核源码树的编译统一起来。在内核源码树中的模块编译与对应目录下的Kconfig文件和Makefile文件有关。其中,分布在内核源码树各个子目录中的Kconfig文件将构成了一个分布式的内核配置数据库,描述了内核配置菜单项的所有内容,每个Kconfig文件分别描述了所属目录源文件相关的内核配置菜单项。在用户运行内核配置命令进行内核配置时,内核配置命令从各个Kconfig文件中读出内核配置菜单项,并将配置菜单显示给用户。用户配置完内核后,其所选的内核配置被存入选项配置文件“.config”中,它在内核编译时被读入Makefile,作为相应菜单项的配置变量,成为Makefile的一部分。(不是太理解???没关系,自己按着开发文档中编译内核驱动的步骤做一遍,再来理解这句话,就能看懂了。通常任何一个语言、任何一向东西的入门都有一个类似hello world的例子,哈哈,这个计算机人都懂得。在驱动开发中,应该都有一个不操作任何硬件的例子,就是hello什么玩意的。)

Linux中系统用户对设备的操作采用文件接口实现。用户采用标准的文件操作访问设备,虚拟文件系统将用户的这种文件访问操作转换为对设备的具体操作。为实现这种用户文件操作到设备操作的转换,虚拟文件系统向下为设备驱动提供了一个标准化的文件操作实现接口。这个接口由include/linux/fs.h文件中的file_operation结构定义,该结构定义的操作大体上可分为三大类:设备的打开和释放操作、设备的读写操作和设备的控制操作。

file_operation是个结构体变量,里面定义了很多指针型成员变量。就是实现用户文件操作到设备操作的转换的具体操作。

现在我们来说一下GPIO,GPIO就是通用输入输出口,在这里我们使用的是输出功能,输出接上继电器,继电器另一端接上高压电源和台灯。GPIO中每个接口由控制寄存器、数据寄存器等组成,我们通过各个寄存器地址使用GPIO接口。下图是tiny6410硬件资源中CON1的部分接口说明:

开发板上使用GPIO向电磁继电器发送高低电平控制台灯亮灭(我的第一个linux驱动程序开发)_第1张图片

查询tiny6410硬件资源可知我们可以使用的各个GPIO接口,在这里我们使用的是GPQ4、GPQ5、GPQ6作为输出端,然后查询GPQ接口的控制寄存器和数据寄存器的地址,再查询GPQ中4、5、6对应的位置,将其设为输出。

开发板上使用GPIO向电磁继电器发送高低电平控制台灯亮灭(我的第一个linux驱动程序开发)_第2张图片开发板上使用GPIO向电磁继电器发送高低电平控制台灯亮灭(我的第一个linux驱动程序开发)_第3张图片

以下代码用于将GPQ控制寄存器的4、5、6接口对应的[8:9] [10:11] [12:13位]分别设为01 ,表示输出。

        unsigned tmp;
        tmp = readl(S3C64XX_GPQCON);
        tmp = (tmp & ~(0x003fU<<8))|(0x0015U<<8);
        writel(tmp,S3C64XX_GPQCON);

然后再初始化GPQ的数据寄存器,将4、5、6为设为1

        tmp = readl(S3C64XX_GPQDAT);
        tmp |= (0x7 << 4);
        writel(tmp,S3C64XX_GPQDAT);


所有的驱动程序代码如下:

#include
#include
#include
//#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include

#include
#include
#include

#include
#include
#include
#include

//#define DEVICE_NAME "leds"
#define DEVICE_NAME "light"  //硬件设备名


static long sbc2440_leds_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{    
    switch(cmd) {
        unsigned tmp;
    case 0:
    case 1:
        if (arg > 3) {
            return -EINVAL;
        }
        tmp = readl(S3C64XX_GPQDAT);
        tmp &= ~(1 << (4 + arg));
        tmp |= ( (!cmd) << (4 + arg) );
        writel(tmp, S3C64XX_GPQDAT);
        //printk (DEVICE_NAME": %d %d\n", arg, cmd);
        return 0;
    default:
        return -EINVAL;
    }
    
}
//vitual files system file --> equipment
static struct file_operations dev_fops = {
    .owner            = THIS_MODULE,
    .unlocked_ioctl    = sbc2440_leds_ioctl,
};

static struct miscdevice misc = {
    .minor = MISC_DYNAMIC_MINOR,
    .name = DEVICE_NAME,
    .fops = &dev_fops,
};

static int __init dev_init(void)
{
    int ret;

    {    
        unsigned tmp;
        
        tmp = readl(S3C64XX_GPQCON);
        tmp = (tmp & ~(0x003fU<<8))|(0x0015U<<8);
        writel(tmp,S3C64XX_GPQCON);
        
        
        
        tmp = readl(S3C64XX_GPQDAT);
        tmp |= (0x7 << 4);
        writel(tmp,S3C64XX_GPQDAT);
        
    }

    ret = misc_register(&misc);

    printk (DEVICE_NAME"\tinitialized\n");

    return ret;
}

static void __exit dev_exit(void)
{
    unsigned tmp;

    tmp = readl(S3C64XX_GPQDAT);
    tmp |= (0x7 << 4);
    writel(tmp,S3C64XX_GPQDAT);    
    printk (DEVICE_NAME"\tremoved\n");

    misc_deregister(&misc);
}

module_init(dev_init);
module_exit(dev_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("FriendlyARM Inc.");

编译完驱动程序后,将.ko文件移植到开发板上相应位置,tiny6410中为了能够正常卸载必须放在/lib/modules/2.6.38-FriendlyARM/目录下,挂载之后就可以在/dev/目录下看到light这个设备,挂载之前是没有的,大家可以测试一下。

下面再给出一个测试驱动的应用程序代码:

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

int main(int argc, char **argv)
{
    int on;
    int led_no;
    int fd;

    if (argc != 3 || sscanf(argv[1], "%d", &led_no) != 1 || sscanf(argv[2],"%d", &on) != 1 ||
            on < 0 || on > 1 || led_no < 0 || led_no > 2) {
        fprintf(stderr, "Usage: leds led_no 0|1\n");
        exit(1);
    }

    fd = open("/dev/light", 0);
    if (fd < 0) {
        perror("open device light");
        exit(1);
    }

    ioctl(fd, on, led_no);
    close(fd);

    return 0;
}

将该程序交叉编译后移植到开发板上,若在挂载驱动之前运行,会提示找不到设备  此时查看/dev/目录下的确没有light设备,挂载之后程序运行无误。

开发板上使用GPIO向电磁继电器发送高低电平控制台灯亮灭(我的第一个linux驱动程序开发)_第4张图片

开发板上使用GPIO向电磁继电器发送高低电平控制台灯亮灭(我的第一个linux驱动程序开发)_第5张图片



你可能感兴趣的:(ARM嵌入式)