目录
一、IIC驱动
二、自己动手写的模拟IIC
三、OLED驱动
四、常用的OLED函数
五、咕咕咕
今天有个小老弟问我OLED的IIC驱动问题,正好我前段时间写了SPI的OLED驱动程序,也想把IIC的驱动补上,凑成一对O(∩_∩)O哈哈~ 今天就稍微总结一下IIC的驱动吧。
本次实验的平台是NodeMCU(ESP8266) 开发环境为安信可 ESP 系列一体化开发环境 SDK版本为 ver: 2.2.1
IIC有硬件IIC和软件(模拟)IIC之分,本次使用的SDK里有一份提供的IIC的库,但仔细查看之后发现这个库也是软件模拟IIC。必须用到的函数有写命令函数,写数据函数
其中IIC引脚初始化函数为
i2c_master_gpio_init();//IIC初始化
使用ESP8266SDK提供的IIC库实现的写命令函数,写数据函数:
void OLED_WrCmd(uint8 WrCmd)
{
i2c_master_start();
i2c_master_writeByte(0x78);
i2c_master_getAck();
i2c_master_writeByte(0x00);
i2c_master_getAck();
i2c_master_writeByte(WrCmd);
i2c_master_getAck();
i2c_master_stop();
}
void OLED_WrData(uint8 WrData)
{
i2c_master_start();
i2c_master_writeByte(0x78);
i2c_master_getAck();
i2c_master_writeByte(0x40);
i2c_master_getAck();
i2c_master_writeByte(WrData);
i2c_master_getAck();
i2c_master_stop();
}
需要注意的是 IIC有七位地址、八位地址和十位地址之分
在7位寻址过程中,从机地址在启动信号后的第一个字节开始传输,该字节的前7位为从机地址,第8位为读写位,其中0表示写,1表示读。
我们用到的OLED是七位寻址的,有些库函数中写地址的时候自动加入最后一位,而有些库中没有这样的操作(为了写库时候的方便),需要自己考虑最后一位(读写位)的值。比如arduino中IIC的驱动,我们写的SSD1306的地址是0x3C(00111100),而网上很多IIC的例程中 写地址的时候写的是0x78(01111000),其实他们是同一个意思,在arduino的库中 最后一位读写位由库负责添加进去(比如address = (address << 1);这样的操作),最终通信的时候在线上传输的都是0x78(01111000)。
#define I2C_MASTER_SDA_GPIO 4
#define I2C_MASTER_SCL_GPIO 5
#define I2C_MASTER_GPIO_OUT(pin,val) \
if(val) I2C_MASTER_GPIO_SET(pin);\
else I2C_MASTER_GPIO_CLR(pin)
#define delay_us os_delay_us
//开始信号
void IIC_Start(void)
{
GPIO_OUTPUT_SET(I2C_MASTER_SDA_GPIO,0);//SDA_OUT();
I2C_MASTER_GPIO_OUT(I2C_MASTER_SDA_GPIO,1);//IIC_SDA=1;
I2C_MASTER_GPIO_OUT(I2C_MASTER_SCL_GPIO,1);//IIC_SCL=1;
delay_us(2);
I2C_MASTER_GPIO_OUT(I2C_MASTER_SDA_GPIO,0);//IIC_SDA=0;
delay_us(2);
I2C_MASTER_GPIO_OUT(I2C_MASTER_SCL_GPIO,0);//IIC_SCL=0;
delay_us(2);
}
void IIC_Stop(void)
{
I2C_MASTER_GPIO_OUT(I2C_MASTER_SCL_GPIO,1);//IIC_SCL=1;
I2C_MASTER_GPIO_OUT(I2C_MASTER_SDA_GPIO,0);//IIC_SDA=0;
delay_us(2);
I2C_MASTER_GPIO_OUT(I2C_MASTER_SDA_GPIO,1);//IIC_SDA=1;
delay_us(2);
}
/*
* 返回1--应答出错
* 返回0--应答正确
*/
uint8_t IIC_Wait_Ask(void)
{
int count=0;
GPIO_DIS_OUTPUT(I2C_MASTER_SDA_GPIO);// SDA_IN();
I2C_MASTER_GPIO_OUT(I2C_MASTER_SCL_GPIO,1);//IIC_SCL=1;
delay_us(2);
while(GPIO_INPUT_GET(I2C_MASTER_SDA_GPIO)) //
{
count++;
if(count>250)
{
IIC_Stop();
return 1;
}
}
I2C_MASTER_GPIO_OUT(I2C_MASTER_SCL_GPIO,0);//IIC_SCL=0;
delay_us(2);
return 0;
}
//写一个字节
void IIC_WriteByte(uint8_t data)
{
uint8_t i;
GPIO_OUTPUT_SET(I2C_MASTER_SDA_GPIO,0);//SDA_OUT();
for(i=0;i<8;i++)
{
I2C_MASTER_GPIO_OUT(I2C_MASTER_SCL_GPIO,0);//IIC_SCL=0;
delay_us(2);
if(data & 0x80) //MSB,从高位开始一位一位传输
I2C_MASTER_GPIO_OUT(I2C_MASTER_SDA_GPIO,1);//IIC_SDA=1;
else
I2C_MASTER_GPIO_OUT(I2C_MASTER_SDA_GPIO,0);//IIC_SDA=0;
I2C_MASTER_GPIO_OUT(I2C_MASTER_SCL_GPIO,1);//IIC_SCL=1;
delay_us(2);
I2C_MASTER_GPIO_OUT(I2C_MASTER_SCL_GPIO,0);//IIC_SCL=0;
data<<=1;
}
}
uint8_t IIC_ReadByte(void)
{
uint8_t data,i;
I2C_MASTER_GPIO_OUT(I2C_MASTER_SDA_GPIO,1);//IIC_SDA=1;
delay_us(2);
for(i=0;i<8;i++)
{
data<<=1;
I2C_MASTER_GPIO_OUT(I2C_MASTER_SCL_GPIO,0);//IIC_SCL=0;
delay_us(2);
I2C_MASTER_GPIO_OUT(I2C_MASTER_SCL_GPIO,1);//IIC_SCL=1;
delay_us(2);
if(GPIO_INPUT_GET(I2C_MASTER_SDA_GPIO))//(GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_7))
data=data | 0x01;
else
data=data & 0xFE;
}
I2C_MASTER_GPIO_OUT(I2C_MASTER_SCL_GPIO,0);//IIC_SCL=0;
delay_us(2);
return data;
}
void WriteCmd(uint8_t command)
{
IIC_Start();
IIC_WriteByte(0x78);//OLED地址
IIC_Wait_Ask();
IIC_WriteByte(0x00);//寄存器地址
IIC_Wait_Ask();
IIC_WriteByte(command);
IIC_Wait_Ask();
IIC_Stop();
}
void WriteDat(uint8_t data)
{
IIC_Start();
IIC_WriteByte(0x78);//OLED地址
IIC_Wait_Ask();
IIC_WriteByte(0x40);//寄存器地址
IIC_Wait_Ask();
IIC_WriteByte(data);
IIC_Wait_Ask();
IIC_Stop();
}
#define OLED_WrCmd WriteCmd //给写命令函数重命名
#define OLED_WrData WriteDat //给写数据函数重命名
接下来驱动OLED屏幕的方法就和SPI是一样的了,向对应的寄存器中写入数据,就可以实现不同的功能
必须用到的函数有 初始化函数
常用的功能函数有清屏函数,设置起点位置函数,写字符函数等
下面是初始化函数,各个寄存器的功能写在了注释中
void OLED_Init()
{
unsigned int a;
for(a=0;a<5000;a++);
OLED_WrCmd(0xAE);//--turn off oled panel
OLED_WrCmd(0x00);//---set low column address
OLED_WrCmd(0x10);//---set high column address
OLED_WrCmd(0x40);//--set start line address Set Mapping RAM Display Start Line (0x00~0x3F)
OLED_WrCmd(0xB0);
OLED_WrCmd(0x81);//--set contrast control register
OLED_WrCmd(0xFF); // Set SEG Output Current Brightness
OLED_WrCmd(0xa1);//--Set SEG/Column Mapping 0xa0左右反置 0xa1正常
OLED_WrCmd(0xc8);//Set COM/Row Scan Direction 0xc0上下反置 0xc8正常
OLED_WrCmd(0xa6);//--set normal display
OLED_WrCmd(0xa8);//--set multiplex ratio(1 to 64)
OLED_WrCmd(0x3f);//--1/64 duty
OLED_WrCmd(0xd3);//-set display offset Shift Mapping RAM Counter (0x00~0x3F)
OLED_WrCmd(0x00);//-not offset
OLED_WrCmd(0xd5);//--set display clock divide ratio/oscillator frequency
OLED_WrCmd(0x80);//--set divide ratio, Set Clock as 100 Frames/Sec
OLED_WrCmd(0xd9);//--set pre-charge period
OLED_WrCmd(0xf1);//Set Pre-Charge as 15 Clocks & Discharge as 1 Clock
OLED_WrCmd(0xda);//--set com pins hardware configuration
OLED_WrCmd(0x12);
OLED_WrCmd(0xdb);//--set vcomh
OLED_WrCmd(0x40);//Set VCOM Deselect Level
OLED_WrCmd(0x20);//-Set Page Addressing Mode (0x00/0x01/0x02)
OLED_WrCmd(0x02);//
OLED_WrCmd(0x8d);//--set Charge Pump enable/disable
OLED_WrCmd(0x14);//--set(0x10) disable
OLED_WrCmd(0xa4);// Disable Entire Display On (0xa4/0xa5)
OLED_WrCmd(0xa6);// Disable Inverse Display On (0xa6/a7)
OLED_WrCmd(0xaf);//--turn on oled panel
OLED_Clear();//OLED清屏
}
void OLED_Clear(void)
{
unsigned char i,n;
for(i=0; i<8; i++)
{
OLED_WrCmd(0xb0+i); //设置页地址(0~7)
OLED_WrCmd(0x00); //设置显示位置—列低地址
OLED_WrCmd(0x10); //设置显示位置—列高地址
for(n=0; n<128; n++) OLED_WrData(0x00); //写0x00到屏幕寄存器上
}
}
void OLED_SetPos(uint8 x, uint8 y)
{
WriteCmd(0xb0+y);
WriteCmd(((x&0xf0)>>4)|0x10);
WriteCmd(x&0x0f);
}
//------将OLED从休眠中唤醒------
void OLED_ON(void)
{
WriteCmd(0X8D); //设置电荷泵
WriteCmd(0X14); //开启电荷泵
WriteCmd(0XAF); //OLED唤醒
}
//------让OLED休眠 -- 休眠模式下,OLED功耗不到10uA------
void OLED_OFF(void)
{
WriteCmd(0X8D); //设置电荷泵
WriteCmd(0X10); //关闭电荷泵
WriteCmd(0XAE); //OLED休眠
}
//--------------------------------------------------------------
// Parameters : x,y -- 起始点坐标(x:0~127, y:0~7); ch[] -- 要显示的字符串; TextSize -- 字符大小(1:6*8 ; 2:8*16)
// Description : 显示codetab.h中的ASCII字符,有6*8和8*16可选择
//--------------------------------------------------------------
void OLED_ShowStr(unsigned char x, unsigned char y, unsigned char ch[], unsigned char TextSize)
{
unsigned char c = 0,i = 0,j = 0;
switch(TextSize)
{
case 1:
{
while(ch[j] != '\0')
{
c = ch[j] - 32;
if(x > 126)
{
x = 0;
y++;
}
OLED_SetPos(x,y);
for(i=0; i<6; i++)
WriteDat(F6x8[c][i]);
x += 6;
j++;
}
}
break;
case 2:
{
while(ch[j] != '\0')
{
c = ch[j] - 32;
if(x > 120)
{
x = 0;
y++;
}
OLED_SetPos(x,y);
for(i=0; i<8; i++)
WriteDat(F8X16[c*16+i]);
OLED_SetPos(x,y+1);
for(i=0; i<8; i++)
WriteDat(F8X16[c*16+i+8]);
x += 8;
j++;
}
}
break;
}
}
//****************功能描述: 显示6*8或8*16的5位整数 显示的坐标(x,y),y为页范围0~7****************************
/*例
OLED_ShowInt(0,0,0,1); //在(0,0)处,显示6*8的"0"
OLED_ShowInt(5,4,12345,2);//在(5,4)处,显示8*16的"12345"
*/
void OLED_ShowInt(unsigned char x, unsigned char y, int Data, unsigned char TextSize)
{
unsigned char temp;
OLED_SetPos(x,y);
switch(TextSize)
{
case 1:
{
if(Data<0)
{
OLED_ShowChar(x,y,'-',1);
x+=6;
Data=-Data;
}
//接下来要显示正数,清空上一次显示负数的个位
//负数比正数多一个负号,额外占了一个显示位
OLED_ShowChar(x+30,y,' ',1);
temp=Data/10000;
OLED_ShowChar(x,y,(temp+'0'),1);
Data%=10000;
temp=Data/1000;
OLED_ShowChar(x+6,y,(temp+'0'),1);
Data%=1000;
temp=Data/100;
OLED_ShowChar(x+12,y,(temp+'0'),1);
Data%=100;
temp=Data/10;
OLED_ShowChar(x+18,y,(temp+'0'),1);
Data%=10;
temp=Data;
OLED_ShowChar(x+24,y,(temp+'0'),1);
}
break;
case 2:
{
if(Data<0)
{
OLED_ShowChar(x,y,'-',2);
x+=8;
Data=-Data;
}
//接下来要显示正数,清空上一次显示负数的个位
//负数比正数多一个负号,额外占了一个显示位
OLED_ShowChar(x+40,y,' ',2);
temp=Data/10000;
OLED_ShowChar(x,y,(temp+'0'),2);
Data%=10000;
temp=Data/1000;
OLED_ShowChar(x+8,y,(temp+'0'),2);
Data%=1000;
temp=Data/100;
OLED_ShowChar(x+16,y,(temp+'0'),2);
Data%=100;
temp=Data/10;
OLED_ShowChar(x+24,y,(temp+'0'),2);
Data%=10;
temp=Data;
OLED_ShowChar(x+32,y,(temp+'0'),2);
}
break;
}
}
/***************功能描述:显示6*8或8*16一个标准ASCII字符串 显示的坐标(x,y),y为页范围0~7****************/
/*例: OLED_ShowChar(39,0,'A',1)*/
void OLED_ShowChar(unsigned char x, unsigned char y, unsigned char ch, unsigned char TextSize)
{
unsigned char c=0,i=0;
c =ch-32;
if(x>120)
{
x=0;
y++;
}
OLED_SetPos(x,y);
switch(TextSize)
{
case 1:
{
for(i=0; i<6; i++)
WriteDat(F6x8[c][i]);
break;
}
case 2:
{
for(i=0; i<8; i++)
WriteDat(F8X16[c*16+i]);
OLED_SetPos(x,y+1);
for(i=0; i<8; i++)
WriteDat(F8X16[c*16+i+8]);
x += 8;
break;
}
}
}
//--------------------------------------------------------------
// Parameters : x0,y0 -- 起始点坐标(x0:0~127, y0:0~7); x1,y1 -- 起点对角线(结束点)的坐标(x1:1~128,y1:1~8)
// Description : 显示BMP位图
//--------------------------------------------------------------
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
以上常用的OLED函数还可以替换为另外一套完整的OLED屏幕显示系统,是参照了Arduino的oled函数库的C语言版本,该版本我会看心情上传到我的github上。