原创 I2C驱动的简单实验之LT6911UXC读取ChipID

2023-6-8 12:09 2270 10 3 分类: MCU/ 嵌入式 文集: Rockchip

近期有点全身心投入到了嵌入式驱动的开发意思了,起早贪黑的学习。不过也是,人生的路都是在不断地学习中度过的。对于干了几年的硬件工程师而言,不说硬件是不是很牛了,就是想换换脑子,整天三极管、电阻、电容的,确实让人乏味。思来想去,硬件是软件的基座,驱动是软件沟通硬件的桥梁。倒不如自己整点知识,也方便自己以后调试硬件不是,再说了从软件角度去理解硬件思维,会有很多不同的收获不是。

奋战了一个月,倒是把驱动的基本框架了解七七八八了,兴致使然,图像采集感觉还不错,公司有产品当开发板,也是省下了大部分的学习成本。

硬件基本结构就是:SOC平台为瑞芯微,视频桥接芯片是LT6911UXC,千兆网络接口和基本的电源电路,还有的最小核心板组成就不多说了。

总归是要初始化和调试LT6911UXC的,那么最基础的当然是通过固定的总线去访问和配置其寄存器了,而大多这类芯片都是用的I2C,LT6911UXC也不例外。于是重点看了下I2C总线的驱动实现框架。那么就在已有的基本驱动框架下实验下了

一、基本的驱动框架

  1. #include <linux/init.h>
  2. #include <linux/module.h>
  3. static int lt6911_driver_init(void)
  4. {
  5. return 0;
  6. }
  7. static void lt6911_driver_exit(void)
  8. {
  9. }
  10. module_init(lt6911_driver_init);
  11. module_exit(lt6911_driver_exit);
  12. MODULE_LICENSE("GPL");
  13. MODULE_AUTHOR("LY");
  14. MODULE_VERSION("V1.0");

二、 增加I2C的框架 
1. 添加一个I2C设备

这一步是通过i2c_add_driver(driver)这个API函数实现的,那么就在驱动加载的时候使用这个函数

  1. static int lt6911_driver_init(void)
  2. {
  3. int ret;
  4. printk("==>this is lt6911_driver_init\n");
  5. ret= i2c_add_driver(<6911_driver);
  6. if(ret<0){
  7. printk("==>lt6911 i2c driver add error\n");
  8. }
  9. return 0;
  10. }

那么要按照以上的实现方式,必须要先实现一个I2C设备,这个设备是通过i2c_driver这个结构体实现的

  1. struct i2c_driver lt6911_driver={
  2. .probe=lt6911_driver_probe,
  3. .remove=lt6911_driver_remove,
  4. .driver={
  5. .owner=THIS_MODULE,
  6. .name="lt6911uxc", //没有设备树使用的匹配名
  7. .of_match_table=lt6911_id //使用设备树匹配的设备列表
  8. },
  9. .id_table=lt6911_id_table //无论使不使用设备树,这里必须实现
  10. };

2. 实现两个函数

从上一步的i2c_driver设备结构体可以看出,需要实现probe和remove函数。probe函数是当I2C设备正确挂载后所执行的函数,remove函数是I2C设备卸载时所执行的函数。

probe函数

  1. int lt6911_driver_probe(struct i2c_client *client, const struct i2c_device_id *id)
  2. {
  3. printk("==>this is lt6911_driver_probe\n");
  4. return 0;
  5. }

remove函数

  1. int lt6911_driver_remove(struct i2c_client *client)
  2. {
  3. printk("==>this is lt6911_driver_remove\n");
  4. return 0;
  5. }

3. 在设备树中对应的I2C下添加此设备信息

上面两步完成后,编译驱动为KO文件,通过insmod是可以加载此驱动的,但是会发现加载后只会执行到init这一步。那是因为我们没有在设备树中添加相应设备信息。我的板卡是挂在了I2C2上的,于是就进行下面操作

  1. &i2c2{
  2. status = "okay";
  3. clock-frequency = <100000>;
  4. lt6911uxc:lt6911uxc@56{
  5. compatible = "lt6911uxc";
  6. status = "okay";
  7. reg = <0x56>; //设备的芯片地址,手册都会说明
  8. interrupt-parent = <&gpio3>;
  9. interrupts = ;
  10. rst-gpio = <&gpio4 RK_PA1 GPIO_ACTIVE_LOW>;
  11. pinctrl-names="default";
  12. pinctrl-0=<<6911_rstn_gpio>;
  13. };
  14. };

然后重新编译内核,烧录开发板。再此进行加载KO文件,发现可以打印probe函数中设置的打印语句了

image-20230608105838962

4. 实现最简单的读取Chip ID

框架都搭建完成,接下来当然是与芯片交流一下了,阅读了下LT6911UXC相关手册,要想读取寄存器的数据还得改变它的I2C工作模式和切换bank.原因是其内部集成了MCU,而这个MCU也是通过这个I2C在内部已经连接了LT6911UXC处理核心。

 

