加入星计划,您可以享受以下权益:

  • 创作内容快速变现
  • 行业影响力扩散
  • 作品版权保护
  • 300W+ 专业用户
  • 1.5W+ 优质创作者
  • 5000+ 长期合作伙伴
立即加入
  • 正文
    • 11.3  GPIO驱动程序实例
  • 相关推荐
  • 电子产业图谱
申请入驻 产业图谱

嵌入式Linux设备驱动开发之:GPIO驱动程序实例

2013/09/13
2
阅读需 34 分钟
加入交流群
扫码加入
获取工程师必备礼包
参与热点资讯讨论

 

11.3  GPIO驱动程序实例

11.3.1  GPIO工作原理

FS2410开发板的S3C2410处理器具有117个多功能通用I/O(GPIO)端口管脚,包括GPIO 8个端口组,分别为GPA(23个输出端口)、GPB(11个输入/输出端口)、GPC(16个输入/输出端口)、GPD(16个输入/输出端口)、GPE(16个输入/输出端口)、GPF(8个输入/输出端口)、GPH(11个输入/输出端口)。根据各种系统设计的需求,通过软件方法可以将这些端口配置成具有相应功能(例如:外部中断或数据总线)的端口。

为了控制这些端口,S3C2410处理器为每个端口组分别提供几种相应的控制寄存器。其中最常用的有端口配置寄存器(GPACON ~ GPHCON)和端口数据寄存器(GPADAT ~ GPHDAT)。因为大部分I/O管脚可以提供多种功能,通过配置寄存器(PnCON)设定每个管脚用于何种目的。数据寄存器的每位将对应于某个管脚上的输入或输出。所以通过对数据寄存器(PnDAT)的位读写,可以进行对每个端口的输入或输出。

在此主要以发光二极管(LED)和蜂鸣器为例,讨论GPIO设备的驱动程序。它们的硬件驱动电路原理图如图11.4所示。

                 

图11.4  LED(左)和蜂鸣器(右)的驱动电路原理图

在图11.4中,可知使用S3C2410处理器的通用I/O口GPF4、GPF5、GPF6和GPF7分别直接驱动LED D12、D11、D10以及D9,而使用GPB0端口驱动蜂鸣器。4个LED分别在对应端口(GPF4~GPF7)为低电平时发亮,而蜂鸣器在GPB0为高电平时发声。这5个端口的数据流方向均为输出。

在表11.15中,详细描述了GPF的主要控制寄存器。GPB的相关寄存器的描述与此类似,具体可以参考S3C2410处理器数据手册。

表11.15 GPF端口(GPF0-GPF7)的主要控制寄存器

寄存器

地址

R/W

功能

初始值

GPFCON

0x56000050

R/W

配置GPF端口组

0x0

GPFDAT

0x56000054

R/W

GPF端口的数据寄存器

未定义

GPFUP

0x56000058

R/W

GPF端口的取消上拉寄存器

0x0

GPFCON

描述

GPF7

[15:14]

00 = 输入  01 = 输出  10 = EINT7  11 = 保留

GPF6

[13:12]

00 = 输入  01 = 输出  10 = EINT6  11 = 保留

GPF5

[11:10]

00 = 输入  01 = 输出  10 = EINT5  11 = 保留

GPF4

[9:8]

00 = 输入  01 = 输出  10 = EINT4  11 = 保留

GPF3

[7:6]

00 = 输入  01 = 输出  10 = EINT3  11 = 保留

GPF2

[5:4]

00 = 输入  01 = 输出  10 = EINT2  11 = 保留

GPF1

[3:2]

00 = 输入  01 = 输出  10 = EINT1  11 = 保留

GPF0

[1:0]

00 = 输入  01 = 输出  10 = EINT0  11 = 保留

GPFDAT

描述

GPF[7:0]

[7:0]

每位对应于相应的端口,若端口用于输入,则可以通过相应的位读取数据;若端口用于输出,则可以通过相应的位输出数据;若端口用于其他功能,则其值无法确定。

GPFUP

描述

GPF[7:0]

[7:0]

0:向相应端口管脚赋予上拉(pull-up)功能

1:取消上拉功能 

