9_【综合】编写LED控制驱动和系统应用

完整的LED驱动和上层控制应用

Posted by Jerry Chen on June 22, 2019

头文件

  • Linux中申请GPIO的头文件

    include/linux/gpio.h

  • 三星平台的GPIO配置函数的包含头文件

    arch/arm/plat-samsung/include/plat/gpio-cfg.h

  • EXYNOS平台GPIO配置函数传参定义的包含头文件

    arch/arm/mach-exynos/include/mach/gpio.h(在该头文件中已经引用了gpio-exynos4.h)

  • EXYNOS平台4412平台处理器引脚有关的宏

    arch/arm/mach-exynos/include/mach/gpio-exynos4.h

函数和宏

  • linux GPIO申请函数和赋值函数

    申请:gpio_request

    赋值:gpio_set_value

  • 三星平台配置GPIO函数和参数

    函数:s3c_gpio_cfgpin

    输出参数:S3C_GPIO_OUTPUT

编写之前

驱动probe函数中干的事情一般有:注册设备节点(其实任意地方均可注册)、申请资源(如GPIO)

开始

将leds驱动从内核去掉

本节内容按照这篇文章进行操作:这里

也就是取消leds编译进内核(取消宏定义,那么leds内核设备信息、驱动被取消编译),下面会重写一个led驱动和设备信息以模块加载进内核进行实验。

避免因为存在的leds驱动申请了GPIO资源而导致我们新写的led驱动在申请资源时失败。

完成后make zImage生成内核,进行烧录

编写led驱动和设备信息

驱动和设备信息匹配的文章参见: 这里(不过这个是设备信息自动注册到内核了),本节介绍的是编写模块注册驱动和设备信息。

编写led设备信息

加载该设备信息模块后,会在/sys/devices/platform/目录生成DEV_NAME的设备信息。

led_device.c :注册设备信息的文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#include <linux/init.h>
#include <linux/module.h>
#include <linux/platform_device.h>

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

#define DEV_NAME "platform_led"

static void led_release(struct device *pdv){
	printk(KERN_EMERG "led_release ok!\n");
};

static struct platform_device platform_dev_led = {
	.name	= DEV_NAME,
	.id		= -1,
	.dev	= {
		.release = led_release,
	},
};

static int led_init(void){
	/*注册到/sys/devices/platform目录*/
	platform_device_register(&platform_dev_led);
	return 0;
}
static void led_exit(void){
	platform_device_unregister(&platform_dev_led);
}

module_init(led_init);
module_exit(led_exit);

编写led驱动

这里只申请一个led灯的GPIO资源,当驱动DRV_NAME和设备信息中DEV_NAME匹配后就会执行probe函数:申请GPIO,设置输出模式,初始拉低,注册设备节点到dev目录;一旦失去匹配:释放GPIO,取消dev目录的注册。

led_driver.c :注册驱动和设备节点申请资源的文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
#include <linux/init.h>
#include <linux/module.h>
#include <linux/platform_device.h>

/*注册杂项设备设备节点的头文件*/
#include <linux/miscdevice.h>
/*文件操作的头文件*/
#include <linux/fs.h>

/*linux 申请的gpio头文件*/
#include <linux/gpio.h>
/*配置函数和参数配置的宏头文件*/
#include <plat/gpio-cfg.h>
/*配置参数头文件*/
#include <mach/gpio.h>
/*gpio引脚相关头文件,可不要,在上面的文件中已包含*/
//#include <mach/gpio-exynos4.h>

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

#define DRV_NAME "platform_led"
#define NODE_NAME "led"

static int led_open(struct inode *inode, struct file *file){
	printk(KERN_EMERG "led_open ok!\n");
	return 0;
}
static int led_release(struct inode *inode, struct file *file){
	printk(KERN_EMERG "led_release ok!\n");
	return 0;
}
static long led_ioctl(struct file *file, unsigned int cmd, unsigned long arg){
	printk(KERN_EMERG "cmd is %d,arg is %ld;\n",cmd,arg);
	
	if(cmd > 1){
		printk(KERN_EMERG "cmd is 0 or 1\n");
		return 1;
	}
	if(arg != 1){
		printk(KERN_EMERG "arg is only 1\n");
		return 1;
	}
	
	gpio_set_value(EXYNOS4_GPL2(0),cmd);
	
	return 0;
}

