目录
1.配置寄存器
我们要进行树莓派引脚的驱动就要对树莓派的引脚进行一些配置,比如我想把树莓派的某个 I/O 引脚置为高电平,那么我就需要对该引脚相关的寄存器进行配置:
- 这里以pin4为例,要把树莓派pin4 引脚配置成输出引脚需要配置 GPFSEL 这个寄存器(每个寄存器都是32位)
而pin4属于 GPFSEL0 这个寄存器 ,所以需要把 GPFSEL0 寄存器的14-12 位配置成 001
*GPFSEL0 &=~(0x6<<12); 把bit 13 -14 配置成0
*GPFSEL0 |=(0x1<<12); 把第12位配置成1
- 配置好输出引脚后,把输出引脚置为高电平需要配置 GPSET 这个寄存器
而pin4属于 GPSET0 这个寄存器 ,所以需要把 GPSET0 寄存器的第4位配置成 1
*GPSET0 |=0x1<<4;
- 把输出引脚置为低电平需要配置 GPCLR 这个寄存器
而pin4属于 GPCLR0 这个寄存器 ,所以需要把 GPCLR0 寄存器的第4位配置成 1
*GPSET0 |=0x1<<4;
2.设置寄存器的地址
由于bcm2835芯片手册给的地址是总线地址,但是上层并不能对这个地址进行访问
而我们在编写树莓派驱动程序的时候,IO空间的起始地址是0x3f000000,加上GPIO的偏移量0x2000000,所以GPIO的物理地址应该是从0x3f200000开始的。参考图中寄存器的偏移量得出我们选用的寄存器的物理地址是:
GPFSEL0 0x3f200000
GPSET0 0x3f20001c
GPCLR0 0x3f200028
然后在这个基础上进行Linux系统的MMU内存虚拟化管理,映射到虚拟地址上,这里用到了一个ioremap 的
函数将物理地址转成虚拟地址,这样上层就能访问的到了。
GPFSEL0=(volatile unsigned int *)ioremap(0x3f200000,4);
GPSET0 =(volatile unsigned int *)ioremap(0x3f20001C,4);
GPCLR0 =(volatile unsigned int *)ioremap(0x3f200028,4);
3.编写驱动代码
#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 *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"; //模块名
volatile unsigned int* GPFSEL0=NULL; //volatile不会因编译器的优化而省略,每次直接读值
volatile unsigned int* GPSET0=NULL;
volatile unsigned int* GPCLR0=NULL;
//led_open函数
static int pin4_open(struct inode *inode,struct file *file)
{
printk("pin4_open\n"); //内核的打印函数和printf类似
*GPFSEL0 &=~(0x6<<12); //0x6 0110左移12位 取反后1001与上 结果为把bit13-14配置成0
*GPFSEL0 |=(0x1<<12); //由于不确定bit 12一定是1,所以还需把bit 12 配置成1
return 0;
}
//led_write函数
static ssize_t pin4_write(struct file *file,const char __user *buf,size_t count, loff_t *ppos)
{
int userCmd;
printk("pin4_write\n"); //内核的打印函数和printf类似
copy_from_user(&userCmd,buf,count); //从上层获取函数的值第一个参数是一个char类型的指针const char __user *buf,用int也行
if(userCmd==1)
{
printk("set 1\n");
*GPSET0 |=0x1<<4; //把第四引脚置一
}else if(userCmd==0)
{
printk("set 0\n");
*GPCLR0 |=0x1<<4; //把第四引脚清零
}else
{
printk("undo\n");
}
return 0;
}
static struct file_operations pin4_fops = {
.owner = THIS_MODULE,
.open = pin4_open,
.write = pin4_write,
};
int __init pin4_drv_init(void)
{
int ret;
devno = MKDEV(major,minor); //创建设备号
ret = register_chrdev(major, module_name,&pin4_fops); //注册驱动 告诉内核,把这个驱动加入到内核驱动的链表中
pin4_class=class_create(THIS_MODULE,"myfirstdemo"); //让代码在dev下自动生成设备
pin4_class_dev =device_create(pin4_class,NULL,devno,NULL,module_name); //创建设备文件
GPFSEL0=(volatile unsigned int *)ioremap(0x3f200000,4); //第一个参数真正的物理地址,第二个参数映射的大小 一个寄存器4个字节 4*8=32 bit
GPSET0=(volatile unsigned int*)ioremap(0x3f20001c,4);
GPCLR0=(volatile unsigned int*)ioremap(0x3f200028,4);
return 0;
}
void __exit pin4_drv_exit(void)
{
iounmap(GPFSEL0); //取消 ioremap 的映射
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");
4.编写应用层代码
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
int main()
{
int fd;
int cmd;
fd=open("/dev/pin4",O_RDWR);
if(fd < 0){
printf("open failed\n");
perror("reason:");
}else{
printf("succeed\n");
}
printf("输入 1/0 控制 pin4 输出");
scnaf("%d",cmd);
printf("%d\n",cmd);
if(cmd==1){
write(fd,&cmd,1);
}else if(cmd == 0){
write(fd,&cmd,1);
}
return 0;
}
5.编译
- 把写好的驱动代码(pin4_driver.c)放到源码树目录的 /drivers/char 目录下
- 修改Makefile (放在那个目录就在那个目录修改Makefile)
参考:树莓派驱动编译
6.测试
运行应用层代码,按下1时pin4引脚输出高电平
按下0时pin4引脚输出低电平