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

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

分析

步骤一:确定控制引脚
打开原理图确定LED的GPIO引脚位置,通过下图知GPIO0_B7可以控制LED9。
在这里插入图片描述
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即可。
在这里插入图片描述
查找手册知复用寄存器的基地址PMU_GRF0xFDC20000,因此PMU_GRF_GPIO0B_IOMUX_H 的地址为0xFDC20000(基地址)+0x000C(偏移地址)=0xFDC2000C.
在这里插入图片描述
使用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

在这里插入图片描述

GPIO的基地址:
在这里插入图片描述

通过寄存器的描述可知:寄存器的高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
在这里插入图片描述

再次通过命令 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 <linux/kernel.h>
#include <linux/init.h>              //初始化头文件
#include <linux/module.h>            //最基本的文件,支持动态添加和卸载模块。
#include <linux/miscdevice.h>        //注册杂项设备头文件
#include <linux/fs.h>                //注册设备节点的文件结构体
#include <linux/uaccess.h>
#include <linux/errno.h>
#include <linux/io.h>

/*
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 <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>

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内核中注册一个杂项设备与字符设备以及内核传参的详细教程

  • 20
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
RK3568开发板是基于Rockchip RK3568处理器的一个开发平台,主要用于嵌入式系统开发和媒体应用领域。该开发板具有丰富的硬件资源和灵活的扩展接口,适合各类嵌入式项目的开发和调试。 首先,RK3568开发板采用了Rockchip自家研发的RK3568处理器,该处理器采用了先进的ARM架构,具有强大的计算和图形处理能力。它基于22nm工艺制造,拥有六个Cortex-A55核心,最高主频可达1.8GHz,能够提供出色的性能和能效比。 其次,该开发板还配备了丰富的外设接口,包括多个USB接口、以太网口、HDMI接口等,方便用户连接外部设备。此外,还提供了各类扩展接口,如SPI、I2C、UART等,方便用户接入各类传感器、显示屏等外部硬件。 另外,开发板上还搭载了完整的软件开发环境,包括针对RK3568的开发工具链、操作系统以及各类应用程序库。开发者可以利用这些工具进行应用程序的开发、调试和性能优化。同时,Rockchip还提供了丰富的开发文档和示例代码,帮助开发者快速上手。 总之,RK3568开发板是一款功能强大、资源丰富的开发平台,适用于嵌入式系统开发和媒体应用领域。它提供了高性能的处理器、丰富的硬件资源和完善的软件开发环境,为开发者提供了一站式的开发解决方案。无论是进行系统调试还是开发新的应用程序,该开发板都能够满足开发者的需求。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值