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.总结分享(学到的写出来也教会别人,费曼学习法)
linux下的i2c介绍
在硬件层中,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总线上的外设流程框图:
在应用程序中读写I2C设备
通过前面的介绍,我们已经知道站在cpu的角度来看,操作I2C外设实际上就是通过控制cpu中挂载该I2C外设的I2C控制器,而这个I2C控制器在linux系统中被称为"I2C适配器",而且在linux系统中,每一个设备都是以文件的形式存在的,所以在linux中操作I2C外设就变成了操作I2C适配器设备文件。
Linux系统(也就是内核)为每个I2C适配器生成了一个主设备号为89的设备节点(次设备号为0-255),它并没有针对特定的I2C外设而设计,只是提供了通用的read(),write(),和ioctl()等文件操作接口,在用户空间的应用层就可以借用这些接口访问挂接在适配器上的I2C设备的存储空间或寄存器,并控制I2C设备的工作方式。
操作流程
1.确定I2C适配器的设备文件节点
i2c适配器的设备节点是/dev/i2c-x,其中x是数字。由于适配器编号是动态分配的(和注册次序有关),所以想了解哪一个适配器对应什么编号,可以查看/sys/class/i2c-dev/目录下的文件内容。
bash
cat /sys/class/i2c-dev/i2c-0/name
cat /sys/class/i2c-dev/i2c-1/name
然后查看硬件原理图外设是挂在cpu的哪个控制器中,或者借助i2ctools工具可以测试出来。
本次实验我的oled外设屏对应的I2C控制器的设备节点为:/dev/i2c-0。
2.打开适配器对应的设备节点
当用户打开适配器设备节点的时候,Kernel中的i2c-dev代码为其建立一个i2c_client,但是这个i2c_client并不加到i2c_adapter的client链表当中。当用户关闭设备节点时,它自动被释放。
3.IOCTL控制
这个可以参考内核源码中的include/linux/i2c-dev.h文件。下面举例说明主要的IOCTL命令:
- I2C_SLAVE_FORCE 设置I2C从设备地址(只有在该地址空闲的
- 情况下成功。)
- I2C_SLAVE_FORCE 强制设置I2C从设备地址(无论内核中是否已有驱动在使用这个地址都会成功)
- I2C_TENBIT 选择地址位长:
- 0 表示是7bit地址 ;
- 不等于0 就是10 bit的地址。只有适配器支持I2C_FUNC_10BIT_ADDR,这个请求才是有效的。
- I2C_FUNCS 获取适配器支持的功能,详细的可以参考文件include/linux/i2c.h
- I2C_RDWR 设置为可读写
- I2C_RETRIES 设置收不到ACK时的重试次数
- I2C_TIMEOUT 设置超时的时限
4.使用I2C协议和设备进行通信
代码: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分配内存。)
5.用read和write读写I2C设备
可以使用read()/write()来与I2C设备进行通信。
第一,打开I2C控制器文件节点: fd =open("/dev/i2c-0", O_RDWR);
第二,设置设备的设备地址:ioctl(fd,I2C_SLAVE, 0x50);
第三,向设备读写数据。
OLED显示屏介绍
SSD1306芯片框图
IIC 通讯
- 地址:
0111100'R/W#'
('R/W#'=0,写模式;'R/W#'=1,读模式) - 数据格式: (IIC地址) +控制字节 (1 Byte) +数据字 (n Byte)
命令/数据 标志位
控制字: 0'D/C'000000
('D/C'=0,命令;'D/C'=1,数据)
i2c-tools介绍
i2cdetect -l查看当前系统的I2C总线
总线挂载了I2C设备,可通过i2cdetect扫描每一个总线的所有设备。
bash
i2cdetect -l
i2cdetect -y -r 1 :查看总线1上的所有从设备("--"表示地址被检测到了,但是没有芯片,"UU"表示地址正在被某一个驱动使用,而16进制的地址号3c)
bash
i2cdetect -y -r 1
查询总线1(I2C -1)的功能,命令:
bash
i2cdetect -F 1
i2cget:获取某一个总线上某一个从设备的寄存器值。
如下:获取1总线从设备0x62寄存器00的值。
bash
i2cget -f -y 1 0x62 0x00
-f:强制访问设备
-y:取消交互模式。默认情况下,i2cdetect将等待用户的确认,当使用此标志时,它将直接执行操作。
i2cset 设置某一个总线上某一个从设备的寄存器的值。
如下:设置1总线从设备0x62寄存器00的值为0x00
bash
i2cset -f -y 1 0x62 0x00 0x00
设置i2c1上从地址为0x62的外设0x00寄存器的值为0x00
i2cdump:查看某一个总线上某一个从设备所有寄存器的值,寄存器地址为8位
如下:查看i2c1上0x62外设所有寄存器的值
bash
i2cdump -f -y 1 0x62
i2ctranfer:向寄存器地址为16位的从设备读取或者写入数据
bash
i2ctransfer -f -y 1 w2@0x62 0x00 0x00 r32
读取
1:哪条总线
w2:写两个字节地址
0x00 0x00:寄存器地址
r32:往后32个寄存器所对应的寄存器值
bash
i2ctransfer -f -y 1 w2@0x62 0x00 0x00 0x10
1:哪条总线
w2:写两个字节地址
0x00 0x00:寄存器地址
0x10:0x00 0x00寄存器地址往后的寄存器写入0x10
OLED显示屏驱动demo
cpp
/*
# 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模板文件如下:
bash
# 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博客