【linux驱动】结合linux驱动在迅为rk3568开发板上点亮一个LED灯的详细教程

文章目录

  • 分析
  • 驱动编写
  • 测试程序编写

  • 开发环境:迅为3568开发板 + ubuntu18.04
  • 任务:给LED写一个驱动程序,要求当应用程序写入驱动的数据为’1’时点亮LED;当应用程序写入数据为‘0’时熄灭LED。

分析

步骤一:确定控制引脚
打开原理图确定LED的GPIO引脚位置,通过下图知GPIO0_B7可以控制LED9。
【linux驱动】结合linux驱动在迅为rk3568开发板上点亮一个LED灯的详细教程_第1张图片
GPIO0_B7通过一个三极管控制LED9:

  • GPIO0_B7为低电平时,三极管截止,LED处于熄灭状态;
  • GPIO0_B7为高电平时,三极管导通,LED处于点亮状态。

步骤二:配置寄存器
一般情况下,对GPIO 进行配置需要对 GPIO 的复用寄存器,方向寄存器,数据寄存器进行配置:

复用寄存器
通过筛选原理图知GPIO0_B7的复用寄存器在PMU_GRF_GPIO0B_IOMUX_H 上,所以偏移地址为 0x000C。gpio0b7 可以通过控制[14:12]位来选择复用为哪个功能,要控制led 灯,所以功能要复用为 gpio,即[14:12]的三位全部为0即可。
【linux驱动】结合linux驱动在迅为rk3568开发板上点亮一个LED灯的详细教程_第2张图片
查找手册知复用寄存器的基地址PMU_GRF0xFDC20000,因此PMU_GRF_GPIO0B_IOMUX_H 的地址为0xFDC20000(基地址)+0x000C(偏移地址)=0xFDC2000C.
【linux驱动】结合linux驱动在迅为rk3568开发板上点亮一个LED灯的详细教程_第3张图片
使用io -r -4 0xFDC2000C可查看寄存器的默认值,通过下图可知,寄存器的 默认值为0x0000001,也就是说 PMU_GRF_GPIO0B_IOMUX_H 的[14:12]的三位全部为0,因此配置GOIO时可以不用再配置复用寄存器。

在这里插入图片描述

方向寄存器
GPIO 共有四组 GPIO,分别是 GPIOA,GPIOB,GPIOC,GPIOD,每组又以 A0~A7, B0~B7, C0~C7, D0~D7 作为编号区分。GPIO_SWPORT_DDR_L 与GPIO_SWPORT_DDR_H上各有两组GPIO,其中GPIO_SWPORT_DDR_L 上可控制GPIOA与GPIOB,GPIO_SWPORT_DDR_H 上可控制GPIOC与GPIOD。
GPIO0B_7 在 GPIO_SWPORT_DDR_L 上,通过下图可知GPIO_SWPORT_DDR_L寄存器地址的计算方式为基地址(0xfDD60000)+偏移地址(0x0008) = 0xFDD60008

【linux驱动】结合linux驱动在迅为rk3568开发板上点亮一个LED灯的详细教程_第4张图片

GPIO的基地址:
【linux驱动】结合linux驱动在迅为rk3568开发板上点亮一个LED灯的详细教程_第5张图片

通过寄存器的描述可知:寄存器的高16为是控制位,控制低16位数据是否能够写入,二者也是一一对应的关系,如第16为控制第0位,第17为控制第1位,……第31为控制第15位。
因此当我们需要将GPIOB7设置成输出模式时,GPIO_SWPORT_DDR_L寄存器的第15位值为1,第31位值为1。同理,通过命令 io -r -4 0xFDD60008结果如下,显然符合要求,因此方向寄存器也不用配置。
在这里插入图片描述

数据寄存器

