Linux下GPIO驱动

      编写驱动程序,首先要了解是什么类型的设备。linux下的设备分为三类,分别为:字符设备块设备网络设备。字符设备类型是根据是否以字符流为数据的交换方式,大部分设备都是字符设备,如键盘,串口等,块设备则是以块为单位进行管理的设备,如,磁盘。网络设备就是网卡等。

其次要了解应用程序和驱动程序的区别,两者的主要区别分为以下三点:

1.入口函数的任务不相同,应用程序完成一个任务,驱动只完成初始化工作,比如中断申请,寄存器设置,定时器设置。

2.运行时的cpu模式不相同,驱动具有很高的权限,应用程序是在用户态下运行,而驱动程序是在内核态下执行。

3. 驱动程序不能调用C库函数,只能使用内核为驱动程序提供一些函数。

鉴于以上区别,驱动程序需要完成以下三点基本功能:

1:要对设备进行初始化和释放功能模块,就如上面的寄存器设置,中断的申请,向内核注册驱动程序(register_chrdev()),卸载驱动程序(unregister_chrdev())。

2:能进行数据传输,在read(),write()函数里具体实现,数据传输工作。

3:能进行控制操作,给用户提供的ioctl()函数里可实现一些用户的选择性设置功能。

 

模块的编译、安装、卸载:

#include <linux/module.h>  
#include <linux/init.h>  


int __init led_init (void)  
{  
    printk ("led init\n");  
    return 0;  
}  

void __exit led_exit (void)  
{  
    printk ("module exit\n");  
    return ;  
}  

module_init(led_init);  //模块初始化
module_exit(led_exit);  //模块退出

MODULE_LICENSE("GPL");  //许可证

Makefile文件:

KVERS = $(shell uname -r)

obj-m := gpio_driver_linux.o

build: kernel_modules

kernel_modules:
	make -C /lib/modules/$(KVERS)/build M=$(CURDIR) modules
clean:
	make -C /lib/modules/$(KVERS)/build M=$(CURDIR) clean

make:

安装模块:

 

验证成功:

 

卸载模块:

 

裸机GPIO驱动:

 

代码:

#define reg_gpio_ctrl *(volatile int *)(To_Virtual(GPIO_REG_CTRL))
#define reg_gpio_data *(volatile int *)(To_Virtual(GPIO_REG_DATA))
//将物理地址映射为虚拟地址 ioremap()  iounmap()  linux/io.h
//volatile 编译器对访问该变量的代码就不再进行优化,从而可以提供对特殊地址的稳定访问

//led 初始化
void led_init(void) {
    reg_gpio_ctrl |= (1 << n); //设置io口输出
}

void led_on(void) {
    reg_gpio_data |= (1 << n); //输出高电平
}

void led_off(void) {
    reg_gpio_data &= ~(1 << n); //输出低电平
}

Linux下GPIO驱动:

代码: 

#include <linux/module.h> //MODULE_LICENSE
#include <linux/init.h> //module_init

#include <linux/types.h> //dev_t
#include <linux/kdev_t.h> //dev_T
#include <linux/fs.h> //inode file_operations

#include <linux/device.h>//device_creat
#include <linux/cdev.h> 
#include <asm/uaccess.h> //copy_from_user copy_to_user
#include <linux/io.h>  //ioremap iounmap 物理地址转虚拟地址


#define LED_MAJOR 240

int led_major = LED_MAJOR; //主设备号
struct cdev *led_cdev;
struct class *led_cdev_class;

int led_open(struct inode *inode, struct file *filp) {
    printk("open led\n");
    return 0;
}
//read和write方法完成的任务是相似的,即,拷贝数据到应用程序空间,或者反过来从应用程序空间拷贝数据;
ssize_t led_read(struct file *filp, //其中参数filp是文件指针,
char __user *buf, //参数buff指向用户空间的缓冲区,这个缓冲区是一个存放新读入数据的空缓冲区;
size_t count, //参数count是请求传输的数据长度,
loff_t *f_pos) { //offp指明了用户在文件中进行存放操作的位置;
    printk("read led\n");
    return 1; //读取成功返回读取的字节数
}
ssize_t led_write(struct file *filp, //其中参数filp是文件指针,
const char __user *buf, //参数buff指向用户空间的缓冲区,这个缓冲区保存将要写入的数据
size_t conut, //参数count是请求传输的数据长度,
loff_t *f_pos) {//offp指明了用户在文件中进行存放操作的位置;
    printk("write led\n");
    return 1;
}
int led_release(struct inode *inode, struct file *filp) {
    printk("release led\n");
    return 0;
}
int led_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) {
   printk("ioctl led\n");
    return 0;
}

