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

发布于:2022-12-14 ⋅ 阅读:(1013) ⋅ 点赞:(0)

1.BCM2835芯片手册

 

 BCM2835是树莓派3B CPU的型号,是ARM-cotexA53架构cpu Bus是地址总线,00000000-FFFFFFFF是CPU寻址的范围(4G)DMA是高速拷贝单元,CPU可以发动DMA直接让DMA进行数据拷贝,直接内存访问单元。物理地址(PA)1G虚拟地址(VA)4G若程序大于物理地址1G,是不是就跑不了了,不是的,它有个MMU的单元,把物理地址映射成虚拟地址,我们操作的代码基本上都是在虚拟地址。

地址总线 简单来说就是:cpu能够访问内存的范围。

CPU寻找外部的内存单元靠的是地址总线传输的数据

GPIO有41个寄存器,所有访问都是32位的。

Description是寄存器的功能描述。

GPFSEL0(寄存器名)GPIO Function Select 0(功能选择:输入或输出);

GPSET0 (寄存器名)GPIO Pin Output Set 0(将IO口置0);

GPSET1(寄存器名)GPIO Pin Output Set 1(将IO口置1);

GPCLR0(寄存器名)GPIO Pin Output Clear 0 (清0)

 下图的地址是:总线地址(并不是真正的物理地址)

 下图给出第九个引脚的功能选择示例,对寄存器的29-27进行配置,进而设置相应的功能。根据图片下方的register 0表示0~9使用的是register 0这个寄存器。

 文档中的功能说的非常清楚了,引脚输出是001,输入是000等等,我们的寄存器都是分组的,寄存器第0组是FESL0–9

 上面的FSEL4代表的是树莓派底层的4引脚(BCM),而不是我们在调用树莓派库时常用的GPIO脚

GPSETn寄存器为了使IO口设置为1,set4位设置第四个引脚,也就是寄存器的第四位要置为1。

 GPCLRn是清零功能寄存器。

 2.设置寄存器的地址

//这三行是设置寄存器的地址
volatileunsignedint*GPFSEL0=volatile(unsignedint*)0x3f200000;
volatileunsignedint*GPSET0=volatile(unsignedint*)0x3f20001C;
volatileunsignedint*GPCLR0=volatile(unsignedint*)0x3f200028;
//volatile的作用是作为指令关键字,确保本条指令不会因编译器的优化而省略,且要求每次直接读值

但是这样写是有问题的,我们上面讲到了在内核里代码和上层代码访问的是虚拟地址(VA),而现在设置的是物理地址,所以必须把物理地址转换成虚拟地址

我们先把地址初始为NULL

在初始化int __init pin4_drv_init(void) //真实的驱动入口 里赋值。

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

我们在编写驱动程序的时候,IO空间的起始地址是0x3f000000,加上GPIO的偏移量0x2000000,所以GPIO的物理地址应该是从0x3f200000开始的

 3.配置PIN4引脚为输出引脚

 配置pin4引脚为输出引脚 bit 12-14 要配置成001

//配置pin4引脚为输出引脚bit12-14配置成001
*GPFSEL0&=~(0x6<<12);//把bit13、bit14置为0
//0x6是110<<12左移12位 ~取反 &按位与
*GPFSEL0|=~(0x1<<12);//把12置为1|按位或

 4. 获取上层write函数的值,根据值来操作io口,高电平或者低电平

copy_form_user(char *buf , user_buf , count)获取上层write函数的值

copy_from_user(&userCmd, buf, count); //将buf里的数据拷贝到userCmd里去,拷贝长度是count

    //根据值来操作IO口,高电平 或者低电平
    if (userCmd == 1)
    {
        *GPSET0 |= (0x1 << 4);
        //通过给 设置(也是置1的意思)寄存器 写1,将bit4设置为高电平
        //第几个引脚就是左移多少位
        printk("SET 1\n");
    }
    else if (userCmd == 0)
    {
        *GPCLR0 |= (0x1 << 4);
        //通过给 清0寄存器 写1,将bit4设置为高电平
        //第几个引脚就是左移多少位
        printk("SET 0\n");
    }
    else
    {
        printk("usrCmd is wrong!\n");
    }

 


