树莓派gpio驱动编写——不使用wiringPi

一、相关概念

    • 总线地址

总线地址:cpu能够访问内存的范围

可以通过cat /proc/meminfo 来查看内存条大小

    • 物理地址

物理地址:硬件的实际地址或绝对地址

    • 虚拟地址

虚拟地址:逻辑(基于算法的地址(软件层面的地址,假地址))地址称为虚拟地址

  • 它可以用来加载程序数据(数据可能被加载到物理内存上,空间不够就加载到虚拟内存中)

  • 它对应着一段连续的内存地址,起始位置为 0。

  • 之所以说虚拟是因为这个起始的 0 地址是被虚拟出来的, 不是物理内存的 0 地址。

虚拟地址空间的大小也由操作系统决定,32位的操作系统虚拟地址空间的大小为 2^32 字节,也就是 4G,64 系统的操作系统虚拟地址空间大小为 2^64 字节

    • mmu与页表

mmu:MMU是Memory Management Unit的缩写,中文名是内存管理单元,有时称作分页内存管理单元(英语:paged memory management unit,缩写为PMMU)。它是一种负责处理中央处理器(CPU)的内存访问请求的计算机硬件虚拟地址和物理地址的映射关系存储在页表中

MMU位于CPU内,作用:

  • 程序中使用的地址均是虚拟内存地址,进程中的数据是如何进入到物理内存中的呢?

  • MMU完成虚拟内存到物理内存的映射,即虚拟地址映射为物理地址;

  • 流水线中预取指令取到的地址是虚拟地址,需要MMU转换以及设置访问权限

页表

  • 如物理地址为1M,虚拟地址为4M;虚拟地址可以从0-4M来表示物理地址0-1M,中间的算法叫做页表

  • 页表通过mmu来执行,把虚拟地址通过页表映射成物理地址。页表决定了1M在哪里被表示成4M

  • 其中是以页(4KB)为单位进行映射的

    • 设备号

  1. 设备号是用来区分硬件的

linux一切皆为文件,其设备管理同样是和文件系统紧密结合。各种设备都以文件的形式存在/dev目录下,称为设备文件。应用程序可以打开、关闭和读写这些设备文件,完成对设备的操作,就像操作普通的数据文件一样。

在目录/dev下都能看到鼠标,键盘,屏幕,串口等设备文件,硬件要有相对应的驱动,那么open怎样区分这些硬件呢? 依靠文件名与设备号。

  1. 主设备号和次设备号

设备号分为:主设备号:用来区分不同种类的设备

次设备号:用来区分同种类型的多个设备

驱动链表:管理所有设备的驱动
1、添加:在我们编写完驱动程序,加载到内核
2、查找:调用驱动程序,用户空间去open
3、驱动插入链表的顺序由设备号检索
    • 当用户态调用open时,内核态发生了什么

  • 用户层调用open产生一个软中断(终端号为0x80),进入内核空间调用sys_call

  • sys_call调用sys_open,去内核的驱动链表根据主设备号与次设备号找驱动

  • 调用驱动中的open,去做相应的操作

二、gpio驱动编写

cat /proc/iomem  获取虚拟地址所对应的物理地址
pinout   获取对应芯片型号
    • 查看芯片手册

通过阅读芯片手册我们可以得知,写pin4的gpio驱动我们需要使用GPFSEL0、GPSET0、GPCLR0寄存器,且每个寄存器占4位

GPFSEL0:GPIO Function Select 功能选择 输出或输入(GPIO Function Select Registers)

000 = GPIO Pin 9 is an input

001 = GPIO Pin 9 is an output

由该部分可以得知,我们对pin4进行操作就是对FSEL4进行操作,也是对12-14位进行操作,并且为001时为输出模式。

GPSET0: GPIO Pin Output Set; gpio输出1设置

GPCLR0: GPIO Pin Output Clear;gpio输出0设置

    • 进行地址赋值及映射

因为代码操作的是虚拟地址,而我们需要对物理地址进行操作,所以我们需要使用ioremap函数进行物理地址和虚拟地址之间的相互转换

函数原型:void *ioremap(unsigned long phys_addr, unsigned long size)
phys_addr:要映射的起始的IO物理地址;
size:要映射的空间的大小;

因为程序在编译过程中会对程序进行优化,所以我们需要使用volatile关键字来避免程序对该操作的优化

volatile的作用是作为指令关键字,确保本条 指令不会因编译器的优化而省略,且要求每次直接读值
volatile unsigned int *GPFSEL0 = (volatile unsigned int*)ioremap(0xfe200000,4);
volatile unsigned int *GPCLR0  = (volatile unsigned int*)ioremap(0xfe200028,4);
volatile unsigned int *GPSET0  = (volatile unsigned int*)ioremap(0xfe20001C,4);

