编写引脚驱动代码
这边写的是17引脚的驱动代码代码(IO口控制的代码在下面),这边只是简单的代码
驱动代码
#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 device声明
#include <linux/uaccess.h> //copy_from_user的头文件
#include <linux/types.h> //设备号 dev_t 类型声明
#include <asm/io.h> // ioremap iounmap 的头文件
static struct class *pin17_class;
static struct device *pin17_class_dev;
static dev_t devno; //设备号
static int major = 232; //主设备号
static int minor = 0; //次设备号
static char *module_name = "pin17"; //模块名字
volatile unsigned int *GPFSEL0 = NULL;
volatile unsigned int *GPSET0 = NULL;
volatile unsigned int *GPCLR0 = NULL;
static int pin17_read(struct file *file,const char __user *buf,size_t count,loff_t *ppos)
{
printk("pin17_read\n");//内核打印函数
return 0;
}
//pin17_open函数
static int pin17_open(struct inod *inod,struct file *file)
{
printk("pin17_open\n");
/**GPFSEL0 &= ~(0x6 << 12);
*GPFSEL0 |= (0x6 << 12);*/
return 0;
}
//pin17_write函数
static int pin17_write(struct file *file,const char __user *buf,size_t count,loff_t *ppos)
{
//int userCmd;
printk("pin17_write\n");
/*copy_from_user(&userCmd,buf,count);//获取上层write函数的值
//根据值来操作IO口,高电平或者低电平
if(userCmd == 1){
printk("set 1\n");
*GPSET0 |= 0x1 << 17
}*/
return 0;
}
static struct file_operations pin17_fops = {
.owner = THIS_MODULE,
.open = pin17_open,
.write = pin17_write,
.read = pin17_read,
};
int __init pin17_drv_init(void)//真实驱动入口
{
int ret;
printk("insmod driver pin17 success\n");
devno = MKDEV(major,minor);//创建设备号
ret = register_chrdev(major,module_name,&pin17_fops);//注册驱动,告诉内核把这个驱动加到内核链表中
pin17_class = class_create(THIS_MODULE,"myFirstDemo");//让代码在dev自动生成设备
pin17_class_dev = device_create(pin17_class,NULL,devno,NULL,module_name);//创建设备文件
return 0;
}
void __exit pin17_drv_exit(void)
{
device_destroy(pin17_class,devno);
class_destroy(pin17_class);
unregister_chrdev(major,module_name);//卸载驱动
}
module_init(pin17_drv_init);//入口,内核加载驱动的时候,这个宏会被调用
module_exit(pin17_drv_exit);
MODULE_LICENSE("GPL v2");//模块的许可证声明
测试代码
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
int main(int argc, char const *argv[])
{
int fd;
fd = open("/dev/pin17",O_RDWR);
if(fd < 0){
printf("open failed\n");
perror("open:");
}else{
printf("open success\n");
}
fd = write(fd,"1",1);
return 0;
}
编译代码
在linux内核里面编译放到树莓派上去运行
-
打开Linux内核源码目录,进入到驱动的目录
-
选择子目录 char,并把刚刚编写的驱动代码拷进来(pin17Driver2.c)
-
为了让工程编译的时候编译上面pin17Driver2.c的代码,我们需要修改Makefile,
我们这里将他编译成模块的方式
vi Makefile
改成obj-m,驱动程序的文件名字改成叫做pin17Driver2.o,这样Makefile就配置完成了
ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- KERNEL=kernel7 make modules
这里就算是编译通过
- 接下来来我们就拷贝到树莓派上去(pin17Driver2.ko和pin17Test)
scp drivers/char/pin17Driver2.ko pi@(这里是你的树莓派IP地址):/home/pi
把pin17Test.c也编译一下放松到树莓派
arm-linux-gnueabihf-gcc pin17Test.c -o pin17Test
scp pin17Test pi@192.168.0.108:/home/pi
在树莓派测试
sudo insmod pin17Driver2.ko
检查dev底下是否生成pin17
-
来运行测试代码
我们可以下发现运行失败了,问题是:没有权限去打开这个设备,所以我们要设置权限
****检查一下dmesg是打印内核的printk
卸载驱动
sudo rmmod pin17Driver2
IO操控代码编程
选择查看IO口这一块内容
寄存器介绍
寄存器视图
GPIO有41个寄存器。所有访问都假定为32位。
由于我接下来我是编写树莓派17号引脚,所以我选择功能寄存器1
要区分寄存器的引脚和wpi的引脚
这个是查看各个引脚的网址
https://pinout.xyz/pinout/pin12_gpio18
GPFSEL1 GPIO Function Select 1 :GPFSEL1 GPIO功能选择1,功能选择输出/输入
GPSET0 GPIO Pin Output Set 0 :GPSET0 GPIO引脚输出设置0
GPCLR0 GPIO Pin Output Clear 0 :GPCLR0 GPIO引脚输出清除0
编写代码
我们编写寄存器地址的时候会出现问题,不能直接选择下面这个
我们在编写驱动程序的时候,IO空间的起始地址是0x3f000000,加上GPIO的偏移量0x2000000,所以GPIO的物理地址应该是从0x3f200000开始的,然后在这个基础上进行Linux系统的MMU内存虚拟化管理,映射到虚拟地址上。
从上面在这个图得出
GPFSEL0 0x3f200000
GPSET0 0x3f20001c
GPCLR0 0x3f200028
具体代码
#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 device声明
#include <linux/uaccess.h> //copy_from_user的头文件
#include <linux/types.h> //设备号 dev_t 类型声明
#include <asm/io.h> // ioremap iounmap 的头文件
static struct class *pin17_class;
static struct device *pin17_class_dev;
static dev_t devno; //设备号
static int major = 232; //主设备号
static int minor = 0; //次设备号
static char *module_name = "pin17"; //模块名字
//定义寄存器
volatile unsigned int *GPFSEL1 = NULL;
volatile unsigned int *GPSET0 = NULL;
volatile unsigned int *GPCLR0 = NULL;
static int pin17_read(struct file *file,const char __user *buf,size_t count,loff_t *ppos)
{
printk("pin17_read\n");//内核打印函数
return 0;
}
//pin17_open函数
static int pin17_open(struct inode *inode,struct file *file)
{
printk("pin17_open\n");
//配置pin17引脚为输出引脚,把bit 23-21位配置成001就行了,23-0,22-0,21-1
*GPFSEL1 &= ~(0x6 << 21);//把bit24 和bit23配置成0,~是取反的意思,&是按位与,<< 21是左移21位
*GPFSEL1 |= (0x1 << 21);//把bit21配置成1
return 0;
}
//pin17_write函数
static ssize_t pin17_write(struct file *file,const char __user *buf,size_t count,loff_t *ppos)
{
int userCmd;
printk("pin17_write\n");
copy_from_user(&userCmd,buf,count);//获取上层write函数的值
//根据值来操作IO口,高电平或者低电平
if(userCmd == 1){
printk("set 1\n");
*GPSET0 |= 0x1 << 17;//让GPSET0寄存器工作,把bit17设置陈高电平
}else if(userCmd == 0){
printk("set 0\n");
*GPCLR0 |= 0x1 << 17;//让GPCLR0清零寄存器工作,把bit17设置陈低电平
}else{
printk("undo\n");
}
return 0;
}
static struct file_operations pin17_fops = {
.owner = THIS_MODULE,
.open = pin17_open,
.write = pin17_write,
.read = pin17_read,
};
int __init pin17_drv_init(void)//真实驱动入口
{
int ret;
printk("insmod driver pin17 success\n");
devno = MKDEV(major,minor);//创建设备号
ret = register_chrdev(major,module_name,&pin17_fops);//注册驱动,告诉内核把这个驱动加到内核链表中
pin17_class = class_create(THIS_MODULE,"myFirstDemo");//让代码在dev自动生成设备
pin17_class_dev = device_create(pin17_class,NULL,devno,NULL,module_name);//创建设备文件
//寄存器的物理地址转换
GPFSEL1 = (volatile unsigned int *)ioremap(0x3f200004,4);//物理地址转换为虚拟地址,io口寄存器映射成普通内存单元进行访问
GPSET0 = (volatile unsigned int *)ioremap(0x3f20001C,4);
GPCLR0 = (volatile unsigned int *)ioremap(0x3f200028,4);
return 0;
}
void __exit pin17_drv_exit(void)
{
iounmap(GPFSEL1);
iounmap(GPSET0);
iounmap(GPCLR0);
device_destroy(pin17_class,devno);
class_destroy(pin17_class);
unregister_chrdev(major,module_name);//卸载驱动
}
module_init(pin17_drv_init);//入口,内核加载驱动的时候,这个宏会被调用
module_exit(pin17_drv_exit);
MODULE_LICENSE("GPL v2");//模块的许可证声明
其中这里要注意
//寄存器的物理地址转换
GPFSEL1 = (volatile unsigned int *)ioremap(0x3f200004,4);//物理地址转换为虚拟地址,io口寄存器映射成普通内存单元进行访问
GPSET0 = (volatile unsigned int *)ioremap(0x3f20001C,4);
GPCLR0 = (volatile unsigned int *)ioremap(0x3f200028,4);
这里我们得到的是物理地址是不可操作的,我们需要转化成虚拟地址借用下面的函数
void __iomem * __ioremap(unsigned long phys_addr, size_t size, unsigned long flags);
ioremap宏定义在asm/io.h内:
#define ioremap(cookie,size) __ioremap(cookie,size,0)
void __iomem * __ioremap(unsigned long phys_addr, size_t size, unsigned long flags);
参数:
phys_addr:要映射的起始的IO地址
size:要映射的空间的大小
flags:要映射的IO空间和权限有关的标志
该函数返回映射后的内核虚拟地址(3G-4G). 接着便可以通过读写该返回的内核虚拟地址去访问之这段I/O内存资源。
将写好的代码放入内核去编译驱动,跟上面的一样操作
应用层测试代码
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int main(int argc, char const *argv[])
{
int fd;
int cmd;
int data;
fd = open("/dev/pin17",O_RDWR);
if(fd < 0){
printf("open failed\n");
perror("open:");
}else{
printf("open success\n");
}
printf("请输入指令0或1(0低电平,1高电平)\n");
scanf("%d",&cmd);
if(cmd == 1){
data = 1;
}else if(cmd == 0){
data = 0;
}else{
exit(0);
}
printf("data = %d\n",data);
fd = write(fd,&data,1);
return 0;
}
编译加测试
在树莓派上装载驱动的步骤和上面的都一样,直接跳到结果