【迅为iTop4412学习笔记】14.编写一个LED驱动

声明

以下都是我刚开始看驱动视频的个人强行解读,如果有误请指出,共同进步。

本节目标

  1. 编写一个LED驱动

正文

  本节我们就真正的来驱动一个板子上的LED(单片机第一节就是点灯,没想到Linux要学这么久…)

我们首先理清思路。

注册设备 -> 注册驱动 ->调用probe()->probe()里注册杂项设备并生成设备节点->上层调用

这是我们之前学习的过程

我们本节关注的重点就是如何申请GPIO资源,并通过上层调用来控制

我们先上一个底稿代码,也就是第八章注册杂项设备的最终代码。

#include 
#include 
#include 
#include 
#include 

#define DRIVER_NAME "mryang_ctl"

MODULE_LICENSE("Dual BSD/GPL");
MODULE_AUTHOR("MrYang");

// 打开设备节点时调用此函数
static int mryang_open(struct inode *inode, struct file *file){
	printk(KERN_EMERG "mryang_open!\n");
	return 0;
}
// 关闭设备节点时调用此函数
static int mryang_release(struct inode *inode, struct file *file){
	printk(KERN_EMERG "mryang_release\n");
	return 0;
}
// ioctl操作时,调用此函数
static long mryang_unlocked_ioctl( struct file *files, unsigned int cmd, unsigned long arg)
{
	// 打印由上层应用调用ioctl时,参数里cmd的值
	printk("cmd is %u\n",cmd);
	// 打印由上层应用调用ioctl时,参数里arg的值
	printk("arg is %lu\n",arg);
	return 0;
}

// file_operations 结构体 mryang_ops
static struct file_operations mryang_ops = {
	.owner = THIS_MODULE,
	// 打开设备节点时调用此函数
	.open = mryang_open,
	// 关闭设备节点时调用此函数
	.release = mryang_release,
	// ioctl操作时,调用此函数
	.unlocked_ioctl = mryang_unlocked_ioctl,
};

static  struct miscdevice mryang_dev= {
	// 次设备号,可以指定次设备号是多少,若是想让linux自己去分配,则定义为宏定义
	.minor = MISC_DYNAMIC_MINOR,
	// 次设备的名字
	.name = "mryang_misc_ctl",
	
	.fops = &mryang_ops,
};

/* 加载驱动,设备、驱动匹配成功,则调用probe()函数 */
int mryang_probe(struct platform_device *pdv)
{
	printk(KERN_EMERG "probe!\n");
	// 注册杂项设备
	misc_register(&mryang_dev);
	return 0;
}

/* 卸载驱动调用remove()函数 */
int mryang_remove(struct platform_device *pdv)
{
	printk(KERN_EMERG "remove!\n");
	// 卸载杂项设备
	misc_deregister(&mryang_dev);
	return 0;
}

/* 驱动结构体 */
struct platform_driver mryang_driver = {
	.probe = mryang_probe,
	.remove = mryang_remove,
	.driver = {
		.name = DRIVER_NAME,
		.owner = THIS_MODULE,
	}
};

/* 加载模块 */
static int mryang_init(void)
{
	printk(KERN_EMERG "HELLO MrYang\n");
	platform_driver_register(&mryang_driver);
	return 0;
}

/* 卸载模块 */
static void mryang_exit(void)
{
	printk(KERN_EMERG "Bye MrYang\n");
	platform_driver_unregister(&mryang_driver);
}

module_init(mryang_init);
module_exit(mryang_exit);

我们接下来要做的,就是在probe()里申请GPIO资源。

申请GPIO资源

  如果说学过单片机,其实很好理解GPIO的初始化过程。

  之前也提到过,对于linux来说,硬件不直接开放给上层,所以我们需要使用硬件的时候需要向 linux 申请,申请的函数就是

// 函数原型
// int gpio_request(unsigned gpio, const char *label);

  而包含他的头文件是#include

这个属于linux通用头文件,三星的GPIO头文件已包含他(只介绍)

  参数很简单,参数一表示你申请的GPIO引脚是哪一个,参数二只是一个标签,实际意义就是告诉人家你这个GPIO是干啥用的,内容随便怎么写,我们就写个LEDS表明是个LED就行。

  虽然我们知道控制LED的管脚是GPL2_0。那么填入的参数一具体填什么呢?我们先上头文件。

#include 		// 三星平台的GPIO配置函数头文件
#include 			// 三星平台,配置GPIO的参数(如控制高低电平的宏定义)
#include 	// 三星平台,定义实际芯片GPIO的宏定义(比如GPL2_0)

这俩是三星4412平台提供的头文件,里面定义了GPIO的引脚,以及配置GPIO的参数。

直接上实例,以下代码放入probe()函数里,匹配成功就先申请GPIO资源,申请到了再注册杂项设备。

	// 申请GPIO,他是GPL2(0)脚,这个引脚的标签是LEDS(让看代码的人知道这个引脚是用于LED)
	gpio_request(EXYNOS4_GPL2(0),"LEDS");
	// 配置GPL2(0)引脚为输出(若学过单片机不难理解)
	s3c_gpio_cfgpin(EXYNOS4_GPL2(0), S3C_GPIO_OUTPUT);
	// 设置默认输出为低电平(0),因为LED原理图看到三极管高电平开,低电平关。
	gpio_set_value(EXYNOS4_GPL2(0),0);

