刚刚结束对传感器HMC5883L的驱动书写及调试工作,虽然之前对相关的各种知识点都有接触,但是在真正从头书写驱动的时候还是遇到了很多不大不小的麻烦,在这里自行总结一下,也是作为以后驱动书写的一个经验教训,更是对以往所学内核驱动相关知识的复习和总结。事实证明,看了多少书,也不如亲自动手实践学的快,记得牢。

关于I2C

因为手头有几个传感器,都需要用到I2C接口,所以在之前就将I2C子系统复习并深入研究了一番。以下我所提到的或贴出的部分代码也许不适合真正的板级驱动,因为是以模块化形式做测试的。

  • 在此模块化驱动中,不仅要注册驱动(i2c_driver),同时也要对设备信息进行注册(i2c_client),我认为在这里不分前后顺序(就像“先有鸡还是先有蛋”的问题一样没有意义)。在前边分析i2c子系统的时候提到过,对于在i2c适配器注册后再添加的新的设备不能再用i2c_register_board_info了,这会导致设备完全不能被激活,而需要用的是i2c_new_device才能将设备动态的注册到系统中
  • 在使用i2c_new_device时候不仅需要设备的i2c_board_info结构体,还需要其所依附的I2C适配器总线号。首先,关于总线号,可以通过i2cdetect命令进行查看:
  root@arm:/home/debian# i2cdetect -li2c-0    i2c           OMAP I2C adapter                    I2C adapteri2c-1    i2c           OMAP I2C adapter                    I2C adapter

然后,在代码中可以这样使用:

  struct i2c_adapter *adap;int adap_nr = 1; // 总线号为1adap = i2c_get_adapter(adap_nr);

这样就获取了指定总线号的i2c_adapter指针,之后就可以利用这个指针给i2c_new_device用了。最后需要注意,在注册完设备信息后,要使用i2c_put_adapter(adap)将指针释放掉。

  • 用于描述硬件信息的结构体可以做为i2c_client的私有数据保存,而这个结构体中往往也要保存对应的client。这种互相的对应关系应该在probe接口函数中进行:
  static int hmc5883l_probe(struct i2c_client *client, struct i2c_device_id *id){...i2c_set_clientdata(client, dev);dev->client = client;...}

驱动未写,调试先行。如果在开始着手书写驱动前就能直接的通过工具的简单应用对器件进行调试查看的话,会对驱动的书写有很大的帮助。所以这里要说一下关于I2C在shell中的几个调试命令i2cdetect, i2cdump, i2cget, i2cset。首先是i2cdetect,一般用来探测和罗列总线(上边就演示了一下),一般使用方法是:罗列总线->探测有效设备

  i2cdetect -l //罗列现有I2C适配器信息i2cdetect -y -r 1 // 查看总线号为1的I2C适配器上挂载的所有设备,如果设备真实有效,则地址会显示出来,而不是UU,UU代表也许有实际设备,但设备可能是忙状态

查看效果如下:

       0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f00:          -- -- -- -- -- -- -- -- -- -- -- -- -- 10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- 1e -- 20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 50: -- -- -- -- UU UU UU UU -- -- -- -- -- -- -- -- 60: -- -- -- -- -- -- -- -- 68 -- -- -- -- -- -- -- 70: -- -- -- -- -- -- -- -- 

这里就可以看到,设备从地址为0x68和0x1e的设备有实际有效的硬件连接,分别是HMC5883L和AD0接地(不连)的MPU6050。0x54 55 56 57为EEPROM,设备忙。
其次是i2cdump,用来查看器件内部寄存器值,用法为i2cdump -y 总线号 设备地址

  root@arm:/home/debian# i2cdump -y 1 0x1eNo size specified (using byte-data access)0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f    0123456789abcdef00: 10 20 03 01 21 01 4b ff 50 03 48 34 33 00 00 3c    ? ??!?K.P?H43..<10: 00 00 00 00 00 00 00 00 00 00 00 94 09 04 e8 10    ...........?????20: 00 00 00 00 00 00 00 00 10 00 00 00 00 00 00 00    ........?.......30: 00 00 00 14 11 55 56 00 a0 00 07 00 00 00 00 00    ...??UV.?.?.....40: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00    ................50: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00    ................60: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00    ................70: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00    ................80: 10 20 03 01 21 01 4b ff 50 03 48 34 33 00 00 3c    ? ??!?K.P?H43..<90: 00 00 00 00 00 00 00 00 00 00 00 94 09 04 e8 10    ...........?????a0: 00 00 00 00 00 00 00 00 10 00 00 00 00 00 00 00    ........?.......b0: 00 00 00 14 11 55 56 00 a0 00 07 00 00 00 00 00    ...??UV.?.?.....c0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00    ................d0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00    ................e0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00    ................f0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00    ................