为了驱动LED和蜂鸣器,首先通过端口配置寄存器将5个相应寄存器配置为输出模式。然后通过对端口数据寄存器的写操作,实现对每个GPIO设备的控制(发亮或发声)。在下一个小节中介绍的驱动程序中,s3c2410_gpio_cfgpin()函数和s3c2410_gpio_pullup()函数将进行对某个端口的配置,而s3c2410_gpio_setpin()函数实现向数据寄存器的某个端口的输出。

 

11.3.2  GPIO驱动程序

GPIO驱动程序代码如下所示:

/* gpio_drv.h */

#ifndef     FS2410_GPIO_SET_H

#define     FS2410_GPIO_SET_H

#include    <linux/ioctl.h>

#define     GPIO_DEVICE_NAME       "gpio"

#define     GPIO_DEVICE_FILENAME  "/dev/gpio"

#define     LED_NUM                  4

#define     GPIO_IOCTL_MAGIC       'G'

#define     LED_D09_SWT             _IOW(GPIO_IOCTL_MAGIC, 0, unsigned int)

#define     LED_D10_SWT             _IOW(GPIO_IOCTL_MAGIC, 1, unsigned int)

#define     LED_D11_SWT             _IOW(GPIO_IOCTL_MAGIC, 2, unsigned int)

#define     LED_D12_SWT             _IOW(GPIO_IOCTL_MAGIC, 3, unsigned int)

#define     BEEP_SWT                 _IOW(GPIO_IOCTL_MAGIC, 4, unsigned int)

#define     LED_SWT_ON              0

#define     LED_SWT_OFF             1

#define     BEEP_SWT_ON             1

#define     BEEP_SWT_OFF            0

#endif /* FS2410_GPIO_SET_H */

/* gpio_drv.c */

#include <linux/config.h>

#include <linux/module.h>

#include <linux/moduleparam.h>

#include <linux/init.h>

#include <linux/kernel.h>   /* printk() */

#include <linux/slab.h>        /* kmalloc() */

#include <linux/fs.h>       /* everything... */

#include <linux/errno.h>    /* error codes */

#include <linux/types.h>    /* size_t */

#include <linux/mm.h>

#include <linux/kdev_t.h>

#include <linux/cdev.h>

#include <linux/delay.h>

#include <linux/device.h>

#include <asm/io.h>

#include <asm/uaccess.h>

#include <asm/arch-s3c2410/regs-gpio.h>

#include "gpio_drv.h"

static int major = 0; /* 采用字符设备号的动态分配 */

module_param(major, int, 0); /* 以参数的方式可以指定设备的主设备号*/

void s3c2410_gpio_cfgpin(unsigned int pin, unsigned int function)

{ /* 对某个管脚进行配置(输入/输出/其他功能)*/

     unsigned long base = S3C2410_GPIO_BASE(pin); /* 获得端口的组基地址*/

     unsigned long shift = 1;

     unsigned long mask = 0x03; /* 通常用配置寄存器的两位表示一个端口*/

     unsigned long con;

     unsigned long flags;

     if (pin < S3C2410_GPIO_BANKB) 

     {   

        shift = 0;

        mask  = 0x01; /* 在GPA端口中用配置寄存器的一位表示一个端口*/

     }   

     mask <<= (S3C2410_GPIO_OFFSET(pin) << shift);

     local_irq_save(flags); /* 保存现场,保证下面一段是原子操作 */

     con = __raw_readl(base + 0x00);

     con &= ~mask;

    con |= function;

    __raw_writel(con, base + 0x00); /* 向配置寄存器写入新配置数据 */

    local_irq_restore(flags); /* 恢复现场 */

}

void s3c2410_gpio_pullup(unsigned int pin, unsigned int to) 

{ /* 配置上拉功能 */

    unsigned long base = S3C2410_GPIO_BASE(pin); /* 获得端口的组基地址*/

    unsigned long offs = S3C2410_GPIO_OFFSET(pin);/* 获得端口的组内偏移地址 */

    unsigned long flags;

    unsigned long up; 

    

    if (pin < S3C2410_GPIO_BANKB)

    {

        return;

    }

    local_irq_save(flags);

    up = __raw_readl(base + 0x08);

    up &= ~(1 << offs);

    up |= to << offs;

    __raw_writel(up, base + 0x08); /* 向上拉功能寄存器写入新配置数据*/

    local_irq_restore(flags);

}

