前言
使用stm32 驱动4 Pin 的OLED, 现在网上开源的资料多的是,但是为了锻炼自己使用第一手资料的能力,今天我还是从数据手册开始,从头造一波轮子,同时也是为了加深自己对 IIC 协议的理解 ,本系列内容我会从单片机和linux两个板子做一些OLED的 验证,希望后面大家在学习IIC 相关内容的时候,可以少走一些弯路。为了大家学习方面,这些文章也会在我的微信公众号同步,方便大家随时查看,欢迎大家扫码关注。
1. 查阅数据手册
在我们实际工作中,需要我们从头造轮子的机会其实并不多,但是作为搞嵌入式这个行业的,阅读一些简单的数据手册的能力还是要有的,万一碰到碰到一个比较棘手的问题,说不定就需要我们不得不去看芯片的参考手册了。本篇我就以4Pin 的OLED 使用的SSD1306为例,通过stm32 实现对他的驱动。我们 打开他的数据手册,首先不要被全英文的吓到了,现在网上各种的翻译软件都比较给力。再说了,这个手册其实并不需要从头到尾一字不落的读完,首先我们看一下芯片的简单说明,了解芯片的大致的特性。其次:根据自己选择的芯片,使用的通讯方式,着重去看相对应的章节,比如我们本次使用的就是4 Pin的 IIC通信的,所以我们直接去找参考手册中介绍IIC 的.
a. IIC 从机地址是有SA0 决定的,并且R/W# 决定是读取数据还是写入数据。R/W# = 0 为写模式,R/W# = 1 为读数据。
IIC 通信协议:
S 信号 和 P 信号:
ACK 信号 和NAck 信号:
数据传输的时候要保证SDA 总线上数据稳定:
发送数据还是命令室友D/C# 引脚决定的: D/C# = 1 发送data , D/C# = 0 发送的是命令;
SSD 1306 显存大小: 128列 * 64行
如果要驱动SSD1306 首先要实现IIC 总线的驱动代码,即IIC 协议的实现,如果使用的是stm32单片机 来驱动的话,其实可以直接使用硬件IIC,当然了软件IIC也是可以的,本篇我们就先以软件模拟IIC来驱动他。
2. 软件模拟IIC 实现
2.1
#define SCL_Pin GPIO_Pin_6
#define SDA_Pin GPIO_Pin_7
#define IIC_GPIO_Port GPIOB#define OLED_SCLK_LOW GPIO_ResetBits(GPIOB,GPIO_Pin_6)
#define OLED_SCLK_HIGH GPIO_SetBits(GPIOB,GPIO_Pin_6)
#define OLED_SDA_LOW GPIO_ResetBits(GPIOB,GPIO_Pin_7)
#define OLED_SDA_HIGH GPIO_SetBits(GPIOB,GPIO_Pin_7)void OLED_Config(void)
{ GPIO_InitTypeDef GPIO_InitStructure;RCC_APB2PeriphClockCmd( RCC_APB2Periph_GPIOB, ENABLE ); GPIO_InitStructure.GPIO_Pin = SCL_Pin|SDA_Pin;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP ; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(IIC_GPIO_Port, &GPIO_InitStructure);
}/*IIC 起始信号*/
void i2c_start()
{OLED_SCLK_HIGH;OLED_SDA_HIGH;OLED_SDA_LOW;OLED_SCLK_LOW;
}/*IIC 停止信号*/
void i2c_stop()
{OLED_SCLK_HIGH;OLED_SDA_LOW;OLED_SDA_HIGH;}/*等待应答:提供一个scl 时钟周期*/
void i2c_wait_ack(void)
{OLED_SCLK_HIGH;OLED_SCLK_LOW;
}void Write_IIC_Byte(unsigned char IIC_Byte)
{unsigned char i;unsigned char m,da;da=IIC_Byte;OLED_SCLK_LOW;for(i=0;i<8;i++) {m=da;m=m&0x80;if(m==0x80){OLED_SDA_HIGH;}else OLED_SDA_LOW;da=da<<1;OLED_SCLK_HIGH;OLED_SCLK_LOW;}
}/**********************************************
// IIC Write Command
**********************************************/
void Write_IIC_Command(unsigned char IIC_Command)
{i2c_start();Write_IIC_Byte(0x78); //Slave address,SA0=0i2c_wait_ack(); Write_IIC_Byte(0x00); //write commandi2c_wait_ack(); Write_IIC_Byte(IIC_Command); i2c_wait_ack(); i2c_stop();
}/**********************************************
// IIC Write Data
**********************************************/
void Write_IIC_Data(unsigned char IIC_Data)
{i2c_start();Write_IIC_Byte(0x78); //D/C#=0; R/W#=0i2c_wait_ack(); Write_IIC_Byte(0x40); //write datai2c_wait_ack(); Write_IIC_Byte(IIC_Data);i2c_wait_ack(); i2c_stop();
}void OLED_WR_Byte(unsigned dat,unsigned cmd)
{if(cmd){Write_IIC_Data(dat);}else {Write_IIC_Command(dat); }
}
3. OLED 设备驱动程序
void OLED_Init(void)
{ OLED_Config();GPIO_SetBits(GPIOB,GPIO_Pin_6|GPIO_Pin_7); delay_ms(100);OLED_WR_Byte(0xAE,OLED_CMD);//--display offOLED_WR_Byte(0x00,OLED_CMD);//---set low column addressOLED_WR_Byte(0x10,OLED_CMD);//---set high column addressOLED_WR_Byte(0x40,OLED_CMD);//--set start line address OLED_WR_Byte(0xB0,OLED_CMD);//--set page addressOLED_WR_Byte(0x81,OLED_CMD); // contract controlOLED_WR_Byte(0xFF,OLED_CMD);//--128 OLED_WR_Byte(0xA1,OLED_CMD);//set segment remap OLED_WR_Byte(0xA6,OLED_CMD);//--normal / reverseOLED_WR_Byte(0xA8,OLED_CMD);//--set multiplex ratio(1 to 64)OLED_WR_Byte(0x3F,OLED_CMD);//--1/32 dutyOLED_WR_Byte(0xC8,OLED_CMD);//Com scan directionOLED_WR_Byte(0xD3,OLED_CMD);//-set display offsetOLED_WR_Byte(0x00,OLED_CMD);//OLED_WR_Byte(0xD5,OLED_CMD);//set osc divisionOLED_WR_Byte(0x80,OLED_CMD);//OLED_WR_Byte(0xD8,OLED_CMD);//set area color mode offOLED_WR_Byte(0x05,OLED_CMD);//OLED_WR_Byte(0xD9,OLED_CMD);//Set Pre-Charge PeriodOLED_WR_Byte(0xF1,OLED_CMD);//OLED_WR_Byte(0xDA,OLED_CMD);//set com pin configuartionOLED_WR_Byte(0x12,OLED_CMD);//OLED_WR_Byte(0xDB,OLED_CMD);//set VcomhOLED_WR_Byte(0x30,OLED_CMD);//OLED_WR_Byte(0x8D,OLED_CMD);//set charge pump enableOLED_WR_Byte(0x14,OLED_CMD);//OLED_WR_Byte(0xAF,OLED_CMD);//--turn on oled panel
} 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), OLED_CMD);
}void OLED_Display_On(void)
{OLED_WR_Byte(0x8D,OLED_CMD); //设置电荷泵OLED_WR_Byte(0x14,OLED_CMD); //开启电荷泵OLED_WR_Byte(0xAF,OLED_CMD); //OLED唤醒
}void OLED_Display_Off(void)
{OLED_WR_Byte(0x8D,OLED_CMD); //设置电荷泵OLED_WR_Byte(0x10,OLED_CMD); //关闭电荷泵OLED_WR_Byte(0xAE,OLED_CMD); //关闭屏幕显示
}void OLED_Clear(void)
{u8 i = 0, n = 0; for(i = 0; i < 8; i++){OLED_WR_Byte(0xb0+i,OLED_CMD); //设置页地址OLED_WR_Byte(0x00,OLED_CMD); // 设置显示位置-列低地址OLED_WR_Byte(0x10,OLED_CMD); // 设置显示位置-列高地址for(n = 0; n < 128; n++) OLED_WR_Byte(0,OLED_DATA);}
}void OLED_ShowChar(u8 x, u8 y, u8 chr, u8 Char_size)
{unsigned char c = 0, i = 0;c = chr - ' ';if(x > 128 - 1){x = 0;y += 2;}if(Char_size == 8){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 {字体为6号OLED_Set_Pos(x,y);for(i=0;i<6;i++)OLED_WR_Byte(F6x8[c][i],OLED_DATA);}
}void OLED_ShowString(u8 x, u8 y,u8* chr, u8 Char_size)
{while(*chr != '\0'){OLED_ShowChar(x,y,*chr, Char_size);x+=Char_size;if(x > 120){x = 0;y += 2;} chr++;}
}