GPIO 有四组 GPIO,分别是 GPIOA,GPIOB,GPIOC,GPIOD。每组又以 A0~A7, B0~B7, C0~C7, D0~D7 作为编号区分。GPIO0B7 在 GPIO_SWPORT_DR_L 上。这里的情况类似于方向寄存器GPIO_SWPORT_DDR_L,因此不做过多论述。
数据寄存器GPIO_SWPORT_DR_L 的地址为基地址(0xFDD60000)+偏移地址(0x0000)=0xFDD60000
【linux驱动】结合linux驱动在迅为rk3568开发板上点亮一个LED灯的详细教程_第6张图片

再次通过命令 io -r -4 0xFDD60000查看当前寄存器的值,结果如下:
在这里插入图片描述
由于点亮LED时寄存器的第15位值为1,熄灭LED时第15位值为0,而不管是控制LED熄灭还是点亮寄存器的第31位值一定要是1,因此有:

  • LED点亮时数据寄存器值为:0x8000 c040(1000 0000 0000 0000 1100 0000 0100 0000)
  • LED熄灭时数据寄存器值为:0x8000 4040(1000 0000 0000 0000 0100 0000 0100 0000)

也可直接使用使用io命令控制检验上述逻辑是否正确:

  • 熄灭LED io -w -4 0xfdd60000 0x80004040
  • 点亮LED io -w -4 0xfdd60000 0x8000c040
    在这里插入图片描述

注意:
为了不影响开发板上其他功能,以此配置复用寄存器 方向寄存器 数据寄存器时,先使用io命令查看开发板当前寄存器中的值,再改变需要的某几位值。
可能大家会好奇,为什么要这样。举个栗子,开发板启动需要寄存器A的第1位值为1,而我们点亮LED灯时仅需要第2位值为1,那么如果我们仅仅将寄存器A的值设置成0x0000 0010 那么开发板能够正常工作嘛?显然不能。

驱动编写

由于在linux中不能直接使用物理地址,因此加载驱动时使用地址映射,将物理地址转换成虚拟地址:

	// 地址映射
	vir_gpio_dr=ioremap(GPIO_DR,4);
	if(IS_ERR(vir_gpio_dr))
	{
		printk("ioremap is failed.\r\n");
		iounmap(vir_gpio_dr);
		return PTR_ERR(vir_gpio_dr);
	}

地址映射函数ioremap,其函数原型为:

#define ioremap(offset, size)						\
	__ioremap_mode((offset), (size), _CACHE_UNCACHED)

参数释义:

  • offset: 表示待映射地址的起始地址;
  • size:表示待映射地址的大小。
    映射成功后会返回映射后的虚拟地址;否则返回错误。

应用程序控制LED灯的实质就是使用write函数写一个控制命令到内核,因此内核只需要判断写入数据是否符合要求即可。

	// 开灯
	if(kbuff[0] == '1')
	{
		printk("open led.\r\n");
		*vir_gpio_dr = 0x8000c040;
	}
	// 关灯
	else if(kbuff[0] == '0')
	{
		printk("close led.\r\n");
		*vir_gpio_dr = 0x80004040;
	}

完整的驱动程序:

#include 
#include               //初始化头文件
#include             //最基本的文件,支持动态添加和卸载模块。
#include         //注册杂项设备头文件
#include                 //注册设备节点的文件结构体
#include 
#include 
#include 

/*
LED设备驱动:需要查看三个寄存器:
	复用关系寄存器(PMU_GRF Register)  :基地址(0xfdc20000) + 偏移地址(0x000c)
	方向寄存器 (GPIO_SWPORT_DDR):基地址(0xFDD60000)+偏移地址((0x0008)
		 GPIO
		| | | |
		A B C D
		| ……  | 
	   0~7   0~7
	数据寄存器: 基地址(0xFDD60000)+偏移地址(0x0000)
			   亮- 0x8000c040     灭-0x80004040
	GPIO0的基地址 : 0xFDD60000 
*/ 

// 数据寄存器地址
#define GPIO_DR 0xFDD60000

// 保存虚拟地址
unsigned int *vir_gpio_dr;

// 打开设备
int misc_open(struct inode *inode,struct file*file)
{
	printk("misc_open is ok.\r\n");
	return 0;
}

