文章目录
linux地址的概念
地址总线:是由CPU 或有DMA 能力的单元,用来沟通这些单元想要存取(读取/写入)电脑内存元件/地方的实体位址。一个 16位元 宽度的位址总线到达 2 的 16 次方 = 65536 = 64 KB 的内存位址;但现在很多计算机内存已经大于4G(windows XP x32位系统最大只能识别3.29G,所以要使用4G以上大内存就要用windows x64位系统)。
物理地址:是指处理器芯片发出,来进行地址空间寻址的地址,它与处理器地址引脚上发出的电信号相对应。
虚拟地址:程序所操作地址都是虚拟地址,虚拟地址是硬件MMU与软件内存管理结合的产物。由物理地址映射而来。
树莓派IO口寄存器
使用命令可知树莓派的cpu是BCM2835
cat /proc/cpuinfo
芯片手册中的GPIO pin的编号,对应的是管脚表的BCM
GPFSEL 0-5寄存器
功能:GPIO Function Select 功能选择
数量:6个
GPFSEL 0-4:负责50个GPIO pins
GPFSEL 5:负责GPIO PIN50 -53
GPSET 0-1寄存器
功能:设置高电平,赋值1时开启寄存器,0不作用。复位后为0
数量:2个
GPCLR 0-1寄存器
功能:给输出置零,赋值1时开启寄存器,0不作用。复位后为0
数量:2个
其他的寄存器也是差不多这样,这里就不一 一介绍了。详细可看芯片手册
编程所操作的虚拟地址
树莓派官网给出的GPIO口的物理地址
我们发现 GPFSEL0寄存器ⅤC CPU总线地址是0x7E200000,相对基址偏移0x00200000那么ARM物理地址也是偏移这么多,其ARM 物理地址是0x3f200000=0x3f000000+0x00200000。同理, GPSET0的ARM物理地址是0x320001C。我们在嵌入式 Linux中,可以使mmap函数或者 loremap函数将这两个ARM物理地址映射成虚拟地址,就等同于直接操作GPIO硬件地址了。
代码编写
驱动代码
#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 *pin26_class;
static struct device *pin26_class_dev;
static dev_t devno; //设备号
static int major =231; //主设备号
static int minor =0; //次设备号
static char *module_name="pin26"; //模块名
volatile unsigned int* GPFSEL2 = NULL;
volatile unsigned int* GPSET0 = NULL;
volatile unsigned int* GPCLR0 = NULL;
//led_open函数
static int pin26_open(struct inode *inode,struct file *file)
{
printk("pin26_open\n"); //内核的打印函数和printf类似
printk("da dui zhang hen shuai\n");
*GPFSEL2 &= ~(0x110 << 18);
*GPFSEL2 |= (0x01 << 18);
return 0;
}
//led_write函数
static ssize_t pin26_write(struct file *file1,const char __user *buf,size_t count, loff_t *ppos)
{ char cmd;
printk("pin26_write\n");
copy_from_user(&cmd,buf,count);
if(cmd == '1')
*GPSET0 |= (0x01 << 26);
if(cmd == '0')
*GPCLR0 |= (0x01 << 26);
return 0;
}
static struct file_operations pin26_fops = { //这种初始化方式只在linux可用,keil中就不行
.owner = THIS_MODULE,
.open = pin26_open,
.write = pin26_write,
};
int __init pin26_drv_init(void)
{
int ret;
devno = MKDEV(major,minor); //创建设备号
ret = register_chrdev(major, module_name,&pin26_fops); //注册驱动 告诉内核,把这个驱动加入到内核驱动的链表中
pin26_class=class_create(THIS_MODULE,"myfirstdemo");
pin26_class_dev =device_create(pin26_class,NULL,devno,NULL,module_name); //创建设备文件
GPFSEL2=(volatile unsigned int*)ioremap(0x3F200008,4);
GPSET0=(volatile unsigned int*)ioremap(0x3F20001C,4);
GPCLR0=(volatile unsigned int*)ioremap(0x3F200028,4);
return 0;
}
上层代码
#include<stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
void main(){
int fd;
char cmd;
fd=open("/dev/pin26",O_RDWR);
if(fd < 0){
printf("open failed\n");
perror("why");
}
printf("请输入0/1:电平0/1\n");
scanf("%c",&cmd);
if(cmd == '1' | cmd == '0' ){
write(fd,&cmd,1);
printf("cmd:%c\n",cmd);
}
else
printf("undo\n");
}
~
主要函数介绍:
ioremap
取消映射iounmap
功能:将一个IO地址空间映射到内核的虚拟地址空间上去,便于访问.
参数
void * __ioremap(unsigned long phys_addr, unsigned long size, unsigned long flags)
入口: phys_addr:要映射的起始的IO地址;
size:要映射的空间的大小;
flags:要映射的IO空间的和权限有关的标志;
返回值:映射后的虚拟地址
copy_from_user
copy_to_user,是一种数据程序。 作用:从内核区中读取数据到用户区功能:用于将用户空间的数据传送到内核空间。
原型:unsigned long copy_from_user(void * to, const void __user * from, unsigned long n)
第一个参数to:是内核空间的数据目标地址指针,
第二个参数from:是用户空间的数据源地址指针,
第三个参数n:是数据的长度。
返回值:如果数据拷贝成功,则返回零;否则,返回没有拷贝成功的数据字节数。
运行结果
输入0时
输入1时