进行了地址映射,我们还需要解除映射

void iounmap(void* addr)//取消ioremap所映射的IO地址
iounmap(GPFSEL0);  //放入exit函数中
iounmap(GPSET0);
iounmap(GPCLR0);
    • 进行输出设置

由芯片手册看出我们需要对32位中的其中几位,如果我们通过

GPSET0 = 0000000000000....0000..0

该方式进行赋值的话,这样对于我们的检查和编写都会造成很大的难度。所以我们使用位操作(|、&)。

将pin4引脚设置为输出模式
    *GPFSEL0 &= ~(0x6 << 12);//12-14  13、14置0 
    *GPFSEL0 |= (0x1 << 12);//12置1

将pin4引脚值1
    *GPSET0 |= (0x1 << 4);

将pin4引脚置0
    *GPCLR0 |= (0x1 << 4);

我们设置的是输出模式,所以我们需要获取上层的指令,可以通过copy_from_user函数

unsigned long copy_from_user(void * to, const void __user * from, unsigned long n)
第一个参数to是 内核空间的数据目标地址指针,
第二个参数from是 用户空间的数据源地址指针,
第三个参数n是 数据长度
此函数将from指针指向的用户空间地址开始的连续n个字节的数据产送到to指针指向的内核空间地址,简言之是用于将用户空间的数据传送到内核空间
    • 进行驱动模板编写以及测试

内核驱动框架

#include <linux/fs.h>         //file_operations声明
#include <linux/module.h>    //module_init  module_exit声明
#include <linux/init.h>      //__init  __exit 宏定义声明
#include <linux/device.h>     //class  devise声明
#include <linux/uaccess.h>   //copy_from_user 的头文件
#include <linux/types.h>     //设备号  dev_t 类型声明
#include <asm/io.h>          //ioremap iounmap的头文件
 
static struct class *pin4_class;  
static struct device *pin4_class_dev;
 
static dev_t devno;                //设备号
static int major = 231;             //主设备号
static int minor = 0;               //次设备号
static char *module_name = "pin4";   //模块名
 
//led_open函数
static int pin4_open(struct inode *inode,struct file *file)
{
    printk("pin4_open\n");  //内核的打印函数和printf类似
      
    return 0;
}
 
//led_write函数
static ssize_t pin4_write(struct file *file,const char __user *buf,size_t count, loff_t *ppos)
{
    printk("pin4_write\n");
    
    return 0;
}
 
//led_read函数 
static ssize_t pin4_read (struct file *file, char __user *buf, size_t count, loff_t *ppos)
{
    printk("pin4_read\n");

    return 0;
}

static struct file_operations pin4_fops = {
    .owner = THIS_MODULE,
    .open  = pin4_open,
    .write = pin4_write,
    .read  = pin4_read,   
};
 
int __init pin4_drv_init(void)   //真实驱动入口
{
    int ret;
    devno = MKDEV(major, minor);  //创建设备号
    ret   = register_chrdev(major, module_name, &pin4_fops);  //注册驱动  告诉内核,把这个驱动加入到内核驱动的链表中
 
    pin4_class=class_create(THIS_MODULE, "myfirstdemo");        //用代码在dev自动生成设备
    pin4_class_dev =device_create(pin4_class, NULL, devno, NULL, module_name);  //创建设备文件
    
    return 0;
}
 
void __exit pin4_drv_exit(void)
{
    device_destroy(pin4_class, devno);
    class_destroy(pin4_class);  
    unregister_chrdev(major, module_name);  //卸载驱动
 
}
 
module_init(pin4_drv_init);  //入口,内核加载该驱动的时候,这个宏被使用,在insmod时候就会调用
module_exit(pin4_drv_exit);
MODULE_LICENSE("GPL v2");

上层应用程序

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
 
int main()
{
        int fd;
 
        fd = open("/dev/pin4",O_RDWR);
        if(fd < 0){
                printf("open failed\n");
                perror("reson");
        }else{
                printf("open success\n");
        }
 
        fd = write(fd,1,1);//写一个字符'1',写一个字节
        return 0;
}
 

驱动模块的编译以及测试

将我们写好的驱动放入/drivers/char/文件夹下,并且对Makefile进行添加驱动

ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- KERNEL=kernel7l make -j4 modules
  • 通过该指令进行编译驱动生成xx.ko文件,该文件就为我们所需要的驱动文件。并且通过scp指令将xx.ko文件传到树莓派上

  • 在树莓派上通过insmod指令安装驱动

  • 通过lsmod检查驱动是否安装成功,此时如果我们直接对驱动进行操作,会提示权限不够

  • 通过chmod指令为该驱动增加权限

