linux设备驱动归纳总结(五):4.写个简单的LED驱动

linux 设备驱动归纳总结(五): 4. 写个简单的 LED 驱动


xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

在上面的章节的知识,已经能够实现个简单的LED驱动。居于前面操作LED的函数(5th_mm_2/3rd/test.c),我一步一步来修改。

xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx


一、实现硬件操作函数


一般的,我写驱动的时候,我会先确定一些基本的硬件操作函数能够使用。LED驱动我要实现三个操作:配置、开灯和关灯,所以我先要实现这几个硬件操作函数。

其实这些在我介绍IO内存时已经实现了(5th_mm_2/3rd/test.c),我只是稍作了一点修改,改了一下内存的数据类型,其实没什么大出入。

/*5th_mm_4/1st/test.c*/

1 #include <linux/module.h>

2 #include <linux/init.h>

3

4 #include <asm/io.h>

5 #include <linux/ioport.h>

6

7 unsigned long virt, phys;

8 unsigned long gpecon, gpedat, gpeup; //其实我就改了这里的数据类型,其实都是用来存放地址

9 unsigned long reg;//没有多大的影响。

10 struct resource *led_resource;

11

12 void s3c_led_config(void) //还将函数的名字改成好听点

13 {

14 reg = ioread32(gpecon);

15 reg &= ~(3 << 24);

16 reg |= (1 << 24);

17 iowrite32(reg, gpecon);

18

19 reg = ioread32(gpeup);

20 reg &= ~(3 << 12);

21 iowrite32(reg, gpeup);

22 }

23

24 void s3c_led_on(void)

25 {

26 reg = ioread32(gpedat);

27 reg &= ~(1 << 12);

28 iowrite32(reg, gpedat);

29 }

30

31 void s3c_led_off(void)

32 {

33 reg = ioread32(gpedat);

34 reg |= (1 << 12);

35 iowrite32(reg, gpedat);

36 }

37

38 void init_led_device(void)

39 {

40 phys = 0x56000000;

41 virt = (unsigned long)ioremap(phys, 0x0c);

42

43 gpecon = virt + 0x40;

44 gpedat = virt + 0x44;

45 gpeup = virt + 0x48;

46 }

47

48 static int __init test_init(void) //模块初始化函数

49 {

50 init_led_device();

51

52 led_resource = request_mem_region(phys, 0x0c, "LED_MEM");

53 if(NULL == led_resource){

54 printk("request mem error!\n");

55 return - ENOMEM;

56 }

57

58 s3c_led_config();

59 s3c_led_on();

60 printk("hello led!\n");

61 return 0;

62 }

63

64 static void __exit test_exit(void) //模块卸载函数

65 {

66 if(NULL != led_resource){

67 s3c_led_off();

68 iounmap((void *)virt);

69 release_mem_region(phys, 0x0c);

70 }

71 printk("bye\n");

72 }

73

74 module_init(test_init);

75 module_exit(test_exit);

76

77 MODULE_LICENSE("GPL");

78 MODULE_AUTHOR("xoao bai");

79 MODULE_VERSION("v0.1");

至于验证我就不做了,效果还是一样,加载模块灯亮,卸载模块灯灭。


xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx


二、面向对象思想——定义一个LED的结构体


上面的函数中,一大堆的全局变量实在让人看起来不舒服。我在第三章字符设备的文中介绍过,把这些变量定义在一个结构体中,方便以后引用,如函数传参

/*5th_mm_4/2nd/test.c*/

7 struct _led_t{

8 //hardware obb

9 unsigned long virt, phys;

10 unsigned long gpecon, gpedat, gpeup;

11 unsigned long reg;

12 struct resource *led_resource;

13

14 void (*config)(struct _led_t *);//这里把LED驱动的三个操作函数指针也放进去

15 void (*on)(struct _led_t *);

16 void (*off)(struct _led_t *);

17 };

根据上面定义的数据结构,我再修改一下1st目录的程序,就成了2nd目录中的函数。现在函数做了两步:

1)实现硬件的基本操作。

2)定义了一个面向对象数据类型。


xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx


三、实现硬件设备初始化函数和注销函数


在对硬件进程操作(配置,开灯、关灯)之前,需要先进行IO内存映射等操作,前面的函数写得很零散,这里我整理了一下:

1)当插入模块时,需要进行一些内存映射等设备初始化操作,使用函数init_led_device

