Linux 设备驱动开发(二)---I/O口操控

一、知识储备

总线地址:地址总线 (Address Bus;又称:位址总线) 属于一种电脑总线 (一部份),是由CPU 或有DMA 能力的单元,用来沟通这些单元想要存取(读取/写入)电脑内存元件/地方的实体位址。简单来说就是CPU能够访问内存的范围。
比如装了32位的win7的系统,明明内存条8G,可是系统只识别了3.8G ,装了64位系统才能识别8G,因为 32位操作系统只能表示/访问2的32次方=4294967296 个地址,每个地址访问一个字节。所以4294967296(即2的32次方)个地址访问2的32次方个字节,即4 GB。
4,294,967,296 Byte=4,194,304 KB
4,194,304 KB=4,096 MB
4,096 MB=4 GB

物理地址:在存储器里以字节为单位存储信息,为正确地存放或取得信息,每一个字节单元给以一个唯一的存储器地址,称为物理地址(Physical Address),又叫实际地址或绝对地址。

虚拟地址:程序访问存储器所使用的逻辑地址称为虚拟地址,与实地址模式下的分段地址类似,虚拟地址也可以写为“段:偏移量”的形式,这里的段是指段选择器。
在树莓派Linux操作系统中,物理地址通过页表(MMU)管理映射成虚拟地址。
Linux 设备驱动开发(二)---I/O口操控_第1张图片

volatile关键字

volatile是一种类型修饰符,提醒编译器他后面所定义的变量随时都有可能改变,因此编译后的程序每次需要存储或读取这个变量的时候,都会直接从变量地址中(内存中)读取数据。如果没有volatile关键字,则编译器可能优化读取和存储,可能暂时使用寄存器(缓存)中的值,如果这个变量由别的程序更新,将出现不一致。存储器映射的硬件寄存器通常也要加volatile说明,因为每次对它的读写可能不同;

二、内核函数

1、ioremap函数

将一个IO物理地址空间映射到内核的虚拟地址空间上去,便于访问
#include
void * ioremap(unsigned long phys_addr, unsigned long size)
参数:
phys_addr:要映射的起始的IO物理地址
size:要映射的空间的大小,32位为4字节。
返回:映射后的虚拟地址

2、iounmap函数

解除物理地址的映射
#include
void iounmap(unsigned long virtual_addr)
参数:
virtual_addr:映射后的虚拟地址

3、copy_from_user函数

#include
static inline unsigned long copy_from_user(void *to, const void __user *from, unsigned long n)
参数:
to:内核空间的指针
from:用户空间指针
n:表示从用户空间想内核空间拷贝数据的字节数。
返回:如果成功执行拷贝操作,则返回0,否则返回还没有完成拷贝的字节数。

三、驱动代码编写

树莓派I/O口操控与STM32相似,也是通过寄存器操作来实现。
(参考BCM2835芯片手册文件 ) 点击下载(提取码:1111)
1、与I/O口操控相关的寄存器:(寄存器在总线上的地址从 0x7E200000 开始, 对应到物理内存上就是 0x3F200000)
(1)GPFSELn(物理地址:0x3F200000) 控制I/O口模式:输入/输出
Linux 设备驱动开发(二)---I/O口操控_第2张图片
pin4 I/O口模式控制对应于GPFSEL0中的第12-14位,且设置为001为输出模式。

(2)GPSETn(物理地址:0x3F20001C) I/O口置高电平:位写1有效,写0无效
Linux 设备驱动开发(二)---I/O口操控_第3张图片
pin4置高电平控制对应于GPSET0的第4位,且置为1。

(3)GPCLRn(物理地址:0x3F200028) I/O口置低电平:位写1有效,写0无效
Linux 设备驱动开发(二)---I/O口操控_第4张图片
pin4置低电平控制对应于GPCLR0的第4位,且置为1。

基于上一节的内核驱动基本框架,编写了pin4的I/O口驱动程序,简单控制pin4输出高/低电平。在框架中添加了以下内容:
(1)定义了三个寄存器指针,通过ioremap函数将寄存器的物理地址映射成虚拟地址,并分别指向GPFSEL0、GPSET0、GPCLR0寄存器的虚拟地址。
(2)在pin4_open函数中添加了GPIO初始化,将pin4设置为输出模式。
(3)在pin4_write函数中,添加了copy_from_user函数,拷贝从用户态发送的数据,即用户态write函数中的buf,通过判断语句,选择控制I/O口输出高/低电平。
(4)在驱动出口函数中,解除寄存器物理地址的映射。

