写驱动目的是为了实现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");
实现效果