sudo chmod 666 /dev/pin4

此时就可以运行上层应用程序进行测试了,当程序运行结束后,通过dmesg进行驱动的输出检测

出现以上两行信息,及说明我们的模块是正确的、可以使用的。

三、进行pin4口的驱动编写

将以上我们所查阅的资料以及准备的模板进行整合

内核驱动

//字符设备驱动框架

#include <linux/fs.h>        //file_operations声明
#include <linux/module.h>    //module_init  module_exit声明
#include <linux/init.h>      //__init  __exit 宏定义声明
#include <linux/device.h>    //class  devise声明
#include <linux/uaccess.h>   //copy_from_user 的头文件
#include <linux/types.h>     //设备号  dev_t 类型声明
#include <asm/io.h>          //ioremap iounmap的头文件
 
static struct class *pin4_class;  
static struct device *pin4_class_dev;
 
static dev_t devno;                //设备号
static int major = 231;            //主设备号
static int minor = 0;              //次设备号
static char *module_name = "pin4";   //模块名
 
volatile unsigned int *GPFSEL0 = NULL;
volatile unsigned int *GPSET0  = NULL;
volatile unsigned int *GPCLR0  = NULL;

//led_open函数
static int pin4_open(struct inode *inode,struct file *file)
{
    //printk("pin4_open\n");  //内核的打印函数和printf类似

    *GPFSEL0 &= ~(0x6 << 12);//12-14  13、14置0 
    *GPFSEL0 |= (0x1 << 12);//12置1
    
    printk("pin4 set output success\n");

    return 0;
}
 
//led_write函数
static ssize_t pin4_write(struct file *file,const char __user *buf,size_t count, loff_t *ppos)
{
    //printk("pin4_write\n");
    char cmd_user;

    copy_from_user(&cmd_user,buf,count);

    printk("get value\n");

    if(cmd_user == 1){
        *GPSET0 |= (0x1 << 4);
        printk("pin4 set high\n");
    }
    if(cmd_user == 0){
        *GPCLR0 |= (0x1 << 4);
        printk("pin4 set low\n");
    }

    return 0;
}
 
//led_read函数 
static ssize_t pin4_read (struct file *file, char __user *buf, size_t count, loff_t *ppos)
{
    printk("pin4_read\n");

    return 0;
}

static struct file_operations pin4_fops = {
    .owner = THIS_MODULE,
    .open  = pin4_open,
    .write = pin4_write,
    .read  = pin4_read,   
};
 
int __init pin4_drv_init(void)   //真实驱动入口
{
    int ret;
    devno = MKDEV(major, minor);  //创建设备号
    ret   = register_chrdev(major, module_name, &pin4_fops);  //注册驱动  告诉内核,把这个驱动加入到内核驱动的链表中
 
    pin4_class=class_create(THIS_MODULE, "myfirstdemo");        //用代码在dev自动生成设备
    pin4_class_dev =device_create(pin4_class, NULL, devno, NULL, module_name);  //创建设备文件
    
    GPFSEL0 = (volatile unsigned int*)ioremap(0xfe200000,4);
    GPCLR0  = (volatile unsigned int*)ioremap(0xfe200028,4);
    GPSET0  = (volatile unsigned int*)ioremap(0xfe20001C,4);

    printk("pin4driver success\n");

    return 0;
}
 
void __exit pin4_drv_exit(void)
{
    iounmap(GPFSEL0);
    iounmap(GPSET0);
    iounmap(GPCLR0);

    device_destroy(pin4_class, devno);
    class_destroy(pin4_class);  
    unregister_chrdev(major, module_name);  //卸载驱动
 
}
 
module_init(pin4_drv_init);  //入口,内核加载该驱动的时候,这个宏被使用,在insmod时候就会调用
module_exit(pin4_drv_exit);
MODULE_LICENSE("GPL v2");

上层应用

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
 
int main()
{
        int fd;
        int cmd;
 
        fd = open("/dev/pin4",O_RDWR);
        if(fd < 0){
                printf("open failed\n");
                perror("reson");
        }else{
                printf("open success\n");
        }
 
        printf("请输入0 / 1\n 0:设置pin4为低电平\n 1:设置pin4为高电平\n");
        scanf("%d",&cmd);
 
        if(cmd == 0){
                printf("pin4设置成低电平\n");
        }else if(cmd == 1){
                printf("pin4设置成高电平\n");
        }
 
        fd = write(fd,&cmd,1);//写一个字符'1',写一个字节
        return 0;
}
 

当我们编写、编译、加载完成后,可以通过wiringPi中的gpio readall指令进行检查驱动是否正常运行。

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值