那么就要实现i2c的write和read函数了。驱动程序中I2C的读写都是以包的形式发送和接收的,所以我们先封包。封包使用的结构体是struct i2c_msg,最终的读写函数实现如下

  1. static void lt6911_i2c_write( u16 reg, u8 *values, u32 n)
  2. {
  3. struct i2c_msg msgs[2];
  4. int err, i;
  5. u8 data[8];
  6. u8 bank = reg >> 8;
  7. u8 reg_addr = reg & 0xFF;
  8. u8 buf[2] = {0xFF, bank};
  9. data[0] = reg_addr;
  10. for (i = 0; i < n; i++)
  11. data[i + 1] = values[i];
  12. /* write bank */
  13. msgs[0].addr = lt6911_client->addr;
  14. msgs[0].flags = 0;
  15. msgs[0].len = 2;
  16. msgs[0].buf = buf;
  17. /* write reg data */
  18. msgs[1].addr = lt6911_client->addr;
  19. msgs[1].flags = 0;
  20. msgs[1].len = 1 + n;
  21. msgs[1].buf = data;
  22. err = i2c_transfer(lt6911_client->adapter, msgs, ARRAY_SIZE(msgs));
  23. if(err < 0){
  24. printk("==>transfer error %d\n",err);
  25. }
  26. }

  1. static int lt6911_i2c_read(u16 reg,u8 *values, u32 n)
  2. {
  3. int ret;
  4. u8 bank = reg >> 8;
  5. u8 reg_addr = reg & 0xFF;
  6. u8 bank_buff[2]={0xff,bank};
  7. struct i2c_msg msgs[]={
  8. [0]={
  9. .addr=lt6911_client->addr,
  10. .flags=0,
  11. .len=2,
  12. .buf=bank_buff,
  13. },
  14. [1]={
  15. .addr=lt6911_client->addr,
  16. .flags=0,
  17. .len=sizeof(reg_addr),
  18. .buf=®_addr,
  19. },
  20. [2]={
  21. .addr=lt6911_client->addr,
  22. .flags=1,
  23. .len=sizeof(values),
  24. .buf=values,
  25. }
  26. };
  27. ret = i2c_transfer(lt6911_client->adapter, msgs, ARRAY_SIZE(msgs));
  28. if(ret < 0){
  29. printk("==>transfer error %d\n",ret);
  30. return ret;
  31. }
  32. return 0;
  33. }

读写函数实现没问题了,那么就在init函数中添加调用就可以了

  1. lt6911_i2c_write( 0x80ee, &i2c_enable, 1);
  2. lt6911_i2c_read(0x8100,&rdata,1);
  3. printk("==>lt6911_id is %#x\n",rdata);

编译后,再次加载KO文件,发现在写函数中i2c_transfer函数返回值为-6,意思是NO ACK。怎么回事呢,经过询问最近比较火热的Chatgpt,它告诉我了个答案

 

也就是我们给了设备地址,但是这个函数会将设备地址左移后然后增加读写位,才是真正的发送的地址。而通过开发板命令行中使用I2C工具(命令:i2cdump -y -f 2 0x56)来读取设备寄存器,通过逻辑分析仪抓取后得到

 

0x56左移一位再加上写标志位,确实是0xAC啊,经过资料的一番查找,对于I2C设备地址,都是七位。而资料给的发送格式0x56是带有读写位的。那么去掉读写位,也就是将0x56右移一位,在最高位加一个零,就得到了0x2B,再次使用I2C工具试下

 

就这样成功了,翻阅了大量资料。对于一个初学者而言都是在不断地怀疑和比较中找到了答案。还是挺兴奋的。所以我们就要把设备树中的配置更改下

  1. &i2c2{
  2. status = "okay";
  3. clock-frequency = <100000>;
  4. lt6911uxc:lt6911uxc@56{
  5. compatible = "lt6911uxc";
  6. status = "okay";
  7. reg = <0x2b>; //设备的芯片地址,手册都会说明
  8. interrupt-parent = <&gpio3>;
  9. interrupts = ;
  10. rst-gpio = <&gpio4 RK_PA1 GPIO_ACTIVE_LOW>;
  11. pinctrl-names="default";
  12. pinctrl-0=<<6911_rstn_gpio>;
  13. };
  14. };

编译内核,烧录。读chipID成功

 

总结

i2c的读写最关键的就是设备地址了,驱动的框架是固定的。

学习就应该在怀疑中调试,在调试中比较,在比较中得到答案。我们都是站在巨人的肩膀上的,当自己出现问题时,最好是看看巨人都是怎么做的。

原文链接

作者: 二月半, 来源:面包板社区

链接: https://mbb.eet-china.com/blog/uid-me-1862109.html

版权声明:本文为博主原创,未经本人允许,禁止转载!

给作者打赏,鼓励TA抓紧创作!

赞赏支持
点赞 10
赞赏1

文章评论0条评论)

登录后参与讨论
EE直播间
更多
我要评论
0
10
1
2
3
4
5
6
7
8
9
0
关闭 站长推荐上一条 /6 下一条