2)当卸载模块时,需要进行一些硬件注销操作,使用函数eixt_led_device


接下来就要封装这两个函数:

/*5th_mm_4/3rd/test.c */

45 int init_led_device(struct _led_t *led)

46 {

47 led->phys = 0x56000000;//1指定物理地址

48

49 led->led_resource = request_mem_region(led->phys, 0x0c, "LED_MEM");

50 if(NULL == led->led_resource){//2申请内存区域

51 return - 1;

52 }

53

54 led->virt = (unsigned long)ioremap(led->phys, 0x0c);//3内存映射

55

56 led->gpecon = led->virt + 0x40;//4指定寄存器地址

57 led->gpedat = led->virt + 0x44;

58 led->gpeup = led->virt + 0x48;

59

60 led->config = s3c_led_config;//5将操作函数也放进结构体成员

61 led->on = s3c_led_on;

62 led->off = s3c_led_off;

63

64 return 0;

65 }

66

67 void exit_led_device(struct _led_t *led)

68 {

69 if(NULL != led->led_resource){

70iounmap((void *)led->virt);

71release_mem_region(led->phys, 0x0c);

72 }

73 }

74

75 struct _led_t my_led;

76

77 static int __init test_init(void) //模块初始化函数

78 {

79 if (-1 == init_led_device(&my_led)){ //加载模块时就调用init_led_device

80 printk("request mem error!\n");

81 return - ENOMEM;

82 }

83

84my_led.config(&my_led); //这里调用操作函数是多于了,我迟点会放在ioctl

85my_led.on(&my_led); //这里只不过加载时候灯亮一下,让我知道加载成功

86 printk("hello led!\n");

87 return 0;

88 }

89

90 static void __exit test_exit(void) //模块卸载函数

91 {

92 my_led.off(&my_led);

93 exit_led_device(&my_led); //卸载时调用exit_led_device

94 printk("bye\n");

95 }

至于验证我就不做了,效果还是一样,加载模块灯亮,卸载模块灯灭。


xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx


四、实现字符设备的申请,即模块与内核的接口


需要实现ioctl功能,首先要这个设备需要先注册,使用字符设备注册的知识:

字符设备注册三步曲:

/*5th_mm_4/4th/test.c*/

18 struct _led_t{

19 //hardware obb

20 unsigned long virt, phys;

21 unsigned long gpecon, gpedat, gpeup;

22 unsigned long reg;

23 struct resource *led_resource;

24

25 void (*config)(struct _led_t *);

26 void (*on)(struct _led_t *);

27 void (*off)(struct _led_t *);

28

29 //kernel oob

30 dev_t devno; //往结构体添加了两个成员

31 struct cdev led_cdev;

32 };

。。。。。。

90 struct _led_t my_led;

91 struct file_operations s3c_led_fops = {

92 //暂时还是空的

93 };

94

95 static int __init led_driver__init(void) //模块初始化函数

96 {

97 int ret;

98

99 ret = init_led_device(&my_led);

100 if (ret){

101 P_DEBUG("request mem error!\n");

102 ret = - ENOMEM;

103 goto err0;

104 }

105

106ret = alloc_chrdev_region(&my_led.devno, 0, 1, "s3c_led_driver"); //1申请cdev

107 if (ret){

108 P_DEBUG("alloc chrdev failed!\n");

109 goto err1;

110 }

111 P_DEBUG("major[%d], minor[%d]\n", MAJOR(my_led.devno), MINOR(my_led.devno));

112

113 cdev_init(&my_led.led_cdev, &s3c_led_fops); //2初始化cdev

114

115 ret = cdev_add(&my_led.led_cdev, my_led.devno, 1); //3添加cdev

116 if (ret){

117 P_DEBUG("cdev_add failed!\n");

118 goto err2;

119 }

120

121 my_led.config(&my_led);

122 my_led.on(&my_led);

123 P_DEBUG("hello led!\n");

124 return 0;

125

126 err2:

127 unregister_chrdev_region(my_led.devno, 1);

128 err1:

129 exit_led_device(&my_led);

130 err0:

131 return ret;

132 }

133

134 static void __exit led_driver__exit(void) //模块卸载函数

135 {

136 my_led.off(&my_led);

137

138 unregister_chrdev_region(my_led.devno, 1); //卸载是注销cdev结构

139 exit_led_device(&my_led);

140 P_DEBUG("bye\n");

141 }


这里就可以验证一下了:

