驱动笔记(二)

写驱动目的是为了实现IO口的操作,实现自己的wiringPi库

微机总线地址物理地址虚拟地址介绍
地址总线 (Address Bus;又称:位址总线) 属于一种电脑总线 (一部份),是由CPU 或有DMA 能力的单元,用来沟通这些单元想要存取(读取/写入)电脑内存元件/地方的实体位址。

简而言之就是CPU能够访问内存的范围

现象:32位的win7系统,明明内存条8G,但是内存条显示3.8G,装了64位,才能识别到win7

32位能表示/访问4,294,967,296bit 也就是2的32次方

bit  4,294,967,296B
kbit   4,194,304KB
mbit  4,096M
gbit   4G
树莓派32位 1G内存

cat /proc/meminfo查看树莓派内存条大小
在这里插入图片描述
在存储器里以字节为单位存储信息,为正确地存放或取得信息,每一个字节单元给以一个唯一的存储器地址,称为物理地址(Physical Address),又叫实际地址或绝对地址。(硬盘中排列的地址)

虚拟地址是Windows程序时运行在386保护模式下,这样程序访问存储器所使用的逻辑地址(基于算法的地址(软件层面的地址,是个假地址))称为虚拟地址,与实地址模式下的分段地址类似,虚拟地址也可以写为“段:偏移量”的形式,这里的段是指段选择器。

如果程序在磁盘中超过了物理地址(树莓派1G),程序会跑不起来,都是一点一点取址运算,虚拟地址可以把1G映射成4G的物理地址

cat /proc/cpuinfo查看CPU型号
在这里插入图片描述
BCM2835是树莓派3bCPU型号,他是ARM-cotexA53架构
在这里插入图片描述
16进制FFFF FFFF 为十进制 4,294,967,295=4G
16进制4000 0000 为十进制 1,073,741,824=1G
图中MMU的单元把物理地址映射成虚拟地址,我们操控的都是虚拟地址,页表是虚拟地址物理地址映射中间的一个算法,设计完页表通过MMU执行。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述在这里插入图片描述
在这里插入图片描述

注意:第一张图中的address是总线地址

GPFSEL0   GPIO Function Select 0    功能选择(GPIO Function Select Registers)

14-12   001 = GPIO Pin 4 is an output 把寄存器bit12-bit14配置成001就为output
这的4引脚是底层(BCM编码)的4引脚不是树莓派(wiringPi编码)上的4引脚

GPSET0   GPIO Pin Output Set 0     输出0

0 = No effect
1 = Set GPIO pin n

GPCLR0   GPIO Pin Output Clear 0  清零

0 = No effect
1 = Clear GPIO pin n

volatile是C语言中的一个关键字。 将变量定义为volatile就表示告诉编译器这个变量可能会被竟想不到地改变,在这种情况下,编译器就不会去假设这个变量的值了,及优化器在用到这个变量是必须每次重新读取他的值。

volatile简而言之就是不让编译器去优化这个变量。

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

ioremap

#include <io.h>
void * __ioremap(unsigned long phys_addr, unsigned long size, unsigned long flags)
void *ioremap(unsigned long phys_addr, unsigned long size)

phys_addr:要映射的起始的IO地址
size:要映射的空间的大小
flags:要映射的IO空间的和权限有关的标志

pin4引脚是12-14位,我想让pin4为输出引脚那我就需要把12-14配置成001,不能写成*GPSET0 = 00000000000000000001000000000000;会出问题,这种配置方式会影响其他位的配置,怎么配置呢?看下面的按位与(都为1为1,否则为0)和按位或(有一个为1就是0,两个都没有就是0)操作。

*GPFSEL0 &= ~(0x6 << 12);//保证13,14位和其他位不变
*GPFSEL0 |= (0x1 << 12);//更改12位

上层代码

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

int main()
{
        int fd;
        int cmd;

        fd = open("/dev/pin4",O_RDWR);
        if(fd < 0){
                printf("open pin4 error!\n");
                perror("why:");
        }else{
                printf("open pin4 success!\n");
        }
        
		printf("input commnd :0/1 \n0:setpin4 low\n1:set pin4 high\n");
        scanf("%d",&cmd);
        
        //ssize_t write(int fd, const void *buf, size_t count);
        fd = write(fd,&cmd,4);

        return 0;
}

底层代码

#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 unsigned int* GPFSEL0 = NULL;
volatile unsigned int* GPSET0  = NULL;
volatile unsigned int* GPCLR0  = NULL;

//led_open函数
static int pin4_open(struct inode *inode,struct file *file)
{
    printk("pin4_open\n");  //内核的打印函数和printf类似
   
	//配置pin4引脚为输出引脚
    *GPFSEL0 &= ~(0x6 << 12);
    *GPFSEL0 |=  (0x1 << 12);
   
    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);
    //根据值来操作IO口,高电平或低电平
    if(userCmd == 1){
		printk("set 1\n");
		*GPSET0 |= 0x1 << 4;//这的4是指pin4
    }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,
};

int __init pin4_drv_init(void)   
{

    int ret;
		
	printk("insmod driver pin4 success\n");
    
    devno = MKDEV(major,minor);  //创建设备号
    ret   = register_chrdev(major, module_name,&pin4_fops);  //注册驱动  告诉内核,把这个驱动加入到内核驱动的链表中

    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");

实现效果
在这里插入图片描述
在这里插入图片描述在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

一只小阿大:)

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值