使用驱动程序点亮LED灯

继第一节第一个驱动程序框架记录之后,本篇文章将会在上一篇驱动程序的框架下编写点亮LED的驱动程序,同样会对上一个框架进行修改,优化。接下来进入正题
1、点亮LED程序框架分析

在最开始之前先来梳理一下点亮LED程序的框架
1、通过对驱动程序的框架分析,我们知道第一步都要从入口函数开始,第一个驱动里我写的入口函数很简单,只是完成了必要操作
这里的话就要进行改进。
2、要点亮LED首先要对LED使用的引脚进行配置,引脚的模式只用配置一次,所以该部分工作放在打开设备文件时进行,
因为设备文件也只是打开一次。
3、对LED进行点亮或者熄灭处理,在写应用程序时我们一般通过write函数进行写文件操作,所以,这一部分比较频繁的操作同样
放在了驱动程序的write函数里。

内容就是这些,在简单梳理之后就可以写程序了
2、程序

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <asm/uaccess.h>
#include <asm/irq.h>
#include <asm/io.h>
#include <asm/arch/regs-gpio.h>
#include <asm/hardware.h>
static struct class *first_class;
static struct class_device *fisrst_class_device;

volatile unsigned long *gpfcon = NULL;
volatile unsigned long *gpfdat = NULL;

static int leddrv_open(struct inode *inode, struct file *file)
{
	*gpfcon &= ~((3 << 8) | (3 << 10) | (3 << 12));
	 *gpfcon |= ((1 << 8) | (1 << 10) | (1 << 12));
	 return 0;
}
static ssize_t leddrv_write(struct file *file, const char __user *buf, size_t count, loff_t * ppos)
{
	 int val;
 	copy_from_user(&val, buf, count);
 	if (val == 1) {
 		*gpfdat &= ~((1 << 4) | (1 << 5) | (1 << 6));
 	} else {
 		*gpfdat |= ((1 << 4) | (1 << 5) | (1 << 6));
 	}
 	return 0;
 }
struct file_operations leddrv_fops = {
    .owner  =   THIS_MODULE,
    .open   =   leddrv_open,     
 .write = leddrv_write,
};

//入口函数
int major;
static int leddrv_init(void)
{
	 major = register_chrdev(0, "led_drv", &leddrv_fops); 
	 first_class = class_create(THIS_MODULE, "led_drv");
	  fisrst_class_device = class_device_create(first_class, NULL,MKDEV(major,0),NULL,"led");
	 gpfcon = (volatile unsigned long *)ioremap(0x56000050, 16);
	  gpfdat = gpfcon + 1;
	 return 0;
}
//出口函数
static void leddrv_exit(void)
{
	 unregister_chrdev(major, "led_drv");
 class_device_unregister(fisrst_class_device);
  class_destroy(first_class);
 iounmap(gpfcon);
}
module_init(leddrv_init);
module_exit(leddrv_exit);
MODULE_LICENSE("GPL");

测试程序

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
int main(int argc, char **argv)
{
 int fd;
 int val = 1;
  fd = open("/dev/led", O_RDWR);
 if (fd < 0)
 {
  printf("can't open!\n");
 }
  if (argc != 2)
 {
  printf("Usage :\n");
  printf("%s <on|off>\n", argv[0]);
  return 0;
 }
  if (strcmp(argv[1], "on") == 0)
 {
  val  = 1;
 }
  else
 {
  val = 0;
 }
  write(fd, &val, 4);
 return 0;
}

3、总结

驱动程序改进的地方:
1、第一节中在注册设备驱动的时候,选择的主设备号是固定的,是通过我们查找了一个空闲的设备号来确定的,
这样的做法肯定是不方便的,那么,在上面的程序中,在注册设备驱动的时候传入的主设备号为0的
时候,内核就会给我们自动分配一个空闲的主设备号,在卸载的时候使用该设备号卸载驱动程序。
2、在上一节中只是完成了编写、插入一个驱动模块的工作,在打开设备文件时发现并没有设备文件,然后
我们通过手动执行mknod命令创建了一个设备文件,才完成了后续操作,在这一节中我们使用
"class_create""class_device_create"创建了一个类和类下的一个设备,
那么什么是类呢?我们插入的驱动模式内核都把他当做是一个类来看待,在"/sys/class"目录下就有使用
class_create函数创建的类文件,在该文件夹里有一个设备文件,是以我们创建时候命名的设备来命名的,
进入该设备文件夹里,使用"cat dev"命令,可以显示该设备驱动的主次设备号,
实际上内核在自动创建/dev/目录下的设备节点时就是使用该信息进行创建的。
3、硬件使用的地址都是物理地址,但是在内核中使用的地址是虚拟地址,所以在操作LED设备物理地址
的时候首先需要把该地址转换为虚拟地址,这里使用"ioremap"函数进行映射,在出口函数里使用
"iounmap"函数把之前映射的地址取消
4、用户空间的write函数调用的是驱动层的write函数,它们之间必须有参数的传递过程,在驱动函数write的参数中,
第二个和第三个参数就是用户空间传递下来的缓冲区和数据的长度,使用"copy_from_user"函数把数据拷贝到
内核空间中供我们使用,同样使用"copy_to_user"函数把数据从内核空间拷贝到用户空间
5、一系列工作做完之后就可以像裸机那样去操作LED灯了,进行点亮或者熄灭

结果:
注册的字符设备,主设备号是252
在这里插入图片描述
进入/sys/class目录,发现注册的类
在这里插入图片描述
进入led_drv目录下,查看类里面的设备
在这里插入图片描述
再进入文件夹,查看设备的主次编号
在这里插入图片描述
结果和我们注册时候写的是一样的,到这里,简单的点亮LED的驱动程序就已经完成

发布了33 篇原创文章 · 获赞 2 · 访问量 1022

猜你喜欢

转载自blog.csdn.net/weixin_41791581/article/details/103395917