[root: 4th]# insmod test.ko

<kernel>[led_driver__init]major[253], minor[0]//申请成功的设备号

<kernel>[led_driver__init]hello led!

[root: 4th]# rmmod test

<kernel>[led_driver__exit]bye

既然设备申请成功,接下来就是要实现系统调用接口了。


xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx


五、实现系统调用对应的函数ioctl


在这里,我需要实现的内容是,在应用层使用ioctl系统调用,可以操作LED配置、打开和关闭。接下来实现文件操作结构体中的ioctl

1首先要定义命令:

/*5th_mm_4/5th/led_ioctl.h*/

1 #ifndef _LED_H

2 #define _LED_H

3

4 #define LED_MAGIC 'x'

5 #define LED_CONF _IO(LED_MAGIC, 0)

6 #define LED_ON _IO(LED_MAGIC, 1)

7 #define LED_OFF _IO(LED_MAGIC, 2)

8

9 #endif /* _LED_H */

2接着实现文件操作结构体中的ioctl

/*5th_mm_4/5th/led_driver.c */ //这里我把文件的名字改了

92 int s3c_led_ioctl(struct inode *node, struct file *filp, unsigned int cmd, unsign ed long args)

93 {

94 int ret;

95 struct _led_t *dev = container_of(node->i_cdev, struct _led_t, led_cdev);

96 switch(cmd){

97 case LED_CONF:

98 dev->config(dev);

99 break;

100 case LED_ON:

101 dev->on(dev);

102 break;

103 case LED_OFF:

104 dev->off(dev);

105 break;

106 default:

107 P_DEBUG("unknow cmd!\n");

108 ret = - EINVAL;

109 goto err0;

110 }

111 return 0;

112

113 err0:

114 return ret;

115 }

116

117 struct _led_t my_led;

118 struct file_operations s3c_led_fops = {

119 .ioctl = s3c_led_ioctl, //一定要加上。打开和关闭操作我不实现,使用默认的

120 };

3接着实现应用层函数:

1 #include <stdio.h>

2 #include <sys/types.h>

3 #include <sys/stat.h>

4 #include <fcntl.h>

5 #include <strings.h>

6 #include <sys/ioctl.h>

7

8 #include "led_ioctl.h"

9

10 int main(int argc, char *argv[])

11 {

12 int fd;

13 fd = open("/dev/led_driver", O_RDWR);

14 if(fd < 0){

15 perror("open");

16 return -1;

17 }

18

19 ioctl(fd, LED_CONF);

20

21 if(!strncasecmp("on", argv[1], 3))

22 ioctl(fd, LED_ON);

23

24 if(!strncasecmp("off", argv[1], 3))

25 ioctl(fd, LED_OFF);

26

27

28 return 0;

29 }

验证一下:

[root: 5th]# insmod led_driver.ko

<kernel>[led_driver__init]major[253], minor[0]

<kernel>[led_driver__init]hello led!

[root: 5th]# mknod /dev/led_driver c 253 0

[root: 5th]# ./app on //亮灯

[root: 5th]# ./app off //灭灯

[root: 5th]# rmmod led_driver

<kernel>[led_driver__exit]bye


xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx


六、使用信号量


其实在单处理器非抢占内核下,是没有必要使用到内核同步机制的,这里使用信号量来限制只能同时一个进程打开并操作led设备文件。实现的方法就是在打开的时候使用信号量:

/*5th_mm_4/6th/led_driver.c*/

20 struct _led_t{

21 //hardware obb

22 unsigned long virt, phys;

23 unsigned long gpecon, gpedat, gpeup;

24 unsigned long reg;

25 struct resource *led_resource;

26

27 void (*config)(struct _led_t *);

28 void (*on)(struct _led_t *);

29 void (*off)(struct _led_t *);

30

31 //kernel oob

32 dev_t devno;

33 struct cdev led_cdev;

34 struct semaphore led_sem; //非抢占下,其实单纯使用一个标志flag来实现也行,

35 };//文件打开减一,关闭加一,flag不为零时可打开。

。。。。。。。。

63 int init_led_device(struct _led_t *led)

