一、查看芯片手册
去官网下载芯片手册,查看手册。
1、从芯片手册可以看到,GPIO的基地址(0x0300B000):
2、从芯片手册上可以看到 PC 的偏移量(0x0048):
GPIOx 的地址就是 GPIO基地址 + GPIOx的偏移量,所以 GPIOPC 的地址为: 0x0300B000 + 0x0048 = 0x0300B048 。
3、找到我们需要修改控制的端口信息:
4、通过手册,去了解如何去操作GPIO的数据位:
如果将端口配置为输入或者输出,那么引脚的状态就与相应的位相同。读取的位值是由软件设置的值。
如果将端口配置为功能引脚,那么将会读取未定义的值。
二、编写代码进行IO操作
编写驱动代码 pin5driver.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 device 声明
#include <linux/uaccess.h> //copy_from_user的头文件
#include <linux/types.h> //设备号dev_t类型声明
#include <asm/io.h> //ioremap iounmap的头文件
static struct class *pin5_class;
static struct device *pin5_class_dev;
static dev_t devno; //设备号
static int major =231; //主设备号
static int minor =0; //次设备号
static char *module_name="pin5"; //模块名
//volatile 作用:不会因为编译器的优化而省略,且要求每次直接读取
volatile unsigned int* GPIOBASE = NULL;
volatile unsigned int* GPIOPC = NULL;
volatile unsigned int* GPIODAT = NULL;
//ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
static ssize_t pin5_read(struct file *file, char __user *buf, size_t count, loff_t *ppos)
{
printk("pin5_read\n");
return 0;
}
static int pin5_open(struct inode *inode,struct file *file)
{
printk("pin5_open\n"); //内核打印函数,和printf类似
//把bit22~bit20 配置成001 ,为输出模式
*GPIOPC &= ~(0x6 << 20); //把bit22和bit21配置为0
*GPIOPC |= (0x1 << 20); //把bit20配置为1
return 0;
}
static ssize_t pin5_write(struct file *file,const char __user *buf,size_t count, loff_t *ppos)
{
char user_cmd;
printk("pin5_write\n");
//获取用户空间write的值
copy_from_user(&user_cmd,buf,count);
//根据值来操控io口,就是往相关的IO输出高电平或者低电平
if(user_cmd == '1'){
*GPIODAT |= 0x01 << 5;
printk("pin5_set\n");
}else if(user_cmd == '0'){
*GPIODAT &= ~(0x01 << 5);
printk("pin5_reset\n");
}else{
printk("undo\n");
}
return 0;
}
static struct file_operations pin5_fops = {
.owner = THIS_MODULE,
.open = pin5_open,
.write = pin5_write,
.read = pin5_read,
};
static int pin5_drv_init(void)
{
int ret;
printk("insmod driver pin5 success...\n");
devno = MKDEV(major,minor); //创建设备号
ret = register_chrdev(major, module_name,&pin5_fops); //注册驱动 告诉内核 把这个驱动加入到>内核链表当中
pin5_class=class_create(THIS_MODULE,"myfirstdemo"); //让代码在dev自动生成设备
pin5_class_dev =device_create(pin5_class,NULL,devno,NULL,module_name); //创建设备文件
/***********************************************************
* (1)0x300B000、0x0300B048、0x0300B058 这些均为 物理地址
* (2)需要通过 ioremap 函数 将 物理地址 映射为 虚拟地址
* (3)把IO口的寄存器 映射成 普通的内存单元 进行访问
************************************************************/
/************************映射虚拟地址************************/
//GPIO基地址
GPIOBASE = (volatile unsigned int *)ioremap(0x0300B000,4);
//GPIOPC地址
GPIOPC = (volatile unsigned int *)ioremap(0x0300B048,4);
//GPIO数据地址
GPIODAT = (volatile unsigned int *)ioremap(0x0300B058,4);
/***********************************************************/
return 0;
}
static void pin5_drv_exit(void)
{
//解除映射
iounmap(GPIOBASE);
iounmap(GPIOPC);
iounmap(GPIODAT);
device_destroy(pin5_class,devno);
class_destroy(pin5_class);
unregister_chrdev(major, module_name);
}
module_init(pin5_drv_init);
module_exit(pin5_drv_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("SHUN-GE");
三、驱动代码调试
1、首先,我们可以看到我们写好的驱动代码:
2、修改Makefile文件,添加如下内容:
3、然后我们需要回到内核的源码目录下,进行模块的编译,即 orangepi-build-main/kernel/orange-pi-4.9-sun50iw9 这个路径下:
输入以下命令,编译成模块:
make modules ARCH=arm64 CROSS_COMPILE=aarch64-none-linux-gnu-
编译通过如下所示:
4、将生成的 pin5driver.ko 模块 ,拷贝到开发板中:
5、在开发板中加载驱动模块:
输入以下命令即可:
sudo insmod pin5driver.ko
6、查看一下是否生成了pin5这个设备驱动:
输入以下命令:
ls /dev/pin5 -l
结果如下所示:
7、修改pin5驱动的用户权限:
输入如下命令:
sudo chmod 666 /dev/pin5
查看结果:
8、写一个用于测试的应用程序:
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
int main()
{
int fd;
int cmd;
fd = open("/dev/pin5",O_RDWR);
if(fd < 0){
printf("open fail\n");
}else {
printf("open success\n");
}
printf("input command: 1/0\n");
printf("1: set pin5 high\n");
printf("0: set pin5 low\n");
scanf("%d",&cmd);
if(cmd == 1){
fd = write(fd,"1",1);
printf("%d=cmd \n",cmd);
}else if(cmd == 0){
fd = write(fd,"0",1);
printf("%d=cmd \n",cmd);
}
}
7、测试结果:
(1)PC5 为 OUT 模式,输出电平为1:
(2)PC5 为 OUT 模式,输出电平为0:
(3)用 dmesg 命令查看内核层 的 驱动程序打印的信息:
以上,就完成了IO口的驱动开发。