1 驱动代码编写
根据BCM2835芯片手册编写引脚4 IO口驱动,将引脚4设置为输出模式进行使用
该图有用信息为:
GPFSEL io口功能寄存器,控制io口输出输入模式
GPSET0 输出寄存器偏移量为0x1C,功能为输出1
GPSET1 输出寄存器偏移量为0x20,功能为输出1
GPCLR0 清除寄存器偏移量为0x28,功能为清0
GPCLR1 清除寄存器偏移量为0x2C,功能为清0
该图有用信息为:FSEL4为引脚4,控制位是12-14位,它属于GPFSEL0寄存器,将12-14位设置为001就是output模式
该图有用信息:
GPSETn: GPIO引脚输出寄存器,控制引脚输出1的寄存器
设置n号引脚输出1,就将n位置1。无效则置0
GPSET0寄存器属于0-31位,控制引脚0-31
GPSET1寄存器数去32-53位,控制引脚32-54
该图有用信息:
GPCLRn:GPIO引脚输出清除寄存器,控制引脚输出0的寄存器
设置n号引脚输出0,就将n位置1。无效则置0
GPCLR0寄存器属于0-31位,控制引脚0-31
GPCLR1寄存器数去32-53位,控制引脚32-54
注意:
树莓派3B的CPU为BCM2835,IO空间的起始地址是0x3f000000加上GPIO的偏移量0x2000000,所以GPIO的物理地址应该是从0x3f200000开始的,然后在这个基础上进行Linux系统的MMU内存虚拟化管理,映射到虚拟地址上。BCM2835芯片手册中的地址是总线地址,0x3f000000才是起始地址,这是造成大部分人驱动出现“段错误”的原因。
信息整理:
将引脚4设置为output模式使用FSEL4寄存器,输出1使用GPSET0寄存器,输出0使用GPCLR0寄存器
GPFSEL0 12-14位设置为001 *GPFSEL4 |= ~(0x6 << 12); *GPFSEL4 |= 0x1 << 12 地址:0x3f200000
GPSET0 输出1 *GPSET0 |= 0x1 << 4 地址:0x3f20001C
GPCLR0 输出0 *GPSET0 |= 0x1 << 4 地址:0x3f200028
映射寄存器地址:
定义全局变量时不能赋值为变量值,(地址不能赋值给定义时的全局变量)
//映射
*GPFSEL4 = (volatile unsigned int*)ioremap(0x3f200000, 4);
*GPSET0 = (volatile unsigned int*)ioremap(0x3f20001C, 4);
*GPCLR0 = (volatile unsigned int*)ioremap(0x3f200020, 4);
//解映
iounmap(GPCLR0);
iounmap(GPSET0);
iounmap(GPFSEL0);
volatile:
保证地址不会因编译器的优化而被更改,每次直接读取值
ioremap函数:
将物理地址映射成虚拟地址来访问寄存器
void *__ioremap(unsigned long phys_addr, unsigned long size, unsigned long flags)
void *ioremap(unsigned long phys_addr, unsigned long size)
入口: phys_addr:要映射的起始的IO地址;
size:要映射的空间的大小;
flags:要映射的IO空间的和权限有关的标志;
phys_addr:是要映射的物理地址
size:是要映射的长度,单位是字节
头文件:io.h
iounmap函数:
解除映射:void iounmap(void* addr)//取消ioremap所映射的IO地址
copy_to_user函数:
static inline unsigned long __must_check copy_to_user(void __user *to, const void *from, unsigned long n)
to 目标地址,这个地址是用户空间的地址
from 源地址,这个地址是内核空间的地址
n 将要拷贝的数据的字节数
copy_form_user函数:
static inline unsigned long __must_check copy_from_user(void *to, const void __user *from, unsigned long n)
to 目标地址,这个地址是内核空间的地址
from 源地址,这个地址是用户的地址
n 将要拷贝的数据的字节数
先初始化驱动设备,再映射寄存器地址,结束先取消映射寄存器地址,再卸载驱动设备
驱动代码pin4_drive.c
#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 = 230; //主设备号
static int minor = 0; //次设备号
static char *module_name = "pin4"; //模块名
volatile unsigned int *GPFSEL0 = NULL;
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");
*GPFSEL0 &= ~(0x6 << 12); //set pin 4 output
*GPFSEL0 |= (0x1 << 12);
return 0;
}
//led_write函数
static ssize_t pin4_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos)
{
char cmd;
copy_from_user(&cmd, buf, count);
printk("cmd %c\n", cmd);
if (cmd == '1') {
printk("set 1\n");
*GPSET0 |= (0x1 << 4);
} else if (cmd == '0') {
printk("set 0\n");
*GPCLR0 |= (0x1 << 4);
} else {
printk("input false!\n");
}
return 0;
}
static struct file_operations pin4_fops = {
.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_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);
GPSET0 = (volatile unsigned int*)ioremap(0x3f20001C, 4);
GPCLR0 = (volatile unsigned int*)ioremap(0x3f200028, 4);
printk("pin4_init\n");
return 0;
}
void __exit pin4_drv_exit(void)
{
iounmap(GPCLR0);
iounmap(GPSET0);
iounmap(GPFSEL0);
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");
测试文件text.c
#include <unistd.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
int main(){
int ret;
int fd = open("/dev/pin4",O_RDWR);
if(fd < 0)
{
printf("open file fail\n");
exit(-1);
}
char cmd;
printf("高电平输入1,低电平输入0\n");
scanf("%c", &cmd);
ret = write(fd, &cmd, 1);
if (ret < 0)
{
printf("write fail\n");
}
return 0;
}
2 驱动代码编译运行
编译运行过程参考博客:
https://blog.csdn.net/weixin_54178481/article/details/119037397
运行结果