查看原文
其他

搞驱动开发的比一般开发的薪资更高?由浅入深了解驱动开发

嵌入式ARM 2021-01-31


最近小编看了下嵌入式Linux驱动开发的招聘信息发现,其实一般的嵌入式开发工程师薪资貌似还不如嵌入式驱动开发工程师,前者基本全能:


嵌入式开发工程师


  • 精通精通单片机C语言编程,熟悉STM32和UCOS编程;

  • 熟悉485、CAN通讯、串口、网口、DDR、LCD等各种常用硬件接口&传感器设备的驱动开发和调试;

  • 熟练使用显示屏模块编辑软件;

  • 熟悉嵌入式Linux系统开发、驱动开发、应用程序开发和调试;

  • 有各种网络通信,wifi模组、Zigbee、蓝牙等智能硬件和智能穿戴开发经验者相关经验优先;

  • 2年以上相关工作经验,有手机、平板电脑、智能终端相关产品的硬件设计经验,有知名手机公司经验优先;


嵌入式Linux驱动工程师

  • 精通C编程语言

  • 熟悉Linux系统框架,了解硬件基本原理,开发调试过SPI ,I2C,PCM,USB,LCD设备驱动

  • 了解硬件基本原理,会看原理图和PCB板图

  • 有嵌入式及Linux应用及驱动企业开发经验



显然,做嵌入式驱动开发这块任务要轻松很多。而且,按照目前基本行情来说,搞Driver驱动的收入要比一般的开发工程师的收入要高一些,因为很多人想转这一行。


言归正题,今天我们来学学嵌入式Linux驱动开发实践,按照由易到难,由浅入深的顺序,就从LED开始。

 

LED驱动可以说是hello world之后最简单的驱动模块了。如果自己写一个LED驱动那是很简单的,其实用Linux内核中的leds子系统来做也是比较简单的,内核中的leds子系统是将led抽象成platform_device,并有leds_class。这样,在/sys/class/leds/目录下面就可以利用sysfs文件系统来实现LED的操作。其实,也可以在此基础上添加/dev访问的接口,甚至用不着包含mknod的脚本和udev等工具,在模块注册后就可以生成/dev下的设备文件。

        

一步一步的来,首先利用platform虚拟总线来实现sysfs文件系统下的LED操作.

        

在mach-smdk2440.c中新增platform_device如下:

1. static struct platform_device smdk_led5 = {  

2.     .name       = "s3c24xx_led",  

3.     .id     = 0,  

4.     .dev        = {  

5.         .platform_data = &smdk_pdata_led5,  

6.         .release = &platform_led_release,  

7.     },  

8. };  

对于具体的板子,是GPB5-8对应四个LED,所以这里smdk_pdata_led5的定义如下:

1. static struct s3c24xx_led_platdata smdk_pdata_led5 = {  

2.     .gpio       = S3C2410_GPB(5),  

3.     .flags      = S3C24XX_LEDF_ACTLOW ,//| S3C24XX_LEDF_TRISTATE,  

4.     .name       = "led5",  

5.     //.def_trigger  = "nand-disk",  

6. };  

接着在static struct platform_device *smdk2440_devices[] __initdata中新增&smdk_led5,这样系统在启动时就会由smdk2440_machine_init调用platform_add_devices(smdk2440_devices, ARRAY_SIZE(smdk2440_devices));来添加刚才加上的platform_device.

        

看完了platform_device再看下platform_driver是如何注册的:

        

在drivers/leds目录下的Makefile中有obj-$(CONFIG_LEDS_S3C24XX) += leds-s3c24xx.o

        

所以在make menuconfig的时候记得将其选为M.

        

这样,注册leds-s3c24xx.ko就会在/sys/class/leds中有      

led5  led6  led7  led8四个目录,进入led5目录,有

brightness devicepower trigger
dev max_brightnesssubsystemuevent        

执行echo 0 > brightness就可以关闭led,而echo 1 > brightness就可以打开led.

        

因为有leds_class,而在led-class.c中leds-init函数中有leds_class->dev_attrs = led_class_attrs;
        

其中,led_class_attrs定义如下

1. static struct device_attribute led_class_attrs[] = {  

2.     __ATTR(brightness, 0644, led_brightness_show, led_brightness_store),  

3.     __ATTR(max_brightness, 0444, led_max_brightness_show, NULL),  

4. #ifdef CONFIG_LEDS_TRIGGERS  

5.     __ATTR(trigger, 0644, led_trigger_show, led_trigger_store),  

6. #endif  

7.     __ATTR_NULL,  

8. };  