IO口驱动代码 pin4driver2.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  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 确保本条指令不会因编译器的优化而省略,且要求每次直接读值
//编译器有时会觉得设置的这个地址不太好,会重新给你分配一个地址 volatile的作用就体现出来了
volatile unsigned int *GPFSEL0 = NULL;
volatile unsigned int *GPSET0 = NULL;
volatile unsigned int *GPCLR0 = NULL;

static int pin4_read(struct file *file1, char __user *buf, size_t size, loff_t *ppos)
{
    printk("pin4_read\n");
    return 0;
}

// led_open函数
static int pin4_open(struct inode *inode, struct file *file)
{
    printk("pin4_open\n"); //内核的打印函数和printf类似
    //配置PIN4引脚为输出引脚

    //因为要配置成输出型引脚,所以要将其14~12位配置为001
    *GPFSEL0 &= ~(0x6 << 12);
    //因为&1结果必是1,取反就是0
    //所以将二进制6(110)左移12位与原数进行&,则结果必是11*,取反则是00*
    *GPFSEL0 |= (0x1 << 12);
    //再将第12位置为1即可
    //所以直接将二进制1 | 原数即可

    return 0;
}

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

    //获取上层write函数的值
    copy_from_user(&userCmd, buf, count); //将buf里的数据拷贝到userCmd里去,拷贝长度是count

    //根据值来操作IO口,高电平 或者低电平
    if (userCmd == 1)
    {
        *GPSET0 |= (0x1 << 4);
        //通过给 设置(也是置1的意思)寄存器 写1,将bit4设置为高电平
        //第几个引脚就是左移多少位
        printk("SET 1\n");
    }
    else if (userCmd == 0)
    {
        *GPCLR0 |= (0x1 << 4);
        //通过给 清0寄存器 写1,将bit4设置为高电平
        //第几个引脚就是左移多少位
        printk("SET 0\n");
    }
    else
    {
        printk("usrCmd is wrong!\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;
    printk("insmod driver pin4 success\n");                //代表驱动装载成功
    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); // 1.入口,内核加载该驱动的时候,这个宏会被调用
module_exit(pin4_drv_exit);
MODULE_LICENSE("GPL v2");

上层测试代码pin4test.c:

#include<stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
int main()

{
        int fd;
        int cmd;
        int data;

        fd = open("/dev/pin4", O_RDWR);

        if(fd < 0){
                printf("open failed\n");
                perror("reson:");
        }else{
                printf("open success\n");
        }

        printf("input commnd: 1/0\n1:set pin4 high \n0:set pin4 low\n");
        scanf("%d", &cmd);
        if(cmd == 1){
                data = 1;
        }
        if(cmd == 0){
                data = 0;
        }
        printf("data = %d\n",data);
        write(fd, &data, 1);

        return 0;
}

5.IO口驱动代码编译

打开ubuntu:

首先在 /home/lit/SYSTEM/linux-rpi-4.14.y/drivers/char 目录下保存pin4driver2.c

然后在系统目录/home/lit/SYSTEM/linux-rpi-4.14.y下使用指令:ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- KERNEL=kernel7 make modules对驱动模块进行编译生成.ko文件

然后将编译后的驱动用scp指令发送到树莓派

然后再将上层代码进行编译arm-linux-gnueabihf-gcc pin4test.c -o realtest,同样将测试代码用scp指令传到树莓派

打开SecureCRT:

在树莓派上面使用指令:insmod pin4drive.ko进行加载驱动(然后lsmod即可查看到该驱动,之前是没有的)

然后使用指令:sudo chmod 666 /dev/pin4给予pin4这个设备可访问权限

./pin4test 运行测试代码

 dmesg查看内核打印信息:

 先 gpio readall 可看到4引脚是0低电平,并且能看到它为out输出模式

 输入1后可以看到4引脚成了1高电平

 


网站公告

今日签到

点亮在社区的每一天
去签到