struct file_operations led_fops = {
    .owner = THIS_MODULE,
    .open = led_open,
    .read = led_read,
    .write = led_write,
    .unlocked_ioctl = led_ioctl,
    .release = led_release,
};

int led_init(void) {
    printk("module_init led\n");
    int result;
    dev_t devid = MKDEV(led_major, 0); //MKDEV是将主设备号和次设备号转换成dev_t类型的一个内核函数。 参数:主设备号, 次设备号
    //申请字符设备的设备号
    if(led_major) {
        //静态申请,通过设定的设备号初值申请
        result = register_chrdev_region(devid, 1, "LED"); 
         //第一个参数:要分配的设备编号范围的初始值(次设备号常设为0)
         //第二个参数:连续编号范围
         //第三个参数:编号相关联的设备名称.  //LED最终会显示在dev/目录下
    } else {
         //动态申请设备号,申请后放到dev
        result = alloc_chrdev_region(&devid, 0, 1, "LED"); 
        //第一个参数:用来存放申请的设备号
        //第二个参数:是请求的最小的次编号
        //第三个参数:是请求的连续设备编号的总数
        //第四个参数:设备名
        led_major = MAJOR(devid); //从设备号中获取主设备号  //MINOR():获取次设备号
    }
    if(led_major < 0)
        return result;
    led_cdev = cdev_alloc();
    if(led_cdev != NULL) {
        cdev_init(led_cdev, &led_fops);
        led_cdev->owner = THIS_MODULE;
        if(cdev_add(led_cdev, devid, 1) != 0) {
             //cdev_add就是将我们之前获得设备号和设备号长度填充到cdev结构中;
             //参数1:字符结构体,参数2:设备号,参数3:个数
            goto error;
        }
    } else {
        return -1;
    }
    led_cdev_class = class_create(THIS_MODULE, "led_cdev_class");
    if(IS_ERR(led_cdev_class)) {
        return -1;
    }
    device_create(led_cdev_class, NULL, devid, NULL, "LED", NULL);
    return 0;
error:
    unregister_chrdev_region(devid, 1);
    return result;
}

void led_exit(void) {
    printk("module_exit led\n");
    cdev_del(led_cdev);
    unregister_chrdev_region(MKDEV(led_major, 0), 1);
    class_destroy(led_cdev_class);
}

module_init(led_init);
module_exit(led_exit);

MODULE_LICENSE("GPL");  //许可

结果:

模块安装成功:

设备号申请成功:

设备创建成功:

运行测试程序:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <errno.h>
#include <fcntl.h>

int main() {

    int led_fd = open("/dev/LED", O_RDWR);
    if(led_fd < 0) {
        perror("open\n");
        return -1;
    }

    int data;
    int ret = read(led_fd, &data, 1);
    if(!ret) {
        perror("read\n");
        return -1;
    }

    data = 0;
    ret = write(led_fd, &data, 1);
    if(!ret) {
        perror("write\n");
        return -1;
    }

    ret = ioctl(led_fd, NULL, NULL);
    if(!ret) {
        perror("ioctl\n");
        return -1;
    }
    close(led_fd);
    return 0;
}

结果:

裸机GPIO驱动与LinuxGPIO驱动的区别:

裸机GPIO驱动:

  1. 裸机驱动一般针对没有操作系统支持的层面,不用考虑操作系统对它的调用.
  2. 底层,跟寄存器打交道,有些MCU提供了库

Linux下GPIO驱动:

  1. linux下驱动开发直接操作寄存器,不现实
  2. 根据Linux下的各种驱动框架,进行开发;一定要满足框架,也就是linux下各种驱动框架的掌握;
  3. 在linux下,驱动最终表现(对应的外设)是 /dev/xxx下的文件;打开、关闭、读写都可以

 

 

 

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值