树莓派学习——博通BCM2835芯片手册导读、IO口驱动代码调试和测试学习

一、树莓派寄存器介绍:

在这里插入图片描述

  • GPFSEL0 GPIO Function Select 0: 功能选择 输入/输出

  • GPSET0 GPIO Pin Output Set 0 : 输出0

  • GPSET1 GPIO Pin Output Set 1 : 输出1

  • 0 = No effect

  • 1 = Set GPIO pin n

  • GPCLR0 GPIO Pin Output Clear 0: 清零

  • 0 = No effect

  • 1 = Clear GPIO pin n

  • GPCLR1 GPIO Pin Output Clear 1 :清1

  • 每个寄存器都是32位的

在这里插入图片描述

举个例子,例如:我们把引脚5配置位输出引脚

FSEL5 17-15 001 我们把5引脚的17-15配置成001 GPIO Pin 5 is an output

注意:我们配置的底层引脚对应得是BCM

寄存器第0组位 FESL0–9

寄存器第1组位 FSEL10–19

在这里插入图片描述

具体的引脚信息可以通过官网查询

树莓派引脚
在这里插入图片描述

二、寄存器的地址问题

我们在编写驱动程序的时候,IO空间的起始地址是 0x3f000000 ,加上GPIO的偏移量 0x2000000 ,所以GPIO的物理地址应该是从 0x3f200000 开始的,然后在这个基础上进行Linux系统的MMU内存虚拟化管理,映射到虚拟地址上。

在这里插入图片描述

该图的尾部偏移是对的根据GPIO的物理地址0x3f200000可以知道:

  • GPFSEL0 0x3f200000

  • GPSET0 0x3f20001c

  • GPCLR0 0x3f200028

补充:这里我们的地址是物理地址,是操作不了的,所以需要转换成 虚拟地址 ,来进行操作

void __iomem * __ioremap(unsigned long phys_addr, size_t size, unsigned long flags);

ioremap宏定义在asm/io.h内:

#define ioremap(cookie,size)           __ioremap(cookie,size,0)

参数:

  • phys_addr:要映射的起始的IO地址

  • size:要映射的空间的大小

  • flags:要映射的IO空间和权限有关的标志

  • 该函数返回映射后的内核虚拟地址(3G-4G). 接着便可以通过读写该返回的内核虚拟地址去访问之这段I/O内存资源

三、以 pin5 引脚为例子:

pin5drive.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 *pin5_class;
static struct device *pin5_class_dev;

static dev_t devno;     // 设备号
static int major = 233; // 主设备号
static int minor = 1;   //次设备号
static char *module_name = "pin5"; //模块名

volatile unsigned int* GPFSEL0 = NULL; // volatile不会因编译器的优化而省略,每次直接读值
volatile unsigned int* GPSET0  = NULL;
volatile unsigned int* GPCLR0  = NULL;

static int pin5_read (struct file *file, char __user *buf, size_t count, loff_t *ppos)
{
        printk("pin5_read\n"); // 内核的打印函数
        return 0;
}

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


        // 配置pin5引脚为输出引脚 ,需要把 bit 15~17 配置成001
        *GPFSEL0 &= ~(0x6 << 15); // 0x6 0110 左移15位,取反后为1001,把bit 16、17配置成 0 
        *GPFSEL0 |=  (0x1 << 15); // 把 第 15 位bit 配置成 1, 15~17为001输出 / 000输入,其中一工为0~31位


        return 0;
}

//open_write函数
static ssize_t pin5_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos)
{
        int cmd;

        printk("pin5_write\n");

        //      获取上层write函数的值

        copy_from_user(&cmd,buf,count); // 从上层获取函数的值第一个参数是一个char类型的指针const char __user *buf,用int也行

        //      根据值来设定操作 io 口,高电频还是低电频
        printk("get value\n");

        if(cmd == 1)
        {
                *GPSET0 |= 0x1 << 5; // 把第 0 组 第5引脚置为 1 
        }
        else if(cmd == 0)
        {
                *GPCLR0 |= 0x1 << 5;    // 第 5位置1 让清零寄存器把第 5 引脚清零
        }
        else
        {
                printk("undo\n");
        }

        return 0;
}
static struct file_operations pin5_fops = {
        .owner = THIS_MODULE,
        .open  = pin5_open,
        .write = pin5_write,
        .read  = pin5_read,
};


