linux系统下操作I2C总线外设(imx6ull的oled显示屏i2c驱动笔记)

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/目录下的文件内容。

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扫描每一个总线的所有设备。

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

OLED显示屏驱动demo

/*
# 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即可。

其他资源

0.96 OLED显示屏案例

https://download.csdn.net/download/qq8864/88117562

Linux下I2C-tools工具使用_i2cdetect-CSDN博客

正点原子IMX6UL 检测I2C上设备地址_imax415 iic地址-CSDN博客

Android i2cdetect i2cdump i2cget i2cset调试工具使用_i2cget命令详解-CSDN博客

模拟I2C/IIC协议_csi 模拟iic,i2c-CSDN博客

  • 24
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
imx6ull是一款基于Arm架构的处理器,它支持I2C总线I2C驱动分为两部分:I2C控制器驱动I2C设备驱动I2C总线在寻址时使用从位,并且如果启用了中断,Arm平台会中断并检查从读/写位的状态。在复位后,默认情况下,I2C处于从接收操作状态,除非作为主设备操作或响应从设备发送地址。需要注意的是,imx6ullI2C设计与PhilipsTM I2C总线协议兼容,并且仅支持标准和快速模式。有关更多配置、协议和限制的信息,可以参考飞利浦半导体公司的I2C总线规范2.1版。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* [i.MX 6ULL 驱动开发 二十一:I2C(I2C系统+MISC子系统)](https://blog.csdn.net/OnlyLove_/article/details/127739570)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] - *2* *3* [IMX6ULL平台的I2C](https://blog.csdn.net/weixin_52849254/article/details/130844599)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

特立独行的猫a

您的鼓励是我的创作动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值