64 {

65 led->phys = 0x56000000;

66

67 led->led_resource = request_mem_region(led->phys, 0x0c, "LED_MEM");

68 if(NULL == led->led_resource){

69 return - 1;

70 }

71

72 led->virt = (unsigned long)ioremap(led->phys, 0x0c);

73

74 led->gpecon = led->virt + 0x40;

75 led->gpedat = led->virt + 0x44;

76 led->gpeup = led->virt + 0x48;

77

78 led->config = s3c_led_config;

79 led->on = s3c_led_on;

80 led->off = s3c_led_off;

81

82 sema_init(&led->led_sem, 1);

83

84 return 0;

85 }

。。。。。。。

120 int s3c_led_open (struct inode *node, struct file *filp)

121 {

122 struct _led_t *dev = container_of(node->i_cdev, struct _led_t, led_cdev);

123 filp->private_data = dev;

124

125 if (down_trylock(&dev->led_sem)){ //获得锁

126 P_DEBUG("led busy!\n");

127 return - EBUSY;

128 }

129

130 return 0;

131 }

132

133 int s3c_led_release (struct inode *node, struct file *filp)

134 {

135 struct _led_t *dev = filp->private_data;

136 up(&dev->led_sem); //释放锁

137 return 0;

138 }

139

140

141 struct _led_t my_led;

142 struct file_operations s3c_led_fops = {

143 .ioctl = s3c_led_ioctl,

144 .open = s3c_led_open,

145 .release = s3c_led_release,

146 };

为了验证,修改一下应用程序,使程序陷入死循环不退出:

/*5th_mm_4/6th/app.c*/

2 #include <sys/types.h>

3 #include <sys/stat.h>

4 #include <fcntl.h>

5 #include <strings.h>

6 #include <sys/ioctl.h>

7

8 #include "led_ioctl.h"

9

10 int main(int argc, char *argv[])

11 {

12 int fd;

13 fd = open("/dev/led_driver", O_RDWR);

14 if(fd < 0){

15 perror("open");

16 return -1;

17 }

18

19 ioctl(fd, LED_CONF);

20

21 if(!strncasecmp("on", argv[1], 3))

22 ioctl(fd, LED_ON);

23

24 if(!strncasecmp("off", argv[1], 3))

25 ioctl(fd, LED_OFF);

26

27 while(1)

28 {

29 ;

30 }

31

32 close(fd);

33 return 0;

34 }

也来验证一下:

[root: 6th]# insmod led_driver.ko

<kernel>[led_driver__init]major[253], minor[0]

<kernel>[led_driver__init]hello led!

[root: 6th]# mknod /dev/led_driver c 253 0

[root: 6th]# ./app on & //后台开灯

[root: 6th]# ./app off //在灭灯

<kernel>[s3c_led_open]led busy! //灭灯进程无法打开设备文件,返回错误

open: Device or resource busy

[root: 6th]# rmmod led_driver

<kernel>[led_driver__exit]bye


这样,一个简单的LED驱动就实现了,大家也可以尝试将my_led结构体通过kmalloc来申请,我只是觉得这个结构体占用的空间不多,就把这个步骤免了。


xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx


七、总结


上面的驱动我是按以下顺序写的:

1)实现硬件操作config,on.off

2)定义面向对象数据结构

3)定义硬件初始化操作

4)实现字符设备注册

5)实现ioctl等字符设备操作

6)实现信号量限制打开文件个数


上面介绍了我写驱动函数的步骤,其实最先的步骤应该是定义面向对象的数据结构,在开始实现其他的函数操作,只不过我之前已经将部分的硬件操作函数写好了,所以就稍稍改了前三步的步骤。接下来总结一下:

顺序不是一成不变的,但无论怎么写,也要按照从底层到上层,逐个逐个往上封装。

当然,这个驱动只是我结合了之前学的知识写的,内核中的驱动不可能这么简单,


xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