void s3c2410_gpio_setpin(unsigned int pin, unsigned int to)

{ /* 向某个管脚进行输出 */

    unsigned long base = S3C2410_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);

}

int gpio_open (struct inode *inode, struct file *filp)

{ /* open操作函数:进行寄存器配置*/

    s3c2410_gpio_pullup(S3C2410_GPB0, 1); /* BEEP*/    

    s3c2410_gpio_pullup(S3C2410_GPF4, 1); /* LED D12 */    

    s3c2410_gpio_pullup(S3C2410_GPF5, 1); /* LED D11 */    

    s3c2410_gpio_pullup(S3C2410_GPF6, 1); /* LED D10 */    

    s3c2410_gpio_pullup(S3C2410_GPF7, 1); /* LED D9 */    

    s3c2410_gpio_cfgpin(S3C2410_GPB0, S3C2410_GPB0_OUTP);

    s3c2410_gpio_cfgpin(S3C2410_GPF4, S3C2410_GPF4_OUTP);

    s3c2410_gpio_cfgpin(S3C2410_GPF4, S3C2410_GPF5_OUTP);

    s3c2410_gpio_cfgpin(S3C2410_GPF4, S3C2410_GPF6_OUTP);

    s3c2410_gpio_cfgpin(S3C2410_GPF4, S3C2410_GPF7_OUTP);

    return 0;

}

ssize_t gpio_read(struct file *file, char __user *buff, 

                                                    size_t count, loff_t *offp)

{ /* read操作函数:没有实际功能*/

    return 0;

}

ssize_t gpio_write(struct file *file, const char __user *buff, 

                                                     size_t count, loff_t *offp)

{ /* write操作函数:没有实际功能*/

    return 0;

}

int switch_gpio(unsigned int pin, unsigned int swt)

{ /* 向5个端口中的一个输出ON/OFF值 */

    if (!((pin <= S3C2410_GPF7) && (pin >= S3C2410_GPF4)) 

                     && (pin != S3C2410_GPB0))

    {

        printk("Unsupported pin");

        return 1; 

    }

    s3c2410_gpio_setpin(pin, swt);

    return 0;

}

 

static int gpio_ioctl(struct inode *inode, struct file *file, 

                                      unsigned int cmd, unsigned long arg)

{ /* ioctl函数接口:主要接口的实现。对5个GPIO设备进行控制(发亮或发声) */

    unsigned int swt = (unsigned int)arg;

    switch (cmd) 

    {

        case LED_D09_SWT:

        {

            switch_gpio(S3C2410_GPF7, swt);    

        }

        break;

        case LED_D10_SWT:

        {

            switch_gpio(S3C2410_GPF6, swt);

        }

        break;

        case LED_D11_SWT:

        {

            switch_gpio(S3C2410_GPF5, swt);

        }

        break;

        case LED_D12_SWT:

        {

            switch_gpio(S3C2410_GPF4, swt);

        }

        break;

        case BEEP_SWT:

        {

            switch_gpio(S3C2410_GPB0, swt);

            break;

        }

        default:

        {

            printk("Unsupported command\n");

            break;

        }

    }

    return 0;

}

static int gpio_release(struct inode *node, struct file *file)

{ /* release操作函数,熄灭所有灯和关闭蜂鸣器 */

    switch_gpio(S3C2410_GPB0, BEEP_SWT_OFF);

    switch_gpio(S3C2410_GPF4, LED_SWT_OFF);

    switch_gpio(S3C2410_GPF5, LED_SWT_OFF);

    switch_gpio(S3C2410_GPF6, LED_SWT_OFF);

    switch_gpio(S3C2410_GPF7, LED_SWT_OFF);

    return 0;

}

static void gpio_setup_cdev(struct cdev *dev, int minor,

        struct file_operations *fops)

{ /* 字符设备的创建和注册 */

    int err, devno = MKDEV(major, minor);    

    cdev_init(dev, fops);

    dev->owner = THIS_MODULE;

    dev->ops = fops;

    err = cdev_add (dev, devno, 1);

    if (err)

    {

        printk (KERN_NOTICE "Error %d adding gpio %d", err, minor);

    }

}

static struct file_operations gpio_fops = 

{ /* gpio设备的file_operations结构定义 */

    .owner   = THIS_MODULE,

    .open    = gpio_open,        /* 进行初始化配置*/

