当前位置:   article > 正文

2.学习Camera之——Linux驱动基础_linux camera驱动调试

linux camera驱动调试

Linux的三大类驱动: 

 STM32裸机开发与嵌入式Linux开发的一些区别:

 

  1. 嵌入式Linux的开发方式与STM32裸机开发的方式有点不一样。在STM32的裸机开发中,驱动层与应用
  2. 层的区分可能没有那么明显,常常都杂揉在一起。当然,有些很有水平的裸机程序分层分得还是很明显的。
  3. 但是,在嵌入式Linux中,驱动和应用的分层是特别明显的,最直观的感受就是驱动程序是一个.c文件里,
  4. 应用程序是另一个.c文件。比如我们这个hello驱动实验中,我们的驱动程序为hello_drv.c、应用程序为hello_app.c。
  5. 驱动模块的加载有两种方式:第一种方式是动态加载的方式,即驱动程序与内核分开编译,在内核运行的过程
  6. 中加载;第二种方式是静态加载的方式,即驱动程序与内核一同编译,在内核启动过程中加载驱动。在调试驱
  7. 动阶段常常选用第一种方式,因为较为方便;在调试完成之后才采用第二种方式与内核一同编译。
  8. STM32裸机开发与嵌入式Linux开发还有一点不同的就是:STM32裸机开发最终要烧到板子的常常只有一个文件
  9. (除开含有IAP程序的情况或者其它情况),嵌入式Linux就需要分开编译、烧写。
  1. Linux字符设备驱动框架
  2. 我们先看一个图:

  1. 当我们的应用在调用openclosewriteread等函数时,为什么就能操控硬件设备。那是因为有驱动层在
  2. 支撑着与硬件相关的操作,应用程序在调用打开、关闭、读、写等操作会触发相应的驱动层函数。
  3. 本篇笔记我们以hello驱动做分享,hello驱动属于字符设备。实现的驱动函数大概是怎么样的是有套路可寻
  4. 的,这个套路在内核文件include/linux/fs.h中,这个文件中有如下结构体:

  1. 这个结构体里的成员都是些函数指针变量,我们需要根据实际的设备确定我们需要创建哪些驱动函数实体。
  2. 比如我们的hello驱动的几个基本的函数(打开/关闭//写)可创建为:
  3. 1)打开操作
  4. static int hello_drv_open (struct inode *node, struct file *file)
  5. {
  6. printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
  7. return 0;
  8. }
  9. 打开函数的两个形参的类型要与struct file_operations结构体里open成员的形参类型一致,里面有一句
  10. 打印语句,方便直观地看到驱动的运行过程。
  11. 2)关闭操作
  12. static int hello_drv_close (struct inode *node, struct file *file)
  13. {
  14. printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
  15. return 0;
  16. }
  17. 3)读操作
  18. static ssize_t hello_drv_read (struct file *file, char __user *buf, size_t size, loff_t
  19. *offset)
  20. {
  21. int err;
  22. printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
  23. err = copy_to_user(buf, kernel_buf, MIN(1024, size));
  24. return MIN(1024, size);
  25. }
  26. copy_to_user函数的原型为:
  27. static inline int copy_to_user(void __user *to, const void *from, unsigned long n);
  28. 用该函数来读取内核空间(kernel_buf)的数据给到用户空间(buf)。 另外,kernel_buf的定义如下:
  29. static char kernel_buf[1024];
  30. MIN为宏:
  31. #define MIN(a, b) (a < b ? a : b)
  32. 把MIN(1024, size)作为copy_to_user的实参意在对拷贝的数据长度做限制(不能超出kernel_buf的大
  33. 小)。
  34. 4)写操作
  35. static ssize_t hello_drv_write (struct file *file, const char __user *buf, size_t size,
  36. loff_t *offset)
  37. {
  38. int err;
  39. printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
  40. err = copy_from_user(kernel_buf, buf, MIN(1024, size));
  41. return MIN(1024, size);
  42. }
  43. copy_from_user函数的原型为:
  44. static inline int copy_from_user(void *to,const void __user volatile *from,unsigned long
  45. n)
  46. 用该函数来将用户空间(buf)的数据传送到内核空间(kernel_buf)。
  47. 有了这些驱动函数,就可以给到一个struct file_operations类型的结构体变量hello_drv,如:
  48. static struct file_operations hello_drv =
  49. {
  50. .owner = THIS_MODULE,
  51. .open = hello_drv_open,
  52. .read = hello_drv_read,
  53. .write = hello_drv_write,
  54. .release = hello_drv_close,
  55. };
  56. 上面这个结构体变量hello_drv容纳了我们hello设备的驱动接口,最终我们要把这个hello_drv注册给
  57. Linux内核,套路就是这样的:把驱动程序注册给内核,之后我们的应用程序就可以使用
  58. open/close/write/read等函数来操控我们的设备,Linux内核在这里起到一个中间人的作用,把两头的
  59. 驱动与应用协调得很好。
  60. 我们前面说了驱动的装载方式之一的动态装载:把驱动程序编译成模块,再动态装载。动态装载的体现就
  61. 是开发板已经启动运行了Linux内核,我们通过开发板串口终端使用命令来装载驱动。装载驱动有两个命令,
  62. 比如装载我们的hello驱动:
  63. 方法一:insmod hello_drv.ko
  64. 方法二:modprobe hello_drv.ko
  65. 其中modprobe命令不仅能装载当前驱动,而且还会同时装载与当前驱动相关的依赖驱动。有了转载就有卸载,
  66. 也有两种方式:
  67. 方法一:rmmod hello_drv.ko
  68. 方法二:modprobe -r hello_drv.ko
  69. 其中modprobe命令不仅卸载当前驱动,也会同时卸载依赖驱动。
  70. 我们在串口终端调用装载与卸载驱动的命令,怎么就会执行装载与卸载操作。对应到驱动程序里我们有如
  71. 下两个函数:
  72. module_init(hello_init); //注册模块加载函数
  73. module_exit(hello_exit); //注册模块卸载函数
  74. 这里加载与注册有用到hello_init、hello_exit函数,我们前面说的把hello_drv驱动注册到内核就是
  75. 在hello_init函数里做,如:
  76. static int __init hello_init(void)
  77. {
  78. int err;
  79. printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
  80. /* 注册hello驱动 */
  81. major = register_chrdev(0, /* 主设备号,为0则系统自动分配 */
  82. "hello", /* 设备名称 */
  83. &hello_drv); /* 驱动程序 */
  84. /* 下面操作是为了在/dev目录中生成一个hello设备节点 */
  85. /* 创建一个类 */
  86. hello_class = class_create(THIS_MODULE, "hello_class");
  87. err = PTR_ERR(hello_class);
  88. if (IS_ERR(hello_class)) {
  89. printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
  90. unregister_chrdev(major, "hello");
  91. return -1;
  92. }
  93. /* 创建设备,该设备创建在hello_class类下面 */
  94. device_create(hello_class, NULL, MKDEV(major, 0), NULL, "hello"); /* /dev/hello */
  95. return 0;
  96. }
  97. 这里这个驱动程序入口函数hello_init中注册完驱动程序之后,同时通过下面连个创建操作来创建设备
  98. 节点,即在/dev目录下生成设备文件。据我了解,在之前版本的Linux内核中,设备节点需要手动创建,
  99. 即通过创建节点命令mknod 在/dev目录下自己手动创建设备文件。既然已经有新的方式创建节点了,这里
  100. 就不抠之前的内容了。
  101. 以上就是分享关于驱动一些内容,通过以上分析,我们知道,其是有套路(就是常说的驱动框架)可寻的,
  102. 比如:
  103. #include <linux/module.h>
  104. #include <linux/kernel.h>
  105. #include <linux/init.h>
  106. /* 其她头文件...... */
  107. /* 一些驱动函数 */
  108. static ssize_t xxx_read (struct file *file, char __user *buf, size_t size, loff_t
  109. *offset)
  110. {
  111. }
  112. static ssize_t xxx_write (struct file *file, const char __user *buf, size_t size, loff_t
  113. *offset)
  114. {
  115. }
  116. static int xxx_open (struct inode *node, struct file *file)
  117. {
  118. }
  119. static int xxx_close (struct inode *node, struct file *file)
  120. {
  121. }
  122. /* 其它驱动函数...... */
  123. /* 定义自己的驱动结构体 */
  124. static struct file_operations xxx_drv = {
  125. .owner = THIS_MODULE,
  126. .open = xxx_open,
  127. .read = xxx_read,
  128. .write = xxx_write,
  129. .release = xxx_close,
  130. /* 其它程序......... */
  131. };
  132. /* 驱动入口函数 */
  133. static int __init xxx_init(void)
  134. {
  135. }
  136. /* 驱动出口函数 */
  137. static void __exit hello_exit(void)
  138. {
  139. }
  140. /* 模块注册与卸载函数 */
  141. module_init(xxx_init);
  142. module_exit(xxx_exit);
  143. /* 模块许可证(必选项) */
  144. MODULE_LICENSE("GPL");
  145. 按照这样的套路来开发驱动程序的,有套路可寻那就比较好学习了,至少不会想着怎么起函数名而烦恼,
  146. 按套路来就好,哈哈
  147. 关于驱动的知识,这篇笔记中还可以展开很多内容,限于篇幅就不展开了。我们之后再进行学习、分享。
  148. 下面看一下测试程序/应用程序(hello_drv_test.c中的内容,以下代码来自:百问网):
  149. #include <sys/types.h>
  150. #include <sys/stat.h>
  151. #include <fcntl.h>
  152. #include <unistd.h>
  153. #include <stdio.h>
  154. #include <string.h>
  155. /*
  156. * ./hello_drv_test -w abc
  157. * ./hello_drv_test -r
  158. */
  159. int main(int argc, char **argv)
  160. {
  161. int fd;
  162. char buf[1024];
  163. int len;
  164. /* 1. 判断参数 */
  165. if (argc < 2)
  166. {
  167. printf("Usage: %s -w <string>\n", argv[0]);
  168. printf(" %s -r\n", argv[0]);
  169. return -1;
  170. }
  171. /* 2. 打开文件 */
  172. fd = open("/dev/hello", O_RDWR);
  173. if (fd == -1)
  174. {
  175. printf("can not open file /dev/hello\n");
  176. return -1;
  177. }
  178. /* 3. 写文件或读文件 */
  179. if ((0 == strcmp(argv[1], "-w")) && (argc == 3))
  180. {
  181. len = strlen(argv[2]) + 1;
  182. len = len < 1024 ? len : 1024;
  183. write(fd, argv[2], len);
  184. }
  185. else
  186. {
  187. len = read(fd, buf, 1024);
  188. buf[1023] = '\0';
  189. printf("APP read : %s\n", buf);
  190. }
  191. close(fd);
  192. return 0;
  193. }
  194. 就是一些读写操作,跟我们学习文件操作是一样的。学单片机的有些朋友可能不太熟悉main函数的这种
  195. 写法:
  196. int main(int argc, char **argv)
  197. main函数在C中有好几种写法(可查看往期笔记:main函数的几种写法),在Linux中常用这种写法。
  198. argc与argv这两个值可以从终端(命令行)输入,因此这两个参数也被称为命令行参数。argc为命令行
  199. 参数的个数,argv为字符串命令行参数的首地址。
  200. 最后,我们把编译生成的驱动模块hello_drv.ko与应用程序hello_drv_test放到共享目录录
  201. nfs_share中,同时在开发板终端挂载共享目录:
  202. mount -t nfs -o nolock,vers=4 192.168.1.104:/home/book/nfs_share /mnt
  203. 关于ntf网络文件系统的使用可查看往期笔记:【Linux笔记】网络文件系统。
  204. 然后我们通过insmod 命令装载驱动,但是出现了如下错误:

  1. 这是因为我们的驱动的编译依赖与内核版本,编译用的内核版本与当前开发板运行的内核的版本不一致
  2. 所以会产生该错误,重新编译内核,并把编译生成的Linux内核zImage映像文件与设备树文件*.dts文
  3. 件拷贝到开发板根文件系统的/boot目录下,然后进行同步操作:
  4. #mount -t nfs -o nolock,vers=4 192.168.1.114:/home/book/nfs_share /mnt
  5. #cp /mnt/zImage /boot
  6. #cp /mnt/.dtb /boot
  7. #sync
  8. 最后,重启开发板。最后,成功运行程序:

  1. 下面是完整的hello驱动程序:
  2. #include <linux/module.h>
  3. #include <linux/fs.h>
  4. #include <linux/errno.h>
  5. #include <linux/miscdevice.h>
  6. #include <linux/kernel.h>
  7. #include <linux/major.h>
  8. #include <linux/mutex.h>
  9. #include <linux/proc_fs.h>
  10. #include <linux/seq_file.h>
  11. #include <linux/stat.h>
  12. #include <linux/init.h>
  13. #include <linux/device.h>
  14. #include <linux/tty.h>
  15. #include <linux/kmod.h>
  16. #include <linux/gfp.h>
  17. /* 1. 确定主设备号 */
  18. static int major = 0;
  19. static char kernel_buf[1024];
  20. static struct class *hello_class;
  21. #define MIN(a, b) (a < b ? a : b)
  22. /* 3. 实现对应的open/read/write等函数,填入file_operations结构体 */
  23. static ssize_t hello_drv_read (struct file *file, char __user *buf, size_t size, loff_t
  24. *offset)
  25. {
  26. int err;
  27. printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
  28. err = copy_to_user(buf, kernel_buf, MIN(1024, size));
  29. return MIN(1024, size);
  30. }
  31. static ssize_t hello_drv_write (struct file *file, const char __user *buf, size_t size,
  32. loff_t *offset)
  33. {
  34. int err;
  35. printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
  36. err = copy_from_user(kernel_buf, buf, MIN(1024, size));
  37. return MIN(1024, size);
  38. }
  39. static int hello_drv_open (struct inode *node, struct file *file)
  40. {
  41. printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
  42. return 0;
  43. }
  44. static int hello_drv_close (struct inode *node, struct file *file)
  45. {
  46. printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
  47. return 0;
  48. }
  49. /* 2. 定义自己的file_operations结构体 */
  50. static struct file_operations hello_drv = {
  51. .owner = THIS_MODULE,
  52. .open = hello_drv_open,
  53. .read = hello_drv_read,
  54. .write = hello_drv_write,
  55. .release = hello_drv_close,
  56. };
  57. /* 4. 把file_operations结构体告诉内核:注册驱动程序 */
  58. /* 5. 谁来注册驱动程序啊?得有一个入口函数:安装驱动程序时,就会去调用这个入口函数 */
  59. static int __init hello_init(void)
  60. {
  61. int err;
  62. printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
  63. major = register_chrdev(0, "hello", &hello_drv); /* /dev/hello */
  64. hello_class = class_create(THIS_MODULE, "hello_class");
  65. err = PTR_ERR(hello_class);
  66. if (IS_ERR(hello_class)) {
  67. printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
  68. unregister_chrdev(major, "hello");
  69. return -1;
  70. }
  71. device_create(hello_class, NULL, MKDEV(major, 0), NULL, "hello"); /* /dev/hello */
  72. return 0;
  73. }
  74. /* 6. 有入口函数就应该有出口函数:卸载驱动程序时,就会去调用这个出口函数 */
  75. static void __exit hello_exit(void)
  76. {
  77. printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
  78. device_destroy(hello_class, MKDEV(major, 0));
  79. class_destroy(hello_class);
  80. unregister_chrdev(major, "hello");
  81. }
  82. /* 7. 其他完善:提供设备信息,自动创建设备节点 */
  83. module_init(hello_init);
  84. module_exit(hello_exit);
  85. MODULE_LICENSE("GPL");
  86. 嵌入式Linux的学习内容是很多的、坑也是很多的,死磕到底即可。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/笔触狂放9/article/detail/257872?site
推荐阅读
相关标签
  

闽ICP备14008679号