源代码: 5th_mm_4.rar

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
一、主要的显示屏IC IC(1)74HC04的作用:是一个6位反相器。 第7脚GND,电源地。 第14脚VCC,电源正极。 信号由A端输入Y端反相输出, A1与Y1为一组,其它类推。例:A1=“1”则Y1=“0”、A1=“0”则Y1=“1”,其它组功能一样。IC(2)74HC138的作用:八位二进制译十进制译码器。 第8脚GND,电源地。 第15脚VCC,电源正极 第1~3脚A、B、C,二进制输入脚。 第4~6脚片选信号控制,只有在4、5脚为“0”6脚为“1”时,才会被选通,输出受A、B、C信号控制。其它任何组合方式将不被选通,且Y0~Y7输出全“1”。通过控制选通脚来级联,使之扩展到十六位。例:G2A=0,G2B=0,G1=1,A=1,B=0,C=0,则Y0为“0”Y1~Y7为“1”。 IC(3)4HC595的作用:LED驱动芯片,8位移位锁存器。 第8脚GND,电源地。 第16脚VCC,电源正极 第14脚DATA,串行数据输入口,显示数据由此进入,必须有时钟信号的配合才能移入。第13脚EN,使能口,当该引脚上为“1”时QA~QH口全部为“1”,为“0”时QA~QH的输出由输入的数据控制。 第12脚STB,锁存口,当输入的数据在传入寄存器后,只有供给一个锁存信号才能将移入的数据送QA~QH口输出。 第11脚CLK,时钟口,每一个时钟信号将移入一位数据到寄存器。 第10脚RESET,复位口,只要有复位信号,寄存器内移入的数据将清空,显示屏不用该脚,一般接VCC。 第9脚DOUT,串行数据输出端,将数据传到下一个。 第15、1~7脚,并行输出口也就是驱动输出口,驱动LED。 IC(4)953的作用:行驱动管,功率管。其内部是两个CMOS管,1、3脚VCC,2、4脚控制脚,2脚控制7、8脚的输出,4脚控制5、6脚的输出,只有当2、4脚为“0”时,7、8、5、6才会输出,否则输出为高阻状态。 IC(5)TB62726的作用:LED驱动芯片,16位移位锁存器。 第1脚GND,电源地。 第24脚VCC,电源正极 第2脚DATA,串行数据输入 第3脚CLK,时钟输入. 第4脚STB,锁存输入. 第23脚输出电流调整端,接电阻调整 第22脚DOUT,串行数据输出 第21脚EN,使能输入其它功能与74HC595相似,只是TB62726是16位移位锁存器,并带输出电流调整功能,但在并行输出口上不会出现高电平,只有高阻状态和低电平状态。74HC595并行输出口有高电平和低电平输出.。TB62726与5026的引脚功能一样,结构相似。 二、LED显示屏常见信号的了解 1、CLK时钟信号:提供给移位寄存器的移位脉冲,每一个脉冲将引起数据移入或移出一位。数据口上的数据必须与时钟信号协调才能正常传送数据,数据信号的频率必须是时钟信号的频率的1/2倍。在任何情况下,当时钟信号有异常时,会使整板显示杂乱无章。 2、STB锁存信号:将移位寄存器内的数据送到锁存器,并将其数据内容通过驱动电路点亮LED显示出来。但由于驱动电路受EN使能信号控制,其点亮的前提必须是使能为开启状态。锁存信号也须要与时钟信号协调才能显示出完整的图象。在任何情况下,当锁存信号有异常时,会使整板显示杂乱无章。 3、EN使能信号:整屏亮度控制信号,也用于显示屏消隐。只要调整它的占空比就可以控制亮度的变化。当使能信号出现异常时,整屏将会出现不亮、暗亮或拖尾等现象。数据信号:提供显示图象所需要的数据。必须与时钟信号协调才能将数据传送到任何一个显示点。一般在显示屏中红绿蓝的数据信号分离开来,若某数据信号短路到正极或负极时,则对应的该颜色将会出现全亮或不亮,当数据信号被悬空时对应的颜色显示情况不定。 4、ABCD行信号:只有在动态扫描显示时才存在,ABCD其实是二进制数,A是最低位,如果用二进制表示ABCD信号控制最大范围是16行(1111),1/4扫描中只要AB信号就可以了,因为AB信号的表示范围是4行(11)。当行控制信号出现异常时,将会出现显示错位、高亮或图像重叠等现象。 三、常见故障处理手段(工具:万用表、电烙铁、刀片、螺丝刀、镊子……等。) 四、LED电子显示屏的维修方法判断问题必须先主后次方式的处理,将明显的、严重的先处理,小问题后处理。短路应为最高优先级。 1、电阻检测法,将万用表调到电阻档,检测一块正常的电路板的某点的到地电阻值,再检测另一块相同的电路板的同一个点测试与正常的电阻值是否有不同,若不同则就确定了问题的范围。 2、电压检测法,将万用表调到电压档,检测怀疑有问题的电路的某个点的到地电压,比较是否与正常值相似,否则确定了问题的范围。 3、短路检测法,将万用表调到短路检测挡(有的是二极管压降档或是电阻档,一般具有报警功能),检测是否有短路的现象出现,发现短路后应优先解决,使之
以下是一个简单Linux设备驱动程序,用于点亮LED灯。该程序使用了GPIO子系统和字符设备框架。 ```c #include <linux/kernel.h> #include <linux/module.h> #include <linux/fs.h> #include <linux/gpio.h> #include <asm/uaccess.h> #define LED_PIN 17 // 在树莓派上,GPIO17对应的针脚可以用来控制LED灯 static int led_value = 0; // LED灯的状态,0表示关闭,1表示打开 static dev_t dev_num; // 设备号 static struct class *dev_class; // 设备类别 static struct device *dev; // 设备 // 打开设备 static int led_open(struct inode *inode, struct file *file) { return 0; } // 从设备中读取数据 static ssize_t led_read(struct file *file, char __user *buf, size_t count, loff_t *ppos) { return 0; } // 向设备中写入数据 static ssize_t led_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos) { char kbuf; // 从用户空间中读取一个字节的数据 if (copy_from_user(&kbuf, buf, 1)) return -EFAULT; // 根据读取的数据来控制LED灯 if (kbuf == '0') { gpio_set_value(LED_PIN, 0); led_value = 0; } else if (kbuf == '1') { gpio_set_value(LED_PIN, 1); led_value = 1; } return 1; } // 关闭设备 static int led_release(struct inode *inode, struct file *file) { return 0; } // 设备操作函数 static struct file_operations led_fops = { .owner = THIS_MODULE, .open = led_open, .read = led_read, .write = led_write, .release = led_release, }; // 初始化设备驱动程序 static int __init led_init(void) { int ret; // 申请GPIO资源 ret = gpio_request(LED_PIN, "LED"); if (ret < 0) { printk(KERN_ERR "Failed to request GPIO %d: %d\n", LED_PIN, ret); return ret; } // 设置GPIO方向为输出 ret = gpio_direction_output(LED_PIN, 0); if (ret < 0) { printk(KERN_ERR "Failed to set GPIO %d direction: %d\n", LED_PIN, ret); gpio_free(LED_PIN); return ret; } // 注册字符设备驱动程序 ret = alloc_chrdev_region(&dev_num, 0, 1, "led"); if (ret < 0) { printk(KERN_ERR "Failed to allocate device number: %d\n", ret); gpio_free(LED_PIN); return ret; } // 创建设备类别 dev_class = class_create(THIS_MODULE, "led"); if (IS_ERR(dev_class)) { printk(KERN_ERR "Failed to create device class\n"); unregister_chrdev_region(dev_num, 1); gpio_free(LED_PIN); return PTR_ERR(dev_class); } // 创建设备 dev = device_create(dev_class, NULL, dev_num, NULL, "led"); if (IS_ERR(dev)) { printk(KERN_ERR "Failed to create device\n"); class_destroy(dev_class); unregister_chrdev_region(dev_num, 1); gpio_free(LED_PIN); return PTR_ERR(dev); } // 注册设备操作函数 cdev_init(&led_cdev, &led_fops); ret = cdev_add(&led_cdev, dev_num, 1); if (ret < 0) { printk(KERN_ERR "Failed to add device to kernel: %d\n", ret); device_destroy(dev_class, dev_num); class_destroy(dev_class); unregister_chrdev_region(dev_num, 1); gpio_free(LED_PIN); return ret; } printk(KERN_INFO "LED device driver initialized\n"); return 0; } // 卸载设备驱动程序 static void __exit led_exit(void) { // 删除字符设备 cdev_del(&led_cdev); // 销毁设备 device_destroy(dev_class, dev_num); // 销毁设备类别 class_destroy(dev_class); // 释放设备号 unregister_chrdev_region(dev_num, 1); // 释放GPIO资源 gpio_free(LED_PIN); printk(KERN_INFO "LED device driver unloaded\n"); } module_init(led_init); module_exit(led_exit); MODULE_LICENSE("GPL"); MODULE_AUTHOR("Your Name"); MODULE_DESCRIPTION("LED device driver"); ``` 在上述代码中,我们使用`gpio_request()`函数来申请GPIO资源,并使用`gpio_direction_output()`函数将GPIO设置为输出模式。在`led_write()`函数中,我们根据用户空间中读取的数据来控制LED灯的状态。在`led_init()`函数中,我们先申请GPIO资源,然后创建字符设备,并将其注册到内核中。在`led_exit()`函数中,我们释放了GPIO资源,并删除了字符设备

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值