当前位置: 移动技术网 > 科技>人工智能>嵌入式 > 操控树莓派IO口的驱动代码编写

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

2020年10月10日  | 移动技术网科技  | 我要评论
操控树莓派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

在这里插入图片描述
此处指令看到的型号不是树莓派cpu真的型号,其真正型号应该是BCM2837,也就是IO在物理地址上的基址应该是0x3F000000。

在这里插入图片描述
在这里插入图片描述

通过查看芯片手册发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) //真实驱动入口
在这里插入图片描述
如此就实现了物理地址转化成虚拟地址。(注:退出驱动时,用iounmap(*GP)函数解除地址映射);

此处以pin4引脚为例,要把pin4引脚设置为输出引脚,根据芯片手册内容需要配置GPFSEL0的14-12位(位置由0开始)为001。
在这里插入图片描述
但要注意的是在改变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;
}

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

运行

在这里插入图片描述

dmesg查看

在这里插入图片描述

gpio readall查看

在这里插入图片描述
自此操控树莓派pin4 IO口的驱动代码编写完成。如有错误之处,请帮忙指出改正。

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

如您对本文有疑问或者有任何想说的,请点击进行留言回复,万千网友为您解惑!

相关文章:

验证码:
移动技术网