//文件pin4driver.c
#include //file_operation声明
#include //module_init module_exit声明
#include //_init _exit声明
#include //class device 声明
#include //copy_from_user的头文件
#include //设备号 dev_t类型声明
#include //ioremap iounmap的头文件

volatile unsigned int *GPFSEL0=NULL;
volatile unsigned int *GPSET0=NULL;
volatile unsigned int *GPCLR0=NULL;

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";//模块名

static int pin4_open(struct inode *inode, struct file *file)
{
     
	printk("pin4_open\n");//内核的打印函数printk,和printf函数类似
	//GPIO初始化:配置pin4引脚为输出模式
	*GPFSEL0&=~(0x06<<12);
	*GPFSEL0|=(0x01<<12);
	return 0;
}

static ssize_t pin4_write(struct file *file, const char __user * buf, size_t count, loff_t *ppos)
{
     
	static int usrcmd;
	printk("pin4_write\n");
	copy_from_user(&usrcmd,buf,count);//将从用户态write函数发送的数据拷贝到usrcmd
	if(usrcmd==1)
	{
     
		printk("set 1\n");
		//pin4拉高电平
		*GPSET0|=0x01<<4;
	}
	else if(usrcmd==0)
	{
     
		printk("set 0\n");
		//pin4拉低电平
		*GPCLR0|=0x01<<4;
	}
	else
	{
     
		printk("undo\n");
	}
	return 0;
}
static struct file_operations pin4_flops = 
{
     
	.owner  =   THIS_MODULE,
	.open   =   pin4_open,
	.write  =   pin4_write,
};
int __init pin4_drv_init(void)//真实驱动入口
{
     
	devno=MKDEV(major,minor);//创建设备号
	register_chrdev(major, module_name, &pin4_flops);//注册字符设备,并告诉内核把驱动加入到驱动链表中
	pin4_class=class_create(THIS_MODULE,"myfirstdemo");//创建类
	pin4_class_dev=device_create(pin4_class,NULL,devno,NULL,module_name);//创建设备文件,让代码在dev下自动生成设备
	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");//GPL v2规范
//文件pin4test.c
#include 
#include 
#include 
#include 
#include 
#include 
#include 
int main(){
     
    int fd; 
	int cmd;
    fd=open("/dev/pin4",O_RDWR);
	if(fd<0)
	{
     
		printf("load fail!\n");
		perror("why");
	}
	else
	{
     
		printf("load success!\n");
	}
	printf("please input 0/1\n");
	scanf("%d",&cmd);
	printf("cmd=%d\n",cmd);
	write(fd,&cmd,1);
    close(fd);
	return 0;
}

四、驱动编译及其他操作

1、进入/home/sh/Desktop/learnPi/linux-rpi-4.14.y/drivers/char文件夹
cd /home/sh/Desktop/learnPi/linux-rpi-4.14.y/drivers/char
2、在当前文件夹的Makefile文件添加下面的命令
vi Makefile 
obj-m                           += pin4driver.o
3、将pin4driver.c放到/home/sh/Desktop/learnPi/linux-rpi-4.14.y/drivers/char字符设备驱动文件夹
cp ~/Desktop/pin4driver.c .
4、进入/home/sh/Desktop/learnPi/linux-rpi-4.14.y文件夹
cd /home/sh/Desktop/learnPi/linux-rpi-4.14.y
5、编译驱动模块
ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- KERNEL=kernel7 make modules
6、将编译完成的.ko文件通过scp发送到树莓派
scp drivers/char/pin4driver.ko pi@192.168.31.106:/home/pi/Desktop
7、加载驱动模块
sudo insmod pin4driver.ko
8、查看内核中的模块信息是否有pin4driver
lsmod 
9、给/dev/pin4添加权限
sudo chmod 666 /dev/pin4
10、编译pin4test.c输出为pin4test可执行文件
gcc pin4test.c -o pin4test
11、运行pin4test
./pin4test
load success!  //驱动文件打开成功
please input 0/1  //输入1或0,1为高电平,0为低电平
1  //输入1
12、查看所有I/O口的状态信息
gpio readall
13、查看打印信息
dmesg 

1、所有I/O口的状态信息(pin4对应BCM为4,Mode:Out为输出,V:1为高电平)
Linux 设备驱动开发(二)---I/O口操控_第5张图片
2、内核打印信息
在这里插入图片描述

你可能感兴趣的:(Linux学习笔记,linux,c语言)