    .release = gpio_release,    /* 关闭设备*/

    .read    = gpio_read,        

    .write   = gpio_write,

    .ioctl   = gpio_ioctl,        /* 实现主要控制功能*/

};

static struct cdev gpio_devs;

static int gpio_init(void)

{

    int result;

    dev_t dev = MKDEV(major, 0);

    if (major)

    { /* 设备号的动态分配 */

        result = register_chrdev_region(dev, 1, GPIO_DEVICE_NAME);

    }

    else 

    { /* 设备号的动态分配 */

        result = alloc_chrdev_region(&dev, 0, 1, GPIO_DEVICE_NAME);

        major = MAJOR(dev);

    }

    if (result < 0) 

    {

        printk(KERN_WARNING "Gpio: unable to get major %d\n", major);

        return result;

    }

    gpio_setup_cdev(&gpio_devs, 0, &gpio_fops);

    printk("The major of the gpio device is %d\n", major);

    return 0;

}

 

static void gpio_cleanup(void)

{

    cdev_del(&gpio_devs); /* 字符设备的注销 */

    unregister_chrdev_region(MKDEV(major, 0), 1); /* 设备号的注销*/

    printk("Gpio device uninstalled\n");

}

module_init(gpio_init);

module_exit(gpio_cleanup);

MODULE_AUTHOR("David");

MODULE_LICENSE("Dual BSD/GPL");           

下面列出GPIO驱动程序的测试用例:

/* gpio_test.c */

#include <stdio.h>

#include <stdlib.h>

#include <unistd.h>

#include <fcntl.h>

#include <string.h>

#include <sys/types.h>

#include <sys/stat.h>

#include "gpio_drv.h"

int led_timer(int dev_fd, int led_no, unsigned int time)

{ /*指定LED发亮一段时间之后熄灭它*/

    led_no %= 4;

    ioctl(dev_fd, LED_D09_SWT + led_no, LED_SWT_ON); /* 发亮*/

    sleep(time);

    ioctl(dev_fd, LED_D09_SWT + led_no, LED_SWT_OFF); /* 熄灭 */

}

int beep_timer(int dev_fd, unsigned int time)

{/* 开蜂鸣器一段时间之后关闭*/

    ioctl(dev_fd, BEEP_SWT, BEEP_SWT_ON); /* 发声*/

    sleep(time);

    ioctl(dev_fd, BEEP_SWT, BEEP_SWT_OFF);    /* 关闭 */

}

int main()

{

    int i = 0;

    int dev_fd;

        /* 打开gpio设备 */

    dev_fd = open(GPIO_DEVICE_FILENAME, O_RDWR | O_NONBLOCK);

    if ( dev_fd == -1 ) 

    {

        printf("Cann't open gpio device file\n");

        exit(1);

    }

    while(1)

    {

        i = (i + 1) % 4;

        led_timer(dev_fd, i, 1);

        beep_timer(dev_fd, 1);        

    }

    close(dev_fd);

    return 0;

}           

具体运行过程如下所示。首先编译并加载驱动程序:

$ make clean;make /* 驱动程序的编译*/

$ insmod gpio_drv.ko /* 加载gpio驱动 */

$ cat /proc/devices /* 通过这个命令可以查到gpio设备的主设备号 */

$ mknod /dev/gpio  c  252  0  /* 假设主设备号为252, 创建设备文件节点*/

然后编译并运行驱动测试程序:

$ arm-linux-gcc –o gpio_test  gpio_test.c

$ ./gpio_test

运行结果为4个LED轮流闪烁,同时蜂鸣器以一定周期发出声响。

相关推荐

电子产业图谱

华清远见(www.farsight.com.cn)是国内领先嵌入师培训机构,2004年注册于中国北京海淀高科技园区,除北京总部外,上海、深圳、成都、南京、武汉、西安、广州均有直营分公司。华清远见除提供嵌入式相关的长期就业培训、短期高端培训、师资培训及企业员工内训等业务外,其下属研发中心还负责嵌入式、Android及物联网方向的教学实验平台的研发及培训教材的出版,截止目前为止已公开出版70余本嵌入式/移动开发/物联网相关图书。企业理念:专业始于专注 卓识源于远见。企业价值观:做良心教育、做专业教育,更要做受人尊敬的职业教育。