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

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 : 写入或者读出数据的地址 buf0

(**注:**不要忘记给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即可。

其他资源

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博客

相关推荐
AlfredZhao20 小时前
vi 删除指定范围的行,不用再反复按 dd
linux·vi
用户9718356334661 天前
银河麒麟 KY10 申威(SW64) 安装 nginx-1.16.1-2.p01.ky10.sw_64.rpm 详细步骤
linux
猪脚踏浪1 天前
linux 拷贝文件或目录到指定的位置
linux
摇滚侠2 天前
Linux CentOS7 rpm 安装 MySQL 5.7
linux·运维·mysql
bush42 天前
嵌入式linux学习记录十四、术语
linux·嵌入式
载数而行5202 天前
Linux 11 动态监控指令top
linux
不会C语言的男孩2 天前
Linux 系统编程 · 第 8 章:进程基础
linux·c语言
古城小栈2 天前
Unix 与 Linux 异同小叙
linux·服务器·unix
凡人叶枫2 天前
Effective C++ 条款42:了解 typename 的双重意义
java·linux·服务器·c++
2601_961875242 天前
决战申论100题2026|最新|范文
linux·容器·centos·debian·ssh·fabric·vagrant