赞
踩
SPI和I2C总线是在单片机中很常见的一种外设,通过普通IO口也可以模拟spi和i2c总线实现相关外接接口芯片的通信。在前面文章中总结分享过在linux中操作spi外设,这里总结分享下在linux系统中如何操作i2c总线设备,分享给有需要的小伙伴。
快乐的人意识到一天能做的有限,也不低估一年内的改变。在做事的时候 ,设定可实现的目标能带来快乐。如果做事没有得到反馈,没有成就,感觉没有尽头,人就特别容易放弃。为什么干农活容易给人满足感?因为一分耕耘一分收获,做多长时间农活就有多少成果。
在“心流”状态下做事处在“心流”状态下,人有一种自发的喜悦和兴奋的感觉。心流取决于定任务挑战和执行任务的技能之间微妙的平衡。给自己找件事情做,尽量去找那种全身投入的心流感,那样做事效率是最高的。
这里先介绍下博主的学习方法。如果用一个字总结的话,就是“练”。两个字总结的话就是“实战”,三个子总结的话就是“做项目”。项目从何而来?哪有那么多项目让你练手?自己给自己定小目标呗,比如同一个oled显示屏,既可以支持spi接口又可以支持i2c,那就反复换不同的方式去实现一遍。
linux之父Linus大佬说的一句名言 “ Talk is cheap, show me the code ,just read the fucking source code!” 。但是哪有那么多项目练?自己给自己找,权当是拿来玩的,just for fun。
Linus 在学习Minix操作系统的过程中,对自带的终端仿真程序非常不满,于是自己写了一个。然后他不想在Minix系统里写,想要在无系统的裸硬件上写,来更好的理解计算机的执行原理。于是他需要知道cpu的工作原理,知道如何写入屏幕,如何读取键盘的输入,如何读写modem,这就是操作系统的雏形。Linus 用这个终端程序登入学校的电脑,查阅电子邮件,参加Minix新闻组的讨论,但是他还想上传和下载文件,于是还需要磁盘驱动程序和文件系统驱动,这是巨大的工作量。他的日程从此变成了编程-睡觉-编程-睡觉的循环,在过程中,这个终端越来越像操作系统,于是Linus 干脆决定把这个终端做成操作系统。
要做成操作系统,还有一个巨大的挑战,那就是要实现Posix规范,也就是Unix系统里的系统调用,有了这些系统api,其他Unix的程序就可以在这个操作系统上运行。Linus 在电子邮件新闻组里求助未果后,找到Sun公司Unix系统的用户手册,然后根据基本版的系统调用标准,开始自己实现这些功能。在1991年9月17日,Linus把操作系统上传到FTP上,那个时候的Linux操作系统大概1w行左右,而现在Linux超过1000w行代码。所以,你如今看到的庞然大物,大名鼎鼎的linux系统起初也是源于Linus的《just for fun》. Linus的人生哲学,Linus认为驱动人类的是三点动机:生存,人在社会秩序中的位置,以及娱乐。Linus在那年夏天我做了两件事。第一件是什么都没做。第二件事是读完了七百一十九页的《操作系统:设计和执行》。那本红色的简装本教科书差不多等于睡在了我的床。
在软件世界中,一旦你解决了最根本的的问题,兴趣就容易很快地消失。一旦你遇到了不知道而想要了解的东西,兴趣就很容易上来。
我的学习三步法:
1.定一个小目标(不是挣它一个亿啊)
2.专注努力达成目标(不轻言放弃)
3.总结分享(学到的写出来也教会别人,费曼学习法)
在硬件层中,I2C硬件总线只有两条线路,上面可以挂载多个I2C-device,这些I2C-device有的在I2C总线里充当主机的角色,一般情况该主机为板子上的主cpu中的I2C控制器,拿imx6ul板子来说,这个I2C主机就是imx6中的I2C控制器模块。
其他的I2C-device在I2C总线里充当从机的角色,通常这些从机是板子上完成特定功能的传感器外设,只不过该外设与主控cpu的通信方式是只需要两条线路的I2C总线,比如在imx6ul板子中就有eeprom和AP3216两个外设,以及本次实验用的oled硬件显示模块(i2c接口),它们在I2C总线中充当的都是I2C从机的角色,它们和主控芯片imx6中的I2C控制器都是以并联的方式挂在这个I2C总线上。
在内核中,驱动程序对下要完成I2C总线上的I2C通信协议,收集硬件外设的I2C数据并封装成标准的linux操作接口供用户空间的应用程序操作。对上要实现可以通过linux程序把数据流组织成I2C协议下发到硬件层的相应的外设传感器中。
在用户空间的应用程序中,应用工程师完全可以不必理会I2C协议的详细规定。只需要按照驱动层提供给我们的操作I2C外设的操作接口函数就可以像操作linux中其他普通设备文件那样轻松的操作I2C外设了。
下图是linux系统环境里操作i2c总线上的外设流程框图:
通过前面的介绍,我们已经知道站在cpu的角度来看,操作I2C外设实际上就是通过控制cpu中挂载该I2C外设的I2C控制器,而这个I2C控制器在linux系统中被称为“I2C适配器”,而且在linux系统中,每一个设备都是以文件的形式存在的,所以在linux中操作I2C外设就变成了操作I2C适配器设备文件。
Linux系统(也就是内核)为每个I2C适配器生成了一个主设备号为89的设备节点(次设备号为0-255),它并没有针对特定的I2C外设而设计,只是提供了通用的read(),write(),和ioctl()等文件操作接口,在用户空间的应用层就可以借用这些接口访问挂接在适配器上的I2C设备的存储空间或寄存器,并控制I2C设备的工作方式。
i2c适配器的设备节点是/dev/i2c-x,其中x是数字。由于适配器编号是动态分配的(和注册次序有关),所以想了解哪一个适配器对应什么编号,可以查看/sys/class/i2c-dev/目录下的文件内容。
- cat /sys/class/i2c-dev/i2c-0/name
- cat /sys/class/i2c-dev/i2c-1/name
然后查看硬件原理图外设是挂在cpu的哪个控制器中,或者借助i2ctools工具可以测试出来。
本次实验我的oled外设屏对应的I2C控制器的设备节点为:/dev/i2c-0。
当用户打开适配器设备节点的时候,Kernel中的i2c-dev代码为其建立一个i2c_client,但是这个i2c_client并不加到i2c_adapter的client链表当中。当用户关闭设备节点时,它自动被释放。
这个可以参考内核源码中的include/linux/i2c-dev.h文件。下面举例说明主要的IOCTL命令:
代码:ioctl(file,I2C_RDWR,(struct i2c_rdwr_ioctl_data *)msgset); 它可以进行连续的读写,中间没有间歇。只有当适配器支持I2C_FUNC_I2C此命令才有效。参数msgset是一个指针,指向一个i2c_rdwr_ioctl_data类型的结构体,该结构体的功能就是让应用程序可以向内核传递消息。
其成员包括:struct i2c_msg __user *msgs; 和表示i2c_msgs 个数的__u32 nmsgs,它也决定了在硬件I2C总线的硬件通信中有多少个开始信号。由于I2C适配器与外设通信是以消息为单位的,所以struct i2c_msg对我们来说是非常重要的,它可以包含多条消息,而一条消息有可能包含多个数据,比如对于eeprom页写就包含多个数据。
下面介绍一下这个结构体的内容:
__u16 addr: 从设备地址
__u16 flags: 标志(读/写)
I2C_M_TEN: 这是一个10位芯片地址
I2C_M_RD: 从设备到适配器读数据
I2C_M_NOSTART: 不发送起始位
I2C_M_REV_DIR_ADDR: 翻转读写标志
I2C_M_IGNORE_NAK: 忽略I2C的NACK信号
I2C_M_NO_RD_ACK: 读操作的时候不发ACK信号
I2C_M_RECV_LEN : 第一次接收数据的长度
__u16 len: 写入或者读出数据的个数(字节)
__u8 *buf: 写入或者读出数据的地址 buf[0]。
(注:不要忘记给2c_rdwr_ioctl_data结构体中的最重要的结构i2c_msg中的buf分配内存。)
可以使用read()/write()来与I2C设备进行通信。
第一,打开I2C控制器文件节点: fd =open(“/dev/i2c-0”, O_RDWR);
第二,设置设备的设备地址:ioctl(fd,I2C_SLAVE, 0x50);
第三,向设备读写数据。
0111100'R/W#'
('R/W#'=0,写模式;'R/W#'=1,读模式)控制字: 0'D/C'000000
('D/C'=0,命令;'D/C'=1,数据)
i2cdetect -l 查看当前系统的I2C总线
总线挂载了I2C设备,可通过i2cdetect扫描每一个总线的所有设备。
i2cdetect -l
i2cdetect -y -r 1 :查看总线1上的所有从设备("--"表示地址被检测到了,但是没有芯片,"UU"表示地址正在被某一个驱动使用,而16进制的地址号3c)
i2cdetect -y -r 1
查询总线1(I2C -1)的功能,命令:
i2cdetect -F 1
i2cget:获取某一个总线上某一个从设备的寄存器值。
如下:获取1总线从设备0x62寄存器00的值。
i2cget -f -y 1 0x62 0x00
-f:强制访问设备
-y:取消交互模式。默认情况下,i2cdetect将等待用户的确认,当使用此标志时,它将直接执行操作。
i2cset 设置某一个总线上某一个从设备的寄存器的值。
如下:设置1总线从设备0x62寄存器00的值为0x00
i2cset -f -y 1 0x62 0x00 0x00
设置i2c1上从地址为0x62的外设0x00寄存器的值为0x00
i2cdump :查看某一个总线上某一个从设备所有寄存器的值,寄存器地址为8位
如下:查看i2c1上0x62外设所有寄存器的值
i2cdump -f -y 1 0x62
i2ctranfer:向寄存器地址为16位的从设备读取或者写入数据
i2ctransfer -f -y 1 w2@0x62 0x00 0x00 r32
读取
1:哪条总线
w2:写两个字节地址
0x00 0x00:寄存器地址
r32:往后32个寄存器所对应的寄存器值
i2ctransfer -f -y 1 w2@0x62 0x00 0x00 0x10
1:哪条总线
w2:写两个字节地址
0x00 0x00:寄存器地址
0x10:0x00 0x00寄存器地址往后的寄存器写入0x10
- /*
- # I2C testing utility (using iecdev driver)
- # test iicdev-oled
- # author: yangyongzhen
- # qq: 534117529
- # wx: yongzhen1111
- # Copyright (C) 2023 yangyongzhen <5234117529@qq.com>
- #
- */
-
- #include <stdint.h>
- #include <unistd.h>
- #include <stdio.h>
- #include <stdlib.h>
- #include <string.h>
- #include <getopt.h>
- #include <fcntl.h>
- #include <sys/ioctl.h>
- #include <linux/types.h>
- #include <linux/i2c-dev.h>
- #include <linux/i2c.h>
-
- #include "oledfont.h"
- #include "bmp.h"
- #include "oled.h"
-
- /* I2C设备地址 */
- #define I2C_ADDR 0x3c
-
- #define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0]))
-
-
- static void pabort(const char *s)
- {
- perror(s);
- abort();
- }
-
- static const char *iic_device = "/dev/i2c-1";
-
- static int i2c_fd = -1;
-
- static void hex_dump(const void *src, size_t length, size_t line_size, char *prefix)
- {
- int i = 0;
- const unsigned char *address = src;
- const unsigned char *line = address;
- unsigned char c;
-
- printf("%s | ", prefix);
- while (length-- > 0) {
- printf("%02X ", *address++);
- if (!(++i % line_size) || (length == 0 && i % line_size)) {
- if (length == 0) {
- while (i++ % line_size)
- printf("__ ");
- }
- printf(" | "); /* right close */
- while (line < address) {
- c = *line++;
- printf("%c", (c < 33 || c == 255) ? 0x2E : c);
- }
- printf("\n");
- if (length > 0)
- printf("%s | ", prefix);
- }
- }
- }
-
- /*
- * Unescape - process hexadecimal escape character
- * converts shell input "\x23" -> 0x23
- */
- static int unescape(char *_dst, char *_src, size_t len)
- {
- int ret = 0;
- char *src = _src;
- char *dst = _dst;
- unsigned int ch;
-
- while (*src) {
- if (*src == '\\' && *(src+1) == 'x') {
- sscanf(src + 2, "%2x", &ch);
- src += 4;
- *dst++ = (unsigned char)ch;
- } else {
- *dst++ = *src++;
- }
- ret++;
- }
- return ret;
- }
-
-
- void Write_IIC_Byte(unsigned char iic_byte)
- {
- u8 buf[2] = {0};
- buf[0] = iic_byte;
- if(i2c_fd){
- write(i2c_fd, buf, 1);
- }
- }
- //向SSD1106写入一个字节。
- //dat:要写入的数据/命令
- //cmd:数据/命令标志 0,表示命令;1,表示数据;
- void OLED_WR_Byte(u8 dat,u8 cmd)
- {
- if(cmd)
- {
- Write_IIC_Byte(I2C_ADDR); //D/C#=0; R/W#=0
- Write_IIC_Byte(0x40); //write data
- Write_IIC_Byte(dat);
- }
- else
- {
- Write_IIC_Byte(I2C_ADDR); //Slave address,SA0=0
- Write_IIC_Byte(0x00); //write command
- Write_IIC_Byte(dat);
- }
- }
-
- //初始化SSD1306
- void OLED_Init(void)
- {
- //OLED_RST_Set();
- usleep(100000);
- //OLED_RST_Clr();
- usleep(100000);
- //OLED_RST_Set();
-
- OLED_WR_Byte(0xAE,OLED_CMD);//--turn off oled panel
- OLED_WR_Byte(0x02,OLED_CMD);//---set low column address
- OLED_WR_Byte(0x10,OLED_CMD);//---set high column address
- OLED_WR_Byte(0x40,OLED_CMD);//--set start line address Set Mapping RAM Display Start Line (0x00~0x3F)
- OLED_WR_Byte(0x81,OLED_CMD);//--set contrast control register
- OLED_WR_Byte(0xCF,OLED_CMD); // Set SEG Output Current Brightness
- OLED_WR_Byte(0xA1,OLED_CMD);//--Set SEG/Column Mapping 0xa0左右反置 0xa1正常
- OLED_WR_Byte(0xC8,OLED_CMD);//Set COM/Row Scan Direction 0xc0上下反置 0xc8正常
- OLED_WR_Byte(0xA6,OLED_CMD);//--set normal display
- OLED_WR_Byte(0xA8,OLED_CMD);//--set multiplex ratio(1 to 64)
- OLED_WR_Byte(0x3f,OLED_CMD);//--1/64 duty
- OLED_WR_Byte(0xD3,OLED_CMD);//-set display offset Shift Mapping RAM Counter (0x00~0x3F)
- OLED_WR_Byte(0x00,OLED_CMD);//-not offset
- OLED_WR_Byte(0xd5,OLED_CMD);//--set display clock divide ratio/oscillator frequency
- OLED_WR_Byte(0x80,OLED_CMD);//--set divide ratio, Set Clock as 100 Frames/Sec
- OLED_WR_Byte(0xD9,OLED_CMD);//--set pre-charge period
- OLED_WR_Byte(0xF1,OLED_CMD);//Set Pre-Charge as 15 Clocks & Discharge as 1 Clock
- OLED_WR_Byte(0xDA,OLED_CMD);//--set com pins hardware configuration
- OLED_WR_Byte(0x12,OLED_CMD);
- OLED_WR_Byte(0xDB,OLED_CMD);//--set vcomh
- OLED_WR_Byte(0x40,OLED_CMD);//Set VCOM Deselect Level
- OLED_WR_Byte(0x20,OLED_CMD);//-Set Page Addressing Mode (0x00/0x01/0x02)
- OLED_WR_Byte(0x02,OLED_CMD);//
- OLED_WR_Byte(0x8D,OLED_CMD);//--set Charge Pump enable/disable
- OLED_WR_Byte(0x14,OLED_CMD);//--set(0x10) disable
- OLED_WR_Byte(0xA4,OLED_CMD);// Disable Entire Display On (0xa4/0xa5)
- OLED_WR_Byte(0xA6,OLED_CMD);// Disable Inverse Display On (0xa6/a7)
- OLED_WR_Byte(0xAF,OLED_CMD);//--turn on oled panel
-
- OLED_WR_Byte(0xAF,OLED_CMD); /*display ON*/
- OLED_Clear();
- OLED_Set_Pos(0,0);
- }
-
- void OLED_Set_Pos(unsigned char x, unsigned char y)
- {
- OLED_WR_Byte(0xb0+y,OLED_CMD);
- OLED_WR_Byte(((x&0xf0)>>4)|0x10,OLED_CMD);
- OLED_WR_Byte((x&0x0f)|0x01,OLED_CMD);
- }
- //开启OLED显示
- void OLED_Display_On(void)
- {
- OLED_WR_Byte(0X8D,OLED_CMD); //SET DCDC命令
- OLED_WR_Byte(0X14,OLED_CMD); //DCDC ON
- OLED_WR_Byte(0XAF,OLED_CMD); //DISPLAY ON
- }
- //关闭OLED显示
- void OLED_Display_Off(void)
- {
- OLED_WR_Byte(0X8D,OLED_CMD); //SET DCDC命令
- OLED_WR_Byte(0X10,OLED_CMD); //DCDC OFF
- OLED_WR_Byte(0XAE,OLED_CMD); //DISPLAY OFF
- }
- //清屏函数,清完屏,整个屏幕是黑色的!和没点亮一样!!!
- void OLED_Clear(void)
- {
- u8 i,n;
- for(i=0;i<8;i++)
- {
- OLED_WR_Byte (0xb0+i,OLED_CMD); //设置页地址(0~7)
- OLED_WR_Byte (0x02,OLED_CMD); //设置显示位置—列低地址
- OLED_WR_Byte (0x10,OLED_CMD); //设置显示位置—列高地址
- for(n=0;n<128;n++)OLED_WR_Byte(0,OLED_DATA);
- } //更新显示
- }
-
-
- //在指定位置显示一个字符,包括部分字符
- //x:0~127
- //y:0~63
- //mode:0,反白显示;1,正常显示
- //size:选择字体 16/12
- void OLED_ShowChar(u8 x,u8 y,u8 chr)
- {
- unsigned char c=0,i=0;
- c=chr-' ';//得到偏移后的值
- if(x>Max_Column-1){x=0;y=y+2;}
- if(SIZE ==16)
- {
- OLED_Set_Pos(x,y);
- for(i=0;i<8;i++)
- OLED_WR_Byte(F8X16[c*16+i],OLED_DATA);
- OLED_Set_Pos(x,y+1);
- for(i=0;i<8;i++)
- OLED_WR_Byte(F8X16[c*16+i+8],OLED_DATA);
- }
- else {
- OLED_Set_Pos(x,y+1);
- for(i=0;i<6;i++)
- OLED_WR_Byte(F6x8[c][i],OLED_DATA);
-
- }
- }
- //m^n函数
- u32 oled_pow(u8 m,u8 n)
- {
- u32 result=1;
- while(n--)result*=m;
- return result;
- }
- //显示2个数字
- //x,y :起点坐标
- //len :数字的位数
- //size:字体大小
- //mode:模式 0,填充模式;1,叠加模式
- //num:数值(0~4294967295);
- void OLED_ShowNum(u8 x,u8 y,u32 num,u8 len,u8 size)
- {
- u8 t,temp;
- u8 enshow=0;
- for(t=0;t<len;t++)
- {
- temp=(num/oled_pow(10,len-t-1))%10;
- if(enshow==0&&t<(len-1))
- {
- if(temp==0)
- {
- OLED_ShowChar(x+(size/2)*t,y,' ');
- continue;
- }else enshow=1;
-
- }
- OLED_ShowChar(x+(size/2)*t,y,temp+'0');
- }
- }
- //显示一个字符号串
- void OLED_ShowString(u8 x,u8 y,u8 *chr)
- {
- unsigned char j=0;
- while (chr[j]!='\0')
- { OLED_ShowChar(x,y,chr[j]);
- x+=8;
- if(x>120){x=0;y+=2;}
- j++;
- }
- }
- //显示汉字
- void OLED_ShowCHinese(u8 x,u8 y,u8 no)
- {
- u8 t,adder=0;
- OLED_Set_Pos(x,y);
- for(t=0;t<16;t++)
- {
- OLED_WR_Byte(Hzk[2*no][t],OLED_DATA);
- adder+=1;
- }
- OLED_Set_Pos(x,y+1);
- for(t=0;t<16;t++)
- {
- OLED_WR_Byte(Hzk[2*no+1][t],OLED_DATA);
- adder+=1;
- }
- }
- /***********功能描述:显示显示BMP图片128×64起始点坐标(x,y),x的范围0~127,y为页的范围0~7*****************/
- void OLED_DrawBMP(unsigned char x0, unsigned char y0,unsigned char x1, unsigned char y1,unsigned char BMP[])
- {
- unsigned int j=0;
- unsigned char x,y;
-
- if(y1%8==0) y=y1/8;
- else y=y1/8+1;
- for(y=y0;y<y1;y++)
- {
- OLED_Set_Pos(x0,y);
- for(x=x0;x<x1;x++)
- {
- OLED_WR_Byte(BMP[j++],OLED_DATA);
- }
- }
- }
-
- int i2cdev_init()
- {
- int ret = -1;
- printf("hello,this is i2cdev_init \n");
- /* 打开eeprom对应的I2C控制器文件 */
- i2c_fd = open(iic_device, O_RDWR);
- if (i2c_fd < 0)
- {
- printf("open i2c DEVICE failed \n");
- return ret;
- }
- /*设置I2C设备地址*/
- ret = ioctl(i2c_fd,I2C_SLAVE_FORCE, I2C_ADDR);
- if ( ret < 0)
- {
- printf("set slave address failed \n");
- }
- printf("i2cdev_init ok \n");
- return ret;
- }
-
- void delay_ms(int ms)
- {
- usleep(ms*1000);
- }
-
- int main(int argc, char *argv[])
- {
- //导出DC口,这里使用的是GPIO1管脚,作为DC口使用(命令数据选择管脚)
- //system("echo 1 > /sys/class/gpio/export");
- //system("echo out >/sys/class/gpio/gpio1/direction");
- u8 t;
- i2cdev_init();
- OLED_Init();
-
- while(1)
- {
- OLED_Clear();
- OLED_ShowCHinese(0,0,0);//中
- OLED_ShowCHinese(18,0,1);//景
- OLED_ShowCHinese(36,0,2);//园
- OLED_ShowCHinese(54,0,3);//电
- OLED_ShowCHinese(72,0,4);//子
- OLED_ShowCHinese(90,0,5);//科
- OLED_ShowCHinese(108,0,6);//技
- OLED_ShowString(0,3,"1.3' OLED TEST");
- //OLED_ShowString(8,2,"ZHONGJINGYUAN");
- // OLED_ShowString(20,4,"2014/05/01");
- OLED_ShowString(0,6,"ASCII:");
- OLED_ShowString(63,6,"CODE:");
- OLED_ShowChar(48,6,t);//显示ASCII字符
- t++;
- if(t>'~')t=' ';
- OLED_ShowNum(103,6,t,3,16);//显示ASCII字符的码值
-
-
- delay_ms(8000);
- OLED_Clear();
- delay_ms(8000);
- OLED_DrawBMP(0,0,128,8,BMP1); //图片显示(图片显示慎用,生成的字表较大,会占用较多空间,FLASH空间8K以下慎用)
- delay_ms(8000);
- OLED_DrawBMP(0,0,128,8,BMP2);
- delay_ms(8000);
- }
-
- return 0;
- }
简单的makefile模板文件如下:
- # test iicdev-oled
- # author: yangyongzhen
- # qq: 534117529
- # wx: yongzhen1111
- # Copyright (C) 2023 yangyongzhen <5234117529@qq.com>
- #
- # This program is free software; you can redistribute it and/or modify
- # it under the terms of the GNU General Public License as published by
- # the Free Software Foundation; either version 2 of the License, or
- # (at your option) any later version.
-
-
- CC ?= arm-linux-gnueabihf-gcc
- AR ?= arm-linux-gnueabihf-ar
- STRIP ?= strip
-
- CFLAGS ?= -O2
- # When debugging, use the following instead
- #CFLAGS := -O -g
- CFLAGS += -Wall
- SOCFLAGS := -fpic -D_REENTRANT $(CFLAGS)
-
- KERNELVERSION := $(shell uname -r)
-
- .PHONY: all strip clean
-
- all:
- $(CC) iicdev_oled.c -o iicdev_oled
-
- clean:
- rm -rf *.o
简单测试,直接执行make即可。
https://download.csdn.net/download/qq8864/88117562
Linux下I2C-tools工具使用_i2cdetect-CSDN博客
正点原子IMX6UL 检测I2C上设备地址_imax415 iic地址-CSDN博客
Android i2cdetect i2cdump i2cget i2cset调试工具使用_i2cget命令详解-CSDN博客
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。