然后是i2cget和i2cset,分别是对寄存器进行获取和写入。用法为i2cget -y 总线号 设备地址 寄存器地址 模式和i2cset -y 总线号 设备地址 寄存器地址 数值 模式。模式默认为b(byte)即读取8bit数据,i2cget可用模式有b/w/c,i2cset可用模式有b/w/c/i/s,其中w为word(16bit),i和s分别为I2C和SMBUS的block数据。

Mutex互斥锁

  • 千万不要忘记初始化mutex互斥锁。静态初始化DEFINE_MUTEX(mutex_name),动态初始化mutex_init(struct mutex *lock)。忘记初始化就使用的话是会直接造成内核报错的。
  • 在中断上下文中不要使用mutex互斥锁,因为如果出现了竞态,mutex有可能进入睡眠,而中断上下文中是绝对不允许睡眠的。所以千万不要使用,如果一定要在中断中使用锁机制来保护一些驱动资源,建议使用spinlock自旋锁(semaphore信号量也不允许使用,同样的原因)。
  • 关注死锁。哪个操作需要进行锁一定要事先自行规划好,不要在某操作一进入的时候锁,而进入其子步骤后又锁,这样就直接死锁了,系统freeze掉。

关于中断

中断的使用很简单,但是却有很多值得注意的细节点。

  • GPIO中断。如一些开发板上,外部扩展出来很多GPIO口,但是却找不到IRQ口,所以就需要将GPIO扩展为中断线。在代码中,使用gpio_to_irq(gpio_nr)函数(linux/gpio.h)就可以得到自动转换后的中断线号了,可以用来请求中断。
  • 若在request_irq的时候最后给的参数不为NULL,那么在free_irq的时候,第二个参数也就必须与其一致,否则会使系统找不到要释放哪个中断的处理程序句柄(当然了,为NULL就都为NULL,这个没有问题,只要一致就可以)。

工作队列

工作队列分work_struct 和delayed_work。区别就是delayed_work会在指定的延迟后开始运行,而work_struct会立即被调度运行。

  • 初始化。INIT_WORK(struct work_struct *work, void (*work_func)(struct work_struct *work))动态初始化work_struct。INIT_DELAYED_WORK(struct delayed_work *work, void (*work_func)(struct work_struct *work))动态初始化delayed_work,在delayed_work的work_func工作函数中,可以通过强制转换将*work转换为struct delayed_work类型。
  • 调度。work_struct的调度为schedule_work(struct work_struct *work),而delayed_work则需要另外一个延迟参数schedule_delayed_work(struct delayed_work *work, unsigned long delay),这里的delay参数就是延迟多久后投入工作,单位是jiffies,常会用到类如msecs_to_jiffies(msecs)等转换函数。
  • 如果未对工作函数指定队列,那么其会自动进入system_wq中,在驱动中也常会用到定义自己的工作队列,但是简单工作往往没有需要这样做。
  • 对于频繁上报信息的工作,最好定义自己的工作队列,将此工作放入自己的工作队列中运行,而不是放入系统默认的system_wq中,这样会避免在系统忙的时候自己的工作被很快调度走,有自己的工作队列在这方面能够起到很大的作用。

completion同步

因为在调试过程中尝试了自检,而又涉及到中断,所以采用了completion作为同步机制,这里提出简单用法。

  • 初始化。init_completion(struct completion *wait)
  • 等待。wait_for_completion_timeout(struct completion *wait, unsigned long timeout),返回值为剩余时间,如果剩余时间为0,也就是说明超时了。
  • 唤醒。complete(struct completion *wait)

代码所在:https://github.com/bbbLinux/projects_hmc5883l