static struct file_operations led_ops = {
	.owner	= THIS_MODULE,
	.open	= led_open,			// open节点执行
	.release	= led_release,	// close节点执行
	.unlocked_ioctl	= led_ioctl,
};

static struct miscdevice misc_led = {
	.minor	= MISC_DYNAMIC_MINOR,	// 自动分配
	.name	= NODE_NAME,
	.fops	= &led_ops,
};

static int led_probe(struct platform_device *pdv){
	int ret;
	
	ret = gpio_request(EXYNOS4_GPL2(0),"LED"); /*申请GPIO资源*/
	if(ret < 0){
		printk(KERN_EMERG "gpio_request EXYNOS4_GPL2(0) failed!\n");
		return ret;
	}
	
	s3c_gpio_cfgpin(EXYNOS4_GPL2(0),S3C_GPIO_OUTPUT); /*设置输出模式*/
	gpio_set_value(EXYNOS4_GPL2(0),0); /*置低*/
	
	misc_register(&misc_led); /*注册到dev目录,可任意放*/
	
	printk(KERN_EMERG "led_probe ok!\n");
	return 0;
}

static int led_remove(struct platform_device *pdv){
	gpio_free(EXYNOS4_GPL2(0)); /*释放GPIO资源*/
	misc_deregister(&misc_led);
	
	printk(KERN_EMERG "led_remove ok!\n");
	return 0;
}

static void led_shutdown(struct platform_device *pdv){
	printk(KERN_EMERG "led_shutdown ok!\n");
}
static int led_suspend(struct platform_device *pdv, pm_message_t state){
	printk(KERN_EMERG "led_suspend ok!\n");
	return 0;
}
static int led_resume(struct platform_device *pdv){
	printk(KERN_EMERG "led_resume ok!\n");
	return 0;
}

static struct platform_driver platform_drv_led = {
	.probe	= led_probe,
	.remove	= led_remove,
	.shutdown	= led_shutdown,
	.suspend	= led_suspend,
	.resume	= led_resume,
	.driver	= {
		.name	= DRV_NAME,
		.owner	= THIS_MODULE,
	},
};

static int led_init(void){
	platform_driver_register(&platform_drv_led);
	return 0;
}
static void led_exit(void){
	platform_driver_unregister(&platform_drv_led);
}

module_init(led_init);
module_exit(led_exit);

进行编译和测试

  1. 对编写的驱动文件和设备信息文件进行模块编译,Makefile的编写可参考:Makefile模板

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    
    #!/bin/bash
       
    obj-m += led_device.o
    obj-m += led_driver.o
       
    KDIR := /home/jerry/Projects/iTop4412_Kernel_3.0
       
    PWD ?= $(shell pwd)
       
    all:
    	make -C $(KDIR) M=$(PWD) modules
       
    clean:
    	rm -rf *.o
       
    
  2. 编写led系统编程测试文件,并并交叉编译为可执行文件led_test

    led_test.c :系统编程中调用生成的设备节点的文件,操作led闪烁

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    
    #include <stdio.h> // 包含打印函数printf
    #include <stdlib.h> // 包含exit函数
    #include <sys/types.h> // 一下基本数据类型的宏定义
    #include <fcntl.h> // 包含open函数
    #include <unistd.h> // 包含close函数
    #include <sys/ioctl.h> // 包含ioctl函数
       
    int main(){
    	int fd;
    	char *led_node = "/dev/led";
       	
    	// 可读可写,非阻塞方式
    	if((fd = open(led_node,O_RDWR|O_NDELAY)) < 0){
    		printf("Test: Failed to open %s!\n",led_node);
    		exit(1);
    	}
    	printf("Test: Open %s successfully!\n",led_node);
       	
    	// 控制一个led 亮 灭 亮
    	ioctl(fd,1,1); // 高
    	sleep(2);
    	ioctl(fd,0,1); // 低
    	sleep(2);
    	ioctl(fd,1,1); // 高
       
    	close(fd);
    	return 0;
    }
    
  3. 准备好所有文件后,进行测试

    编译前:

    编译后:

    1. 拷贝驱动模块、设备信息模块、可执行测试程序到iTop4412(推荐nfs
    2. 挂载设备信息模块、驱动模块,然后执行测试程序