基础简介
驱动概念:驱动就是对底层硬件设备的操作进行封装,并向上层提供函数接口。
linux系统将设备分类:字符设备、块设备、网络设备。
1.字符设备: 一个字节读写设备,不能随机读取设备内存中数据,需要按顺序读取。字符设备是面向流的设备,常见的字符设备有鼠标、键盘、串口、控制台和LED设备等。
2.块设备:任意位置读数据的设备。块设备包括硬盘、磁盘、U盘和SD卡等。
3.网络设备: 网络设备可以是硬件设备,如网卡; 但也可以是软件设备, 比如回环接口(lo)(网络接口负责发送和接收数据报文)。
文件名与设备号:主设备号用来表示一个特定的驱动程序。次设备号用来表示使用该驱动程序的各设备(同为LED灯的驱动(主设备号相同),包含代开和关闭两个字符设备驱动(次设备号不同))。设备号用来区分不同种类的设备和不同类型的设备驱动且插入到链表的位置(顺序)由设备号检索。文件名和设备号用来区分硬件对应的驱动。
地址:总线地址,物理地址,虚拟地址
1.总线地址: cpu能够访问的内存范围
2.物理地址:硬件实际地址或绝对地址,即硬盘上的排列地址
3.虚拟地址:逻辑地址(基于算法的地址,软件层面的地址,是假地址)称为虚拟地址。
虚拟地址的作用:通过分页机制将物理地址映射成比自己本身大的虚拟地址(1G的物理地址,映射成4G的虚拟地址,用来运行大于实际物理地址的程序)。
地址映射函数:
void * __ioremap(unsigned long phys_addr, unsigned long size, unsigned long flags)
phys_addr:要映射的起始的IO地址;
size:要映射的空间的大小;
flags:要映射的IO空间的和权限有关的标志;
功能: IO地址空间映射到内核的虚拟地址空间;
*void ioremap(unsigned long offset, unsigned long size);
offset:物理地址
size:要映射的空间的大小
返回值:页映射,返回虚拟地址
iounmap()函数,解除虚拟地址的映射;
如:iounmap(GPSETL0);
BM2835手册
地址映射
寄存器名,总线地址,寄存器功能
寄存器功能配置
本文例子采用
IO空间的起始地址是0x3f000000,加上GPIO的偏移量0x2000000,GPIO的起始物理地址应该是0x3f200000。
GPFSEL0
GPIO Function Select 0 功能选择输入或输出
GPCLR0
GPIO Pin Output Clear 0 清0
GPSET0
GPIO Pin Output Set 0 将IO输出口置0
volatile的作用是作为指令关键字,确保本条指令不会因编译器的优化而省略,且要求每次直接读值
本文例子中的寄存器地址配置
引脚5
*GPFSEL0 &=~(0x6 <<15);
*GPFSEL0 |=(0x1 <<15);
清除
*GPCLR0 |= (0x1 << 5);
设置为
*GPSET0 |= (0x1 << 5);
本文例子中的寄存器物理地址映射为虚拟地址
GPFSEL0=(volatile unsigned int *)ioremap(0x3f200000,4);
GPSET0 =(volatile unsigned int *)ioremap(0x3f20001C,4);
GPCLR0 =(volatile unsigned int *)ioremap(0x3f200028,4)
驱动编写计测试
驱动代码示例
#include <linux/fs.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/uaccess.h>
#include <linux/types.h>
#include <asm/io.h>
static struct class *pin_class;
static struct device *pin_class_dev;
static dev_t devno; //设备号
static int major =127; //主设备号
static int minor =0; //次设备号
static char *module_name="pin"; //模块名
volatile unsigned int* GPFSEL0 = NULL;
volatile unsigned int* GPSET0 = NULL;
volatile unsigned int* GPCLR0 = NULL;
static int pin_open(struct inode *inode,struct file *file)
{
*GPFSEL0 &=~(0x6 <<15);
*GPFSEL0 |=(0x1 <<15);
printk("pin open sucess\n");
return 0;
}
static ssize_t pin_write(struct file *file,const char __user *buf,size_t count, loff_t *ppos)
{
int usercmd;
copy_from_user(&usercmd,buf,count);
if(usercmd == 1)
{
*GPSET0 |= (0x1 << 5);
printk("set 1");
}
else if(usercmd == 0)
{
*GPCLR0 |= (0x1 << 5);
printk("set 0");
}
else
{
printk("user input error\n");
}
printk("pin write success\n");
return 0;
}
static struct file_operations pin_fops = {
.owner = THIS_MODULE,
.open = pin_open,
.write = pin_write,
};
int __init pin_drv_init(void)
{
int ret;
devno = MKDEV(major,minor); //创建设备号
ret = register_chrdev(major, module_name,&pin_fops); //注册驱动,驱动加入驱动链表
pin_class = class_create(THIS_MODULE,"pinMoudle"); // /dev生成设备
pin_class_dev =device_create(pin_class,NULL,devno,NULL,module_name); //创建设备文件
GPFSEL0=(volatile unsigned int *)ioremap(0x3f200000,4);
GPSET0 =(volatile unsigned int *)ioremap(0x3f20001C,4);
GPCLR0 =(volatile unsigned int *)ioremap(0x3f200028,4);
return 0;
}
void __exit pin_drv_exit(void)
{
iounmap(GPFSEL0);
iounmap(GPSET0);
iounmap(GPCLR0);
device_destroy(pin_class,devno);
class_destroy(pin_class);
unregister_chrdev(major, module_name); //卸载驱动
}
module_init(pin_drv_init);
module_exit(pin_drv_exit);
MODULE_LICENSE("GPL v2");
驱动测试代码
测试流程
ubuntu:
cd /root/linux-rpi-4.14.y/drivers/char
(驱动代码编写)
vim pin_test.c
cd /root/linux-rpi-4.14.y
ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- KERNEL=kernel7 make -j4 modules
生成(/root/linux-rpi-4.14.y/drivers/char):pin_test.ko,上传文件到Pi
Pi:
insmod pin_test.ko
chmod +x /dev/pin
(驱动测试代码)
vim pinTest.c
gcc pinTest.c -o pinTest
接线图
测试图例