// 关闭设备
int misc_close(struct inode * inode, struct file *file)
{
	printk("misc_close\r\n");
	return 0;
}

// 读取设备中信息
ssize_t misc_read (struct file *file, char __user *buff, size_t size, loff_t *loff)
{
	char kbuff[5];
	if(copy_to_user(buff,kbuff,strlen(kbuff)) != 0) // 将内核中的数据给应用
	{
		printk("copy_to_user error\r\n");
		return -1;
	}	
	printk("copy_to_user is successful\r\n");

	
	return size;
}

// 写入设备信息
ssize_t misc_write (struct file *file, const char __user *buff, size_t size, loff_t *loff)
{
	char kbuff[32] ;
	int ret = copy_from_user(kbuff,buff,size);
	if( ret != 0) // 从应用那儿获取数据
	{
		printk("ret of error is %d.\r\n",ret);
		return ret;
	}	
	printk("copy_from_user data:%s.\r\n",kbuff);

	// 开灯
	if(kbuff[0] == '1')
	{
		printk("open led.\r\n");
		*vir_gpio_dr = 0x8000c040;
	}
	// 关灯
	else if(kbuff[0] == '0')
	{
		printk("close led.\r\n");
		*vir_gpio_dr = 0x80004040;
	}

	return size;
}

// 设备文件操作集
struct file_operations misc_fops ={
	.owner = THIS_MODULE,
	.open = misc_open,
	.release = misc_close,
	.read = misc_read,
	.write = misc_write
};

// 设备文件信息描述集
struct miscdevice misc_dev = {
	.minor = MISC_DYNAMIC_MINOR,
	.name = "myLed",
	.fops = &misc_fops
};

// 驱动的入口函数
static int __init misc_init(void)
{
	// 申请杂项设备
	int ret = 0;
	ret = misc_register(&misc_dev);
	printk("--------------------------------------------\r\n");
	if(ret < 0)
		printk("misc register is error.\r\n");
	else
		printk("misc register is seccussful.\r\n");
	
	// 地址映射
	vir_gpio_dr=ioremap(GPIO_DR,4);
	if(IS_ERR(vir_gpio_dr))
	{
		printk("ioremap is failed.\r\n");
		iounmap(vir_gpio_dr);
		return PTR_ERR(vir_gpio_dr);
	}
	
	return 0;
}

//驱动的出口函数
static void __exit misc_exit(void)
{
	// 注销杂项设备
	misc_deregister(&misc_dev);
	// 注销地址映射
	iounmap(vir_gpio_dr);
	printk("baibai\r\n");
}

module_init(misc_init);
module_exit(misc_exit);
MODULE_LICENSE("GPL v2");
MODULE_AUTHOR("zxj");

编译完成后,使用insmod加载驱动到内核后就可直接编写应用程序检验驱动是否正确。

测试程序编写

#include 
#include 
#include 
#include 
#include 
#include 
#include 

int main(int argc,char *argv[])
{
	if(argc != 2)
	{


		printf("the number of your input is eror\n");
		return -1;
	}

	char filePath[] = "/dev/myLed";
	int fd = open(filePath,O_RDWR);
	if(fd < 0)
	{
		printf("opening is failed.\n");
		return -1;
	}
	else
		printf("opening is successful.\n");

	write(fd,argv[1],sizeof(argv[1]));
	printf("write:%s.\r\n",argv[1]);		
	
	close(fd);

	return 0;
}

在使用交叉编译并且传输到开发板后,就可运行检验驱动了。检验命令为:

  • 输入 ./test 1就可点亮LED;
  • 输入 ./test 0就可熄灭LED;

其实本文的驱动还可以使用字符设备的驱动来编写,但是相对于字符设备驱动,杂项设备驱动更为简单,因此本文采用杂项设备来编写。
如果大家单纯地想看杂项设备与字符设备驱动的编写方式,可点击查阅本文【linux驱动开发】在linux内核中注册一个杂项设备与字符设备以及内核传参的详细教程

你可能感兴趣的:(LINUX,linux,单片机,运维)