32位的win7为什么只能识别4g的内存 即使装了8g的内存条。64位才可以识别8g。2的32次方bit bit->kbit->mbit->gbit(每次除以1024)
地址总线:
属于一种电脑总线一部分,是由CPU 或有DMA 能力的单元,用来沟通这些单元想要存取(读取/写入)电脑内存元件/地方的实体位址。
硬件实际地址或者绝对地址
程序太大需要虚拟地址
在存储器里以字节为单位存储信息,为正确地存放或取得信息,每一个字节单元给以一个唯一的存储器地址,称为物理地址(Physical Address),又叫实际地址或绝对地址
虚拟地址通过cpu的转换才能对应到物理地址,而且每次运行程序时,操作系统都会从新安排虚拟地址和物理内存的对应关系。
虚拟地址通过设定的内存映射机制找到对应的物理内存
是树莓派3b 的cpu型号 它是ARM-cotexA53架构
树莓派
Address:总线地址 Field name:寄存器名字 Description:寄存器功能
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);
///
#include // file_operations声明
#include // module_init module_exit声明
#include // __init __exit 宏定义声明
#include // class device声明
#include // copy_from_user的头文件
#include // 设备号 dev_t 类型声明
#include // 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
#include
#include
#include
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
进行查看是否成功驱动