根据sysfs的属性文件,读写属性文件最终调用的就是show和store函数,在这里就是led_brightness_show和led_brightness_store函数。
        

顺着调用其实设置寄存器的动作是在leds-s3c24xx.c中的s3c24xx_led_set实现的。从这里可以看出,LEDS这个子系统也做了抽象,与平台相关的放在一个文件中,而与平台无关的抽象出来放在led-class中。

        

到此,就可以通过sysfs文件系统来访问led设备了。

        

那么,开头说的在此基础如何做/dev访问的接口呢?

        

在leds目录下新建一个文件led-dev.c

        

在Makefile中添加obj-$(CONFIG_LEDS_INTF_DEV) += led-dev.o

        

记得在Kconfig文件中也要添加对应的信息。

        

led-dev.c文件内容如下:

1. /* 

2.  * LEDS subsystem, dev interface 

3.  * 

4.  * Copyright (C) 2012  Baikal 

5.  * Author: Baikal <dndxhej@gmail.com> 

6.  * 

7.  * 

8.  * This program is free software; you can redistribute it and/or modify 

9.  * it under the terms of the GNU General Public License version 2 as 

10.  * published by the Free Software Foundation. 

11. */  

12.   

13.   

14. #include <linux/module.h>  

15. #include <linux/leds.h>  

16. #include <linux/sched.h>  

17.   

18. #include <linux/fs.h>  

19. #include <linux/device.h>  

20. #include <asm/uaccess.h>  

21. #include "leds.h"  

22.   

23. static dev_t led_devt;  

24.   

25. #define LED_DEV_MAX 8   /*actually,there is 4 leds on the 2440 board*/  

26.   

27.   

28. static int led_dev_open(struct inode *inode, struct file *file)  

29. {  

30.   

31.     printk(KERN_INFO "debug by baikal: led dev open\n");  

32.     printk(KERN_INFO "i_rdev:0x%x\n",inode->i_rdev);  

33.     printk(KERN_INFO "i_rdev:%d\n",inode->i_rdev);  

34.     printk(KERN_INFO "i_rdev:major:%d minor:%d\n",MAJOR(inode->i_rdev),MINOR(inode->i_rdev));  

35.     struct led_classdev *led = container_of(inode->i_cdev,  

36.                     struct led_classdev, char_dev);  

37.   

38.   

39.     file->private_data = led;  

40.   

41.   

42.     return 0;  

43. }  

44.   

45. static ssize_t  

46. led_dev_read(struct file *file, char __user *buf, size_t count, loff_t *ppos)  

47. {  

48.     printk(KERN_INFO "debug by baikal: led dev read\n");  

49. }  

50.   

51.   

52. static ssize_t  

53. led_dev_write(struct file *file, char __user *buf, size_t count, loff_t *ppos)  

54. {  

55.     char data;  

56.     printk(KERN_INFO "debug by baikal: led dev write\n");  

57.   

58.     copy_from_user(&data,buf,count);  

59.     if(data == '0')  

60.         led_set_brightness(file->private_data,0);  

61.     else  

62.         led_set_brightness(file->private_data,1);          

63. }  

64.   

65. static const struct file_operations led_dev_fops = {  

66.     .owner      = THIS_MODULE,  

67.     .read       = led_dev_read,  

68.     .write      = led_dev_write,  

69.     .open       = led_dev_open,  

70. //  .release    = led_dev_release,  

71.   

72. };  

73.   

74.   

75.   

76. void led_dev_prepare(struct led_classdev *led)  

77. {  

78.     static int minor = 0;  

79.     printk(KERN_INFO "debug by baikal: led dev pre\n");  

80.   

81.     led_devt = MKDEV(254,minor); //add by baikal  

82.     minor++;  

83.     led->dev->devt = MKDEV(MAJOR(led_devt), MINOR(led_devt));  

84.   

85.   

86.     cdev_init(&led->char_dev, &led_dev_fops);  

87.     //led->char_dev.owner = led->owner;  

88.   

89. }  

90.   

91. void led_dev_add_device(struct led_classdev *led)  

92. {  

93.     printk(KERN_INFO "debug by baikal: led dev add \n");  

94.   

95.     if (cdev_add(&led->char_dev, led->dev->devt, 1))  

96.         printk(KERN_WARNING " failed to add char device ");  

97.     else  

98.         pr_debug("%s: dev (%d:)\n", led->name,  

99.             MAJOR(led_devt));  

100. }  

101.   

102. void led_dev_del_device(struct led_classdev *led)  

103. {  

104.     if (led->dev->devt)  

105.         cdev_del(&led->char_dev);  

106. }  