当然,我们不能只这么写,申请GPIO是有返回值的,我们要根据返回值来查看是否成功申请到GPIO资源,负值则是失败,失败我们就不继续执行了。

  申请了GPIO,注册了杂项设备并生成了设备节点,应用就可以用ioctl来传递参数了,所以用ioctl()传值的cmd参数来控制LED的亮灭。我们在mryang_ioctl()里添加控制GPIO电平的函数

// cmd是ioctl的参数
gpio_set_value(EXYNOS4_GPL2(0),cmd);

下面上整个驱动文件的代码

驱动部分:

#include 
#include 
#include 
#include 
#include 

#include 		// 三星平台的GPIO配置函数头文件
#include 			// 三星平台,配置GPIO的参数(如控制高低电平的宏定义)
#include 	// 三星平台,定义实际芯片GPIO的宏定义(比如GPL2_0)

#define DRIVER_NAME	"mryang_ctl"
#define DEV_NAME		"mryang_misc_ctl"

MODULE_LICENSE("Dual BSD/GPL");
MODULE_AUTHOR("MrYang");

// 打开设备节点时调用此函数
static int mryang_open(struct inode *inode, struct file *file){
	printk(KERN_EMERG "mryang_open!\n");
	return 0;
}
// 关闭设备节点时调用此函数
static int mryang_release(struct inode *inode, struct file *file){
	printk(KERN_EMERG "mryang_release\n");
	return 0;
}
// ioctl操作时,调用此函数
static long mryang_unlocked_ioctl( struct file *files, unsigned int cmd, unsigned long arg)
{
	// 打印由上层应用调用ioctl时,参数里cmd的值
	printk("cmd is %u\n",cmd);
	// 打印由上层应用调用ioctl时,参数里arg的值
	printk("arg is %lu\n",arg);
	// 根据cmd来控制LED
	gpio_set_value(EXYNOS4_GPL2(0),cmd);
	return 0;
}

// file_operations 结构体 mryang_ops
static struct file_operations mryang_ops = {
	.owner = THIS_MODULE,
	// 打开设备节点时调用此函数
	.open = mryang_open,
	// 关闭设备节点时调用此函数
	.release = mryang_release,
	// ioctl操作时,调用此函数
	.unlocked_ioctl = mryang_unlocked_ioctl,
};

static  struct miscdevice mryang_dev= {
	// 次设备号,可以指定次设备号是多少,若是想让linux自己去分配,则定义为宏定义
	.minor = MISC_DYNAMIC_MINOR,
	// 次设备的名字
	.name = DEV_NAME,
	
	.fops = &mryang_ops,
};

/* 加载驱动,设备、驱动匹配成功,则调用probe()函数 */
int mryang_probe(struct platform_device *pdv)
{
	int flag = -1;
	
	printk(KERN_EMERG "probe!\n");
	
	/* 如果驱动最开始章节你的LED没有取消编译进内核,你会发现申请GPIO失败,返回-16。
	因为迅为的内核里有一个驱动正在驱动GPIO了,不释放你无法申请此GPIO。
	所以在此用了一个告诉linux释放GPIO的函数
	*/
	gpio_free(EXYNOS4_GPL2(0));
	
	// 申请GPIO
	flag = gpio_request(EXYNOS4_GPL2(0),"LEDS");
	
	if(flag < 0){
		printk(KERN_EMERG "gpio_request EXYNOS4_GPL2(0) failed!\n");
		return flag;
	}
	// 配置GPIO为输出
	s3c_gpio_cfgpin(EXYNOS4_GPL2(0), S3C_GPIO_OUTPUT);
	// 设置默认输出为0
	gpio_set_value(EXYNOS4_GPL2(0),0);
	
	// 注册杂项设备
	misc_register(&mryang_dev);
	return 0;
}

/* 卸载驱动调用remove()函数 */
int mryang_remove(struct platform_device *pdv)
{
	printk(KERN_EMERG "remove!\n");
	// 卸载杂项设备
	misc_deregister(&mryang_dev);
	return 0;
}

/* 驱动结构体 */
struct platform_driver mryang_driver = {
	.probe = mryang_probe,
	.remove = mryang_remove,
	.driver = {
		.name = DRIVER_NAME,
		.owner = THIS_MODULE,
	}
};

/* 加载模块 */
static int mryang_init(void)
{
	printk(KERN_EMERG "HELLO MrYang\n");
	platform_driver_register(&mryang_driver);
	return 0;
}

/* 卸载模块 */
static void mryang_exit(void)
{
	printk(KERN_EMERG "Bye MrYang\n");
	platform_driver_unregister(&mryang_driver);
}

module_init(mryang_init);
module_exit(mryang_exit);

应用部分:

应用就比较简单了,我也不检查启动程序时传入的参数了

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

int main(int argc, char* argv[])
{
	char *mryang_node = "/dev/mryang_led_ctl";
	
	int fd = open(mryang_node, O_RDWR|O_NDELAY);
	
	int cmd;
	
	if(fd < 0)
	{
		printf("open failed!\n");
	}
	else
	{
		cmd = atoi(argv[1]);
		printf("open success! fd=%d, cmd=%d\n", fd, cmd);
		ioctl(fd, cmd, 2);
	}
	
	close(fd);

	return 0;
}

这样我们编译之后
./app 1 则灯亮
./app 0 则灯灭

大功告成

收尾

这一节相比之前其实就多了一个申请GPIO,配置GPIO,设置GPIO电平这么几个东西。

你可能感兴趣的:(iTop4412,Linux驱动篇)