int __init pin5_drv_init(void) //真实驱动入口
{
        int ret;

        printk("insmod driver pin5 success\n");

        devno = MKDEV(major,minor); //2. 创建设备号
        ret = register_chrdev(major , module_name, &pin5_fops); //3.注册驱动,告诉内核,把这个驱动加入到内核的链表中

        pin5_class = class_create( THIS_MODULE, "myfirstdemo" ); // 让代码在dev自动生成设备
        pin5_class_dev = device_create( pin5_class , NULL , devno , NULL ,module_name ); //创建设备文件

        GPFSEL0 = ( volatile unsigned int *)ioremap(0x3f200000,4);//    第一个参数为物理地址,第二个参数为映射的大小
        GPSET0  = ( volatile unsigned int *)ioremap(0x3f20001C,4);//    一般寄存器32位,4个字节 
        GPCLR0  = ( volatile unsigned int *)ioremap(0x3f200028,4);//    把物理地址 转换为 虚拟地址
        //ioremap由于返回值是void*型需要强制转换  void __iomem * __ioremap(unsigned long phys_addr, size_t size, unsigned long flags)   

        return 0;
}

void __exit pin5_drv_exit(void)
{
        iounmap(GPFSEL0);       //iounmap函数用于取消ioremap()所做的映射
        iounmap(GPSET0);
        iounmap(GPCLR0);

        device_destroy(pin5_class,devno);
        class_destroy(pin5_class);
        unregister_chrdev( major, module_name);// 卸载驱动

}

module_init(pin5_drv_init); // 驱动入口,驱动安装的时候,会调用这个宏
module_exit(pin5_drv_exit);
MODULE_LICENSE("GPL v2");

应用层代码测试 pin5test.c

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

int main()
{
        int fd;
        int usecmd;
        int data;

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

        if(fd < 0)
        {
                printf("open file failed\n");
                perror("why : ");
        }
        else
        {
                printf("open success\n");
        }
        printf("请输入你的指令 : 1/0 , 1: pin5 HIGH , 0: pin5 LOW\n");
        scanf("%d",&usecmd);

        if(usecmd == 1)
        {
                data = 1;
                printf("data = %d\n",data);
        }
        else if(usecmd == 0)
        {
                data = 0;
                printf("data = %d\n",data);
        }
                fd = write(fd, &data,1);

}

(2)编译驱动代码 pin5drive.c ,放入源码树目录的 /drivers/char 目录下

1、先用 vi Makefile 打开,添加相应pin5 驱动说明

在这里插入图片描述

2、编译生成 .ko 文件

ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- KERNEL=kernel7 make modules

在这里插入图片描述

3、把ko文件发送到树莓派上

scp pin5dirve.ko pi@192.168.3.43:/home/pi

4、编译测试代码 pin5test.c 并将其发送到树莓派

arm-linux-gnueabihf-gcc pin5test.c -o pin5test
scp pin5test pi@192.168.3.43:/home/pi

在这里插入图片描述

5、安装 pin5 驱动

sudo insmod pin5drive.ko // 安装驱动,要超级权限

此时 /dev 目录下会有 pin5 :

在这里插入图片描述

lsmod // 可查看驱动是否安装

在这里插入图片描述

6、此时需要给pin5驱动 加权限,这样应用层才能看到测试信息

sudo chmod 666 /dev/pin5   // 666 为可读可写可操作权限

端口号,设备号都有体现

在这里插入图片描述

7、运行测试代码 :

./pin5test

刚开始查看树莓派端口情况:

gpio readall

在这里插入图片描述

以输入1为例子:

在这里插入图片描述

在这里插入图片描述

此时驱动被修改成功为输出端口,为OUT

©️2020 CSDN 皮肤主题: 书香水墨 设计师:CSDN官方博客 返回首页