树莓派驱动开发编写调试(2)

总线地址

32位的win7为什么只能识别4g的内存 即使装了8g的内存条。64位才可以识别8g。2的32次方bit bit->kbit->mbit->gbit(每次除以1024)
地址总线:
属于一种电脑总线一部分,是由CPU 或有DMA 能力的单元,用来沟通这些单元想要存取(读取/写入)电脑内存元件/地方的实体位址。

物理地址

硬件实际地址或者绝对地址
程序太大需要虚拟地址
在存储器里以字节为单位存储信息,为正确地存放或取得信息,每一个字节单元给以一个唯一的存储器地址,称为物理地址(Physical Address),又叫实际地址或绝对地址

虚拟地址

  • 1.逻辑地址(基于算法的地址,软件层面的地址)称为虚拟地址
    2. 程序在磁盘超过1G,就用到虚拟地址,一点一点的拿来运行(没有虚拟地址就无法运行,例如51,32)
    3. 物理地址通过页表ARM MMU映射成虚拟地址

虚拟地址通过cpu的转换才能对应到物理地址,而且每次运行程序时,操作系统都会从新安排虚拟地址和物理内存的对应关系。
虚拟地址通过设定的内存映射机制找到对应的物理内存

BCM2835

是树莓派3b 的cpu型号 它是ARM-cotexA53架构
芯片手册树莓派

  • 1.BCM2835 树莓派3b CPU型号,是ARM-cotexA53架构 //2440 2410 CPU型号 是ARM9架构
    2.树莓派是32位系统,1G 内存,只能识别949M
    3.总线地址4G,物理地址1G,虚拟地址4G
    映射页表的了解:《unix设计与实现》

手册GPIO章节

寄存器

在这里插入图片描述
在这里插入图片描述
Address:总线地址 Field name:寄存器名字 Description:寄存器功能

  • GPFSEL0 GPIO Function Select 0: 功能选择 输入/输出
  • GPSET0 GPIO Pin Output Set0 : 输出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位的

GPFSEL0 GPIO Function Select 0 功能选择(输入输出)

  • volatile含义
    保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的。(实现可见性)
    禁止进行指令重排序。(实现有序性) volatile 只能保证对单次读/写的原子性。i++ 这种操作不能保证原子性

  • ioremap函数
    ioremap宏定义在asm/io.h内:
    #define ioremap(cookie,size) __ioremap(cookie,size,0)
    __ioremap函数原型为(arm/mm/ioremap.c):
    void __iomem * __ioremap(unsigned long phys_addr, size_t size,
    unsigned long flags);
    参数:
    phys_addr:要映射的起始的IO地址
    size:要映射的空间的大小,一般是4,一个寄存器大小是4个字节,32位
    flags:要映射的IO空间和权限有关的标志
    该函数返回映射后的内核虚拟地址(3G-4G). 接着便可以通过读写该返回的内核虚拟地址去访问之这段I/O内存资源。

代码编写

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

定寄存器

volatile unsigned int* GPFSEL0 = NULL;
volatile unsigned int* GPSET0  = NULL;
volatile unsigned int* GPCLR0  = NULL;
//指针指向虚拟地址,0x3f200000是物理地址,ioremap把物理地址映射成虚拟地址
//物理地址:3f000000加偏移量200000
GPFSEL0 = (volatile unsigned int *)ioremap(0x3f200000,4);
GPSET0  = (volatile unsigned int *)ioremap(0x3f20001C,4);
GPCLR0  = (volatile unsigned int *)ioremap(0x3f200028,4);
///
  • copy_from_user函数的目的是从用户空间拷贝数据到内核空间,失败返回没有被拷贝的字节数,成功返回0.
    copy_from_user(void *to, const void __user *from, unsigned long n)
    1. *to 将数据拷贝到内核的地址
    2. *from 需要拷贝数据的地址
    3. n 拷贝数据的长度(字节)
    4. 也就是将@form地址中的数据拷贝到@to地址中去,拷贝长度是n

底层驱动

#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 *pin6_class;
static struct device *pin6_class_dev;

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

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

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

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


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


        return 0;
}

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

        printk("pin6_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 << 6; // 把第 0 组 第6引脚置为 1 
        }
        else if(cmd == 0)
        {
    
    
                *GPCLR0 |= 0x1 << 6;    // 第 6位置1 让清零寄存器把第 6 引脚清零
        }
        else
        {
    
    
                printk("undo\n");
        }

        return 0;
}
static struct file_operations pin6_fops = {
    
    
        .owner = THIS_MODULE,
        .open  = pin6_open,
        .write = pin6_write,
        .read  = pin6_read,
};


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

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

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

        pin6_class = class_create( THIS_MODULE, "myfirstdemo" ); // 让代码在dev自动生成设备
        pin6_class_dev = device_create( pin6_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 pin6_drv_exit(void)
{
    
    
        iounmap(GPFSEL0);       //iounmap函数用于取消ioremap()所做的映射
        iounmap(GPSET0);
        iounmap(GPCLR0);

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

}

module_init(pin6_drv_init); // 驱动入口,驱动安装的时候,会调用这个宏
module_exit(pin6_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;
        int data;

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

        if(fd < 0)
        {
    
    
                printf("open file failed\n");
                perror("error reason : ");
        }

        printf("please enter your messon 1->on | 2->off  \n");
        scanf("%d",&cmd);

        if(cmd == 1)
        {
    
    
                data = 1;
        }
        else if(cmd == 0)
        {
    
    
                data = 0;
        }
                fd = write(fd, &data,1);
}


烧写过程

过程记录: 内核驱动编写调试(1).

过程大致为:
编译pin6driver.c,并放入放入源码树目录的 /drivers/char
用 vi Makefile 打开,添加相应pin6驱动,obj-m
编译生成 .ko 文件-》ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- KERNEL=kernel7 make modules
可以的话这里用多线程编译:ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- KERNEL=kernel7 make -j8 modules
把ko文件发送到树莓派上scp pin6dirve.ko [email protected]:/home/pi
编译测试代码 pin6test.c 并将其发送到树莓派

arm-linux-gnueabihf-gcc pin6test.c -o pin6test
scp pin6test pi@192.168.xxx.xx:/home/pi

安装 pin6 驱动sudo insmod pin6drive.ko
lsmod产看是否成功安装驱动
输入sudo chmod 666 /dev/pin6给与访问权限
配置完成,运行test,然后可以用gpio readall进行查看是否成功驱动

猜你喜欢

转载自blog.csdn.net/weixin_45824920/article/details/114715739