一个简单点亮LED灯的字符设备驱动

本文介绍了一个简单的字符设备驱动程序,用于控制开发板上的LED灯。通过注册字符设备、创建类和设备节点,以及映射GPIO寄存器,实现了用户空间与内核空间的交互。文章还提供了测试函数示例。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

前言

       后面可能会写一系列的驱动学习的文章,现在就以一个最简单的字符设备驱动开始。我的这个字符设备主要是为了点亮开发板的LED灯。

       对于字符设备的文章,网上很多而且也比较简单,所以这篇文章只适合刚学的小白,大神请绕道。

 

正文

先把代码贴出来再把重点讲一下吧

字符设备驱动程序:

#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 int major;
static struct class *first_class;
static struct class_device *first_class_dev;
volatile unsigned long *gpfcon = NULL;
volatile unsigned long *gpfdat = NULL;

static int first_drv_open(struct inode *inode, struct file *file)
{
	/*配置GPF4,5,6为输出引脚*/
	*gpfcon &= ~( (0x3<<8) | (0x3<<10) | (0x3<<12) ); //先清空初始化
	*gpfcon |= ( (0x1<<8) | (0x1<<10) | (0x1<<12) ); //设置为输出引脚
	return 0;
}

static ssize_t first_drv_write(struct file *file, const __user *buf, size_t count, loff_t *ppos)
{
	int val;
	copy_from_user(&val, buf, count); //从用户空间获取数据

	if (1 == val)
	{
		//开灯,低电平有效
		*gpfdat &= ~( (0x1<<4) | (0x1<<5) | (0x1<<6) );
	}
	else
	{
		//关灯
		*gpfdat |= ( (0x1<<4) | (0x1<<5) | (0x1<<6) );
	}
	return 0;
}

/*下面的结构体就是为了和内核联系起来*/
static struct file_operations first_drv_fops = {
	.owner = THIS_MODULE,
	.open  = first_drv_open,
	.write = first_drv_write,
};

int first_drv_init(void)
{
	major = register_chrdev(0, "first_drv", &first_drv_fops); //第一个参数为0,系统会自动分配主设备号
	first_class = class_create(THIS_MODULE, "firstdrv");
	first_class_dev = class_device_create(first_class, NULL, MKDEV(major, 0), NULL, "xyz"); //这是2.6内核的函数,新版的内核使用device_create
	gpfcon = (volatile unsigned long *)ioremap(0x56000050, 16); //第一个参数是物理地址,第二个参数是长度,这里为16字节
	gpfdat = gpfcon + 1;
	return 0;
}

void first_drv_exit(void)
{
	unregister_chrdev(major, "first_drv");

	class_device_unregister(first_class_dev);
	class_destroy(first_class);
	
	iounmap(gpfcon);
}

module_init(first_drv_init);
module_exit(first_drv_exit);
MODULE_LICENSE("GPL");

(1)每个驱动程序加载到内核时,都会先执行module_init函数,这个函数其实是个宏定义(module_init的含义),最终执行的是module_init括号里面定义的first_drv_init函数。自然,卸载函数就会执行module_exit函数。

(2)register_chrdev()函数用于注册字符设备。当第一个参数为0时,系统会自动分配主设备号。我们也看到第三个参数是first_drv_fops结构体,这能将用户空间的open()、write()和first_drv_open()、first_drv_write()函数联系起来。在卸载驱动的时候,我们也要调用相应的卸载字符设备的函数unregister_chrdev()。

(3)调用class_create()函数后,系统会创建一个类:/sys/class/firstdrv。再调用class_device_create()函数就会自动在/dev/目录下创建对应的设备节点:/dev/xyz。当然,在卸载驱动也有相应的卸载函数:class_device_unregister()和class_destroy()。

(4)我们以前写裸板程序的时候,操作GPIO寄存器的时候,都是直接写物理地址,但是这里我们不能这么做。需要通过ioremap()函数将物理地址映射为虚拟地址。在卸载驱动后,我们也要调用iounmap()函数释放本次映射。

 

       到这里,一个粗糙的字符设备程序就完成了,为了代码的阅读,我就没有考虑很多函数调用失败后的情况了,实际生产中肯定不能这么做。下面就看一下用户空间的程序怎么使用字符设备节点了。

测试函数:

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>

int main(int argc, char **argv)
{
	int val = 1;
	int fd;
	fd = open("/dev/xyz", O_RDWR);
	if (fd < 0)
	{
		printf("open failed\n");
		return 0;
	}
	if (argc != 2)
	{
		printf("Usage :\n");
		printf("%s <on|off>\n", argv[0]);
		return 0;
	}
	if (!strcmp(argv[1], "on"))
	{
		val = 1;
	}
	else
	{
		val = 0;
	}
	write(fd, &val, 4);
	return 1;
}

       当调用open()函数成功打开创建的节点后(内核空间中,会调用我们的驱动程序中的first_drv_open()函数),如果我们往节点写1,内核空间中的first_drv_write()函数就会通过copy_from_user()获取到这个值,然后再判断是点亮还是灭灯。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值