树莓派4b的基地址为0xfe00 0000,因此基地址加上偏移地址得到物理地址为0xfe20 000
而树莓派3b的基地址为0x3f00 0000,此处容易混淆
操作I/O口输入、输出0和输入、输出1,根据芯片手册,需要操作三个寄存器
功能选择寄存器:GPFSEL0 选择I/O口及I/O口功能
输出设置寄存器:GPSET0 选择I/O口输出1
输出清除寄存器:GPCLR0 选择I/O口输出0
功能选择寄存器(GPFSELn)
由芯片手册得知,GPFSELn (n= 0时控制引脚09,n=1时控制引脚1019,以此类推)为功能选择寄存器,功能选择寄存器用于定义通用 I/O 引脚的操作,有32位,每3位为1一组进行配置一个引脚,如果想要配置引脚4为输出,则应该让GPFSELn = 为GPFSEL0且配置bit 14-12 为001,其他位保持不变。
输出设置寄存器(GPSETn)
由芯片手册得知,GPFSETn (n= 0时控制引脚031,n=1时控制引脚3257)为输出设置寄存器,输出设置寄存器用于定义通用 I/O 引脚的操作,有32位,每1位配置一个引脚,如果想要配置引脚4输出1,则应该让GPFSETn = 为GPFSET0且配置bit 4为1,其他位保持不变。
输出清除寄存器(GPCLRn)
由芯片手册得知,GPCLRn (n= 0时控制引脚031,n=1时控制引脚3257)为输出设置寄存器,输出设置寄存器用于定义通用 I/O 引脚的操作,有32位,每1位配置一个引脚,如果想要配置引脚4输出1,则应该让GPCLRn = 为GPCLR0且配置bit 4为1,其他位保持不变。
volatile关键字
防止寄存器变量被编译器优化,要求每次直接从寄存器读值
上层代码
#include
#include
#include
#include
int main()
{
int fd;
int cmd;
int data;
fd = open("/dev/pin4",O_RDWR);
if(fd < 0)
{
printf("open failed\n");
}else{
printf("open success\n");
}
printf("please input cmd:1/0\n1:set pin4 high\n0:set pin4 low\n");
scanf("%d",&cmd);
if(cmd == 1)
{
data = 1;
}
else
{
data = 0;
}
printf("data = %d\n",data);
fd = write(fd,&data,4);
return 0;
}
完整驱动代码
#include //file_operations声明
#include //module_init module_exit声明
#include //__init __exit 宏定义声明
#include //class devise声明
#include //copy_from_user 的头文件
#include //设备号 dev_t 类型声明
#include //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 unsigned int* GPSET0 = NULL;
volatile unsigned int* GPCLR0 = NULL;
static int pin4_read (struct file *file,char __user *buf,size_t size,loff_t *ppos)
{
printk("pin4_read\n"); //内核的打印函数,和pintf类似
return 0;
}
//led_open函数
static int pin4_open(struct inode *inode,struct file *file)
{
printk("pin4_open\n"); //内核的打印函数和printf类似
//配置pin4输出模式
*GPFSEL0 &= ~(0x06 << 12); //按位与 0 & 0= 0 ,0 & 1= 0,1 & 0= 0, 1 & 1= 1
//xxxxxxxxxx14 13 12xxxxxxxxx,001取反110,等于6 ;(12 << 0x06)左移12位
// 0 0 1
//为了不影响其他io口,将其取反再&
//
*GPFSEL0 |= (0x06 << 12); //按位或 0 | 0= 0 , 1 | 0= 1 , 0 | 1= 1 , 1 | 1= 1
//将某位置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");
//获取上层write函数的值
copy_from_user(&usercmd,buf,count);
//配置pin4输出高低电平
if(usercmd == 1)
{
printk("set 1\n");
*GPSET0 |= (0x01 << 4);
}
else if (usercmd == 0)
{
printk("set 0\n");/* code */
*GPCLR0 |= (0x01 << 4);
}
else
{
printk("no\n");
}
return 0;
}
static struct file_operations pin4_fops = {
.owner = THIS_MODULE,
.open = pin4_open,
.write = pin4_write,
.read = pin4_read,
};
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(0xfe200000,4); //需要用ioremap(物理地址,映射到多少个字节)把实际地址转换成虚拟地址才有用,因为是操作mmu,虚拟内存管理;
GPSET0 = (volatile unsigned int*)ioremap(0xfe20001c,4);
GPCLR0 = (volatile unsigned int*)ioremap(0xfe200028,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");
测试结果:
打开成功
dmesg查看内核打印信息
查看pin4的状态,可看出已设置成输出模式,低电平状态
查看pin4的状态,可看出已设置成输出模式,高电平状态
驱动代码测试完毕