欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  科技

操控树莓派IO口的驱动代码编写

程序员文章站 2022-07-03 17:15:01
操控树莓派IO口的驱动代码编写树莓派(bcm2835芯片手册)驱动代码要对I/O口操作,首先得把其对应的物理地址在代码中用变量表示出来,但内核和上层代码访问的都是虚拟地址,所以在驱动代码里不能直接写物理地址,需要把物理地址转化为虚拟地址。先定义变量volatile unsigned int* GPFSEL0 = NULL;volatile unsigned int* GPSET0 = NULL;volatile unsigned int* GPCLR0 = NULL;volatile /...

操控树莓派IO口的驱动代码编写

树莓派(bcm2835芯片手册)

要对I/O口操作,首先得把其对应的物理地址在代码中用变量表示出来,但内核和上层代码访问的都是虚拟地址,所以在驱动代码里不能直接写物理地址,需要把物理地址转化为虚拟地址。

先定义变量

volatile unsigned int* GPFSEL0 = NULL;
volatile unsigned int* GPSET0 = NULL;
volatile unsigned int* GPCLR0 = NULL;
volatile //特征修饰符 作为指令关键字
   作用:
   1.确保指令不会因编译器的优化而省略。(编译器自认为人为给的数据不行,可能会
   被编译器给优化掉)
   2.要求每次直接从寄存器读值。(寄存器随着硬件的执行可能会改变寄存器里面的数
   据,如果没有volatile修饰,读取的数据是原先数据的一个备份,是个老数据,数据
   时效性就很差。)

unsigned 
	作用:
	整数分为有符号与无符号,如果要把类型声明为无符号数就需要使用unsigned来修饰
	(除char以外的数据类型中,默认情况下声明的整型变量都是有符号的类型),两者
	区别在于,有符号的数最高位的数作为符号位,无符号最高位作为值。如两个字节的
	short,有符号表示范围是-32768~32767,无符号范围是0~65535.

查看树莓派型号

cat /proc/cupinfo

操控树莓派IO口的驱动代码编写
此处指令看到的型号不是树莓派cpu真的型号,其真正型号应该是BCM2837,也就是IO在物理地址上的基址应该是0x3F000000。

操控树莓派IO口的驱动代码编写
操控树莓派IO口的驱动代码编写

通过查看芯片手册发GPFSEL0寄存器VC  CPU总线地址是0x7E200000,
相对基址(0x7E000000)偏移0x00200000,那么ARM物理地址也是偏移这
么多,所以GPIO的物理地址应该是从0x3f200000 开始 。

物理地址转化为虚拟地址(由芯片手册总线地址GPFSEL0、GPSET0、GPCLR0这三个地址的偏移量推出其物理地址)

 GPFSEL0 = volatile(unsigned int *)ioremap(0x3F200000,4);//ioremap函数将物理地址转换成虚拟地址,io口寄存器映射成普通内存单元进行访问
 GPSET0 = volatile(unsigned int *)ioremap(0x3F20001C,4);
 GPCLR0 = volatile(unsigned int *)ioremap(0x3F200028,4);

把上面三条语句写入函数:int __init pin4_drv_init(void) //真实驱动入口
操控树莓派IO口的驱动代码编写
如此就实现了物理地址转化成虚拟地址。(注:退出驱动时,用iounmap(*GP)函数解除地址映射);

此处以pin4引脚为例,要把pin4引脚设置为输出引脚,根据芯片手册内容需要配置GPFSEL0的14-12位(位置由0开始)为001。
操控树莓派IO口的驱动代码编写
但要注意的是在改变14-12位置上的值时,其它位置上的内容不能变,不然将会影响其它的引脚。

设置pin4引脚为输出引脚

 //把pin4变为输出引脚,配置14-12位置(由位置0开始)的内容为001
        *GPFSEL0 &= ~(0x6<<12);//6的二进制是110左移12位后,110对应的位置是14-12,取反后110变为001其它位为1,和GPFSEL0进行与运算后就实现只有14、13位改变为0
        *GPFSEL0 |= (0x1<<12);//把位置12变为1

获取上层write函数写入的值

 copy_from_user(&userCmd,buf,count);//获取上层write函数写入的值,放入userCmd变量;copy_to_user()函数用来读取引脚
        //根据值来操作io口变成高电平或者低电平
		if(userCmd == 1){//置1,使引脚输出高电平
                printk("set 1\n");
                *GPFSET0 |= (0x1<<4);//通常pin4用1左移4位进行或运算
        }else if(userCmd == 0){//置0,使引脚输出低电平
                printk("set 0");
                *GPCLR0 |= (0x1<<4);
        }else{
                printk("undo\n");
        }

驱动代码pin4driver.c

#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 device声明
#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;


static int pin4_read (struct file *file, char __user *buf, size_t count, loff_t *ppos)
{
        printk("pin4_read\n"); // 内核的打印函数
        //可以用copy_to_user()函数读取引脚
        return 0;
}

//pin4_open函数
static int pin4_open(struct inode *inode, struct file *file)
{
        printk("pin4_open\n"); // 内核的打印函数,和printf类似
        //把pin4变为输出引脚,配置14-12位置(由位置0开始)的内容为001
        *GPFSEL0 &= ~(0x6<<12);//6的二进制是110左移12位后,110对应的位置是14-12,取反后110变为001其它位为1,和GPFSEL0进行与运算后就实现只有14、13位改变为0
        *GPFSEL0 |= ~(0x6<<12);//把位置12变为1
        return 0;
}

//open_write函数
static ssize_t pin4_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos)
{
        printk("pin4_write\n");
        copy_from_user(&userCmd,buf,count);//获取上层write函数写入的值
        //根据值来操作io口变成高电平或者低电平
        if(userCmd == 1){
                printk("set 1\n");
                *GPFSET0 |= (0x1<<4);//通常pin4用1左移4位进行或运算
        }else if(userCmd == 0){
                printk("set 0\n");
                *GPCLR0 |= (0x1<<4);
        }else{
                printk("undo\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); //2. 创建设备号
        ret = register_chrdev(major , module_name, &pin4_fops); //3.注册驱动,告诉内核,把这个驱动加入到内核的链表中

        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(0x3F200000,4);//物理地址转换成虚拟地址,io口寄存器映射成普通内存单元进行访问
        GPSET0 = volatile(unsigned int *)ioremap(0x3F20001C,4);
        GPCLR0 = volatile(unsigned int *)ioremap(0x3F200028,4);
        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);
module_exit(pin4_drv_exit);
MODULE_LICENSE("GPL v2");



上层测试代码

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>


int main()
{
        int fd;
        int cmd;        
        fd = open("/dev/pin4",O_RDWR);
        if(fd < 0){
                printf("open failed\n");
                perror("error");
        }
        else{
                printf("open success\n");
        }
        printf("input commnd 0/1:\n0:set pin4 low\n1:set pin4 high\n");
        scanf("%d",&cmd);
        write(fd,&cmd,4);
        close(fd);
        return 0;
}

然后编译后传到树莓派参考这篇文章

运行

操控树莓派IO口的驱动代码编写

dmesg查看

操控树莓派IO口的驱动代码编写

gpio readall查看

操控树莓派IO口的驱动代码编写
自此操控树莓派pin4 IO口的驱动代码编写完成。如有错误之处,请帮忙指出改正。

本文地址:https://blog.csdn.net/weixin_49817112/article/details/108849996