关于HMC5883L驱动书写及调试的总结相关推荐

  1. php反调试,简单对抗某个驱动的反调试

    标 题: [原创]简单对抗某个驱动的反调试 作 者: ReturnsMe 时 间: 2010-04-18,20:02:11 链 接: http://bbs.pediy.com/showthread.p ...

  2. 易语言编程之CE过驱动保护(ACE)调试教程

    易语言编程之CE过驱动保护(ACE)调试 很多时候,驱动保护很让人头疼, 一用CE,就提示开了程序要结束, 你一搜到地址鼠标右键想看看访问和写入的代码地址,找基址时候,游戏就退出了, 这个教程教你 可 ...

  3. Windows内核编程(三)-内核驱动运行与调试

    内核驱动运行与调试 驱动的运行 驱动的运行通过服务来实现. 微软规定,驱动文件必须经过微软的数字签名后,才可以运行在64位系统上,如果把没有经过签名的驱动直接放在64位操作系统中运行,结果是驱动加载失 ...

  4. windows 驱动与内核调试 学习

    概述 本文讲述笔者在学习内核和驱动开发的笔记. 驱动概述 一般驱动需要运行内核权限下运行(因为涉及硬件读取),比如Intel下的ring 0 权限下.在windwos大量病毒和杀软为了特殊目的往往都是 ...

  5. VS2012编译调试WDM驱动(KdPrint无调试信息 debugview win7无调试信息)

    对于WDM驱动 VS2012有向导可以新建WDM项目 如图 这点说明不用自己配置 文件目录 C/C++ 选项 LINK 选项 等一系列的参数 比以前方便了不少 新建以后是空项目 放入<windo ...

  6. 驱动开发 - WDK 调试及 SVN 环境搭建[转]

    由于从公司辞职了,所以以前在公司里搭建的驱动开发环境也就 Game Over 了, 同样由于那环境是很久以前搭建的,自己也有很多记不清楚的地方了, 而且其中还是有很多需要注意的地方的,所以在这里顺便做 ...

  7. Linux---wifi驱动移植及调试(SSV6x5x)

    一.背景说明及驱动移植 本文主要简述南方硅谷ssv6x5x wifi驱动的移植及调试,官方实际是有一份南方硅谷ssv6x5x 驱动移植用户指南,需要的话可以下载,官方指南对于移植过程描述的很清楚. 移 ...

  8. 嵌入式Linux(5):驱动开发网络调试驱动设备的Linux系统移植

    驱动开发之网络调试驱动设备的Linux系统移植 1.Uboot移植到开发板 uboot移植 2.开发板网络通讯 nfs命令 tftp命令 3.Linux移植到开发板 4.BusyBox 构建根文件系统 ...

  9. OV7670 STM32驱动 YUYV格式调试总结

    前言 单片机只有能够看见世界,才能真正的走向智能化,所以,用单片机驱动摄像头很有必要.而在摄像头当中,OV7670是比较经典的一款. 硬件准备 1 .STM32开发板: 2. OV7670模块: OV ...

最新文章

  1. 清华大学 唐杰 计算机学院 怎么样,我国首位原创虚拟学生,后期希望“她”能够像人一样进行创新...
  2. EditText的各种属性
  3. Java的char数据类型存储一个中文字符
  4. [2897]F SDUTOJ
  5. solr的空间查询(查询地图周围坐标)
  6. 使用selenium进行密码破解(绕过账号密码JS加密)
  7. Android 更改签名
  8. IOT(1)---平台架构
  9. 端口扩展器技术让网络交换焕然一新
  10. 【Java I/O流】File、字符集、字节流、字符流、缓冲流、数据流、对象流、序列化、try-with-resources语句
  11. php执行函数吗_php函数system
  12. java如何实现线程_java中线程的三种实现方式
  13. JavaScript-参数
  14. 七,OpenERP 移库操作模块
  15. 如何保证测试的覆盖率
  16. 二叉树 先序遍历 中序遍历 后续遍历 java实现
  17. 国标1级字库+2级字库,用utf8汉字获取汉字点阵字库
  18. 【文件上传下载】各种类型文件对应的content-type的值
  19. ffmpeg推拉流优化方案
  20. java基础笔试题(50题)

热门文章

  1. Centos显示“用户名不在sudoers文件中,此事将被报告”
  2. SqlServer2017 AlwaysOn 读写分离 无域控
  3. javaScript和JQuery
  4. java虚拟机原理(java虚拟机的基本结构)
  5. MapX和MapXtreme区别
  6. 快消品行业定制化多租户SaaS系统:提供个性化配置,加速快消品企业运转效率
  7. 巨儒艮、漂泊信天翁……这位90后中科院动物所研究员用日历讲述“物种故事”...
  8. RT-Thread 软件包制作及发布流程
  9. Android应用程序开发——创建应用程序
  10. Verify the connector‘s configuration, identify and stop any process that‘s listening on port 80, or