107.   

108.   

109. void __init led_dev_init(void)  

110. {  

111. /* 

112.     int err; 

113.  

114.     err = alloc_chrdev_region(&led_devt, 0, LED_DEV_MAX, "led"); 

115.     if(err < 0) 

116.         printk(KERN_ERR "%s: failed to allocate char dev region\n",__FILE__); 

117. */  

118. }  

119.   

120. void __exit led_dev_exit(void)  

121. {  

122. /* 

123.     if(led_devt) 

124.         unregister_chrdev_region(led_devt, LED_DEV_MAX); 

125. */  

126. }  

并在led-class.c中的led_classdev_register函数的开头处添加

1. static int minor = 0;  

2. dev_t led = MKDEV(254,minor); //add by baikal  

3. printk(KERN_INFO "debug by baikal: led class reg 1\n");  

4.   

5.   

6. led_cdev->dev = kzalloc(sizeof(struct device), GFP_KERNEL);  

7. if (led_cdev->dev == NULL) {  

8.     return -ENOMEM;  

9. }  

10. led_dev_prepare(led_cdev);              //add by baikal  

11. printk(KERN_INFO "debug by baikal: led class reg 2\n");  

12.   

13. minor++;  

并将led_cdev->dev = device_create(leds_class, parent, 0, led_cdev,
     

"%s", led_cdev->name);

        

改为led_cdev->dev = device_create(leds_class, parent, led, led_cdev,
     

"%s", led_cdev->name);

在函数的结尾处再调用 led_dev_add_device(led_cdev);


这样,在模块注册后在/dev目录下就有


[root@BaikalHu led5]# ls /dev/l*
/dev/led5   /dev/led7   /dev/loop0  /dev/loop2  /dev/loop4  /dev/loop6
/dev/led6   /dev/led8   /dev/loop1  /dev/loop3  /dev/loop5  /dev/loop7


这样就在/dev下生成了设备文件。

        

可以照下面的方式来测试/dev下的led设备文件的功能:

1. #include <sys/types.h>  

2. #include <sys/stat.h>  

3. #include <fcntl.h>  

4. #include <stdio.h>  

5. #include <errno.h>  

6. #include <string.h>  

7.   

8. int main(int argc, char **argv)  

9. {  

10.     int fd;  

11.     if(argc != 3)  

12.     {  

13.         printf("usage : ./led led5-8 on/off\n");  

14.         return -1;  

15.     }  

16.   

17.     char file[20] = "/dev/";  

18.   

19.     strcat(file, argv[1]);  

20.   

21.     fd = open(file, O_RDWR);  

22.   

23.     if(fd < 0)  

24.     {     

25.         perror("open led");  

26.         return -1;  

27.     }  

28.   

29.     if(!strcmp(argv[2],"on"))  

30.     {  

31.         write(fd,"1",1);  

32.     }  

33.     else if(!strcmp(argv[2],"off"))  

34.     {  

35.         write(fd,"0",1);  

36.     }  

37.   

38.    

39.     close(fd);  

40.   

41.     return 0;  

42.   

43.   

44. }  

测试一切正常.

        

回头解释下为什么这样就可以自己生成/dev下的设备文件,原因在于在新内核中的device_add函数中有这么一段:

1. if (MAJOR(dev->devt)) {  

2.     error = device_create_file(dev, &devt_attr);  

3.     if (error)  

4.         goto ueventattrError;  

5.   

6.     error = device_create_sys_dev_entry(dev);  

7.     if (error)  

8.         goto devtattrError;  

9.   

10.     devtmpfs_create_node(dev);  

11. }  

看到devtmpfs_create_node了没,这就是答案。

        

通过自己的添加,可以用sysfs和dev两种接口方式来访问LED。但是实际设置寄存器的函数都是同一处地方。

        

linux下的驱动,最终还是要操作寄存器的,但是与裸机不同的是linux将硬件设备抽象为文件,那我们就必须按照这种机制来书写linux下的驱动,机制的根本是文件系统和设备模型,但是可以通过不同的策略来达到目的,比如sysfs和devtmpfs。


-END-




推荐阅读



【01】FreeRTOS 定时器组(基础知识必掌握)【02】主流嵌入式操作系统(RTOS)有哪些?看这14种系统【03】学得越多,发现自己知道的越少!Android与RTOS【04】月薪20K不是梦!学会RTOS给你的身价增增值,嵌入式操作系统UCOS-II总览【05】一位嵌入式er在RTOS项目组的开发经历


    您可能也对以下帖子感兴趣

    文章有问题?点此查看未经处理的缓存