以下都是我刚开始看驱动视频的个人强行解读,如果有误请指出,共同进步。
- 编写一个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的初始化过程。
之前也提到过,对于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电平这么几个东西。