0.96寸OLED屏幕
0.96寸:屏幕对角线长0.96英寸
驱动:SSD1306驱动IC,驱动芯片内置128*64的SRAM存储器,用于缓存要显示的数据
分辨率:128*64(横128,竖64)
供电:3.3V(驱动内部内置升压电路,会将3.3升到7),需要与stm32共地(GND接在stm32上)
针脚:4脚(GND、VCC、SCL、SDA)
从机地址:0x78
指令操作前缀:
先发0x00表示后面的内容是对SSD1306驱动的控制命令;
先发0x40表示后面的内容是要显示的数据
显示流程:
页列显示,每列会以从上到下(上面是低位,下面的高位)的方式显示,显示8bit后自动换到右边的列,重复流程。
超过第127列后自动从本页开头覆盖显示,不自动跳页。1亮,0暗。
显示操作:对目标列进行显示,需要写命令操作。
对页操作:命令高五位是10110(0xB0)时,命令低三位则是页(8行)地址。页地址范围是0~8。
对列操作: 命令高四位为0001(0x10)时,命令低四位则为x坐标的高四位。命令高四位为0000(0x00)时,命令低四位则为x坐标的低四位。
硬件I2C
STM32F106C8T6的I2C资源:I2C1、I2C2。
I2C1的SCL引脚是GPIOB_Pin_6,I2C1的SDA引脚是GPIOB_Pin_7。
I2C1的SCL引脚是GPIOB_Pin_10,I2C1的SDA引脚是GPIOB_Pin_11。
引脚配置:开漏输出
输出:SDA、SCL
工作流程:
启动+等待启动——>7位地址+读写操作(0写1读+)+等待发送完成——>传输数据.+等待传输数据完成....——>停止
数据传输方式:高位先行
工作原理:
启动条件:SCL高时,下拉SDA
停止条件:SCL高时,上拉SDA
发送数据:在完成启动条件后,在SCL高电平时,会发送SDA的数据(0/1);在SCL低电平时,更改SDA上的数据(0/1)。发送方发送完一位数据(8bit)后,需要接收方发送<接收应答>(0/1,0应答,1不应答),然后发送发接收这个应答位来判断发送的数据是否被读取。
硬件资源特质:硬件I2C的收发不需要人为注意应答位,但需要等待各个阶段操作后的标志位生成,这类似于等待软件的应答位。
细节注意:
SCL由STM32控制,所以就算是从机要发送应答,也是由STM32改变SCL的电平去加载数据,同时需要STM32去释放对SDA的控制(拉高SDA)
当STM32是发送方时,STM32在SCL高电平时发送SDA的数据,而从机在这个时候读取SDA的数据;当从机是发送方时,而OLED在SCL高电平时发送的数据,STM32在SCL时接收SDA的数据;
当STM32是接收方时,STM32发送的应答位(0继续发/1别发了);与从机是接收方是,从机发送的应答位(0收到了/1没收到)的含义不同。
软件模拟和硬件实现的区别:
软件:由个人自己写函数(具体看STM32软件模拟I2C驱动0.96寸OLED屏幕-优快云博客)
① 启动条件
② 发送从机地址+读写
③ 发送数据/指令
④终止条件
硬件:使用已有的函数,一切等待的格式都是:while( (I2Cx,等待事件的名称) == ERROR )
①判断总线是否忙(while死循环)
函数:
while (I2C_GetFlagStatus(I2C2, I2C_FLAG_BUSY));
②启动条件+等待启动完成
函数:
I2C_GenerateSTART (I2C2, ENABLE);
while(I2C_CheckEvent(I2C2, I2C_EVENT_MASTER_MODE_SELECT) == ERROR);
③从机地址+写入+等待发送完成
(0x78是OLED的从机地址,I2C_Direction_Transmitter是发送模式,即写入模式)
函数:
I2C_Send7bitAddress(I2C2,0X78,I2C_Direction_Transmitter);
while(I2C_CheckEvent(I2C2, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED) == ERROR);
④指令地址/数据地址+等待发送完成
(根据传参addr来判断 <后面输入的数据data> 是 <对SSD1306操作的指令> 还是 <OLED要显示的内容>)
函数:
I2C_SendData(I2C2,addr);
while(I2C_CheckEvent(I2C2, I2C_EVENT_MASTER_BYTE_TRANSMITTING) == ERROR);//等待数据的发送完成
⑤data+等待发送完成
(根据前面addr的操作地址 来确认data 是 <对SSD1306操作的指令> 还是 <OLED要显示的内容>)
函数:
I2C_SendData(I2C2,data);
while(I2C_CheckEvent(I2C2, I2C_EVENT_MASTER_BYTE_TRANSMITTED) == ERROR);
⑥结束
函数:
I2C_GenerateSTOP (I2C2,ENABLE);
主要代码
硬件I2C及其GPIO口的初始化
void MyI2C_Init(void)
{
/*开启时钟*/
RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C2, ENABLE); //开启I2C2的时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); //开启GPIOB的时钟
/*GPIO初始化*/
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_OD;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10 | GPIO_Pin_11;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure); //将PB10和PB11引脚初始化为复用开漏输出
/*I2C初始化*/
I2C_InitTypeDef I2C_InitStructure; //定义结构体变量
I2C_InitStructure.I2C_Mode = I2C_Mode_I2C; //模式,选择为I2C模式
I2C_InitStructure.I2C_ClockSpeed = 50000; //时钟速度,选择为50KHz
I2C_InitStructure.I2C_DutyCycle = I2C_DutyCycle_2; //时钟占空比,选择Tlow/Thigh = 2
I2C_InitStructure.I2C_Ack = I2C_Ack_Enable; //应答,选择使能
I2C_InitStructure.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit; //应答地址,选择7位,从机模式下才有效
I2C_InitStructure.I2C_OwnAddress1 = 0x00; //自身地址,从机模式下才有效
I2C_Init(I2C2, &I2C_InitStructure); //将结构体变量交给I2C_Init,配置I2C2
/*I2C使能*/
I2C_Cmd(I2C2, ENABLE); //使能I2C2,开始运行
}
字节发送
void OLED_Byte_Write(uint8_t addr, uint8_t data)
{
while (I2C_GetFlagStatus(I2C2, I2C_FLAG_BUSY)); //检查I2C总线是否繁忙
I2C_GenerateSTART (I2C2, ENABLE); //产生起始信号
while(I2C_CheckEvent(I2C2, I2C_EVENT_MASTER_MODE_SELECT) == ERROR);
//EV5事件被检测,发送设备地址
I2C_Send7bitAddress(I2C2,0X78,I2C_Direction_Transmitter);//第几个i2c i2c通信的从机地址 写/发送模式(模式选择0写/1读)
while(I2C_CheckEvent(I2C2, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED) == ERROR);//等待 7bit从机地址+1位读写操作 的指令发送完成
//EV6事件被检测,发送要操作的存储单元地址
I2C_SendData(I2C2,addr);//根据传参addr来判断 <后面输入的数据data> 是 <对SSD1306操作的指令> 还是 <OLED要显示的内容>
while(I2C_CheckEvent(I2C2, I2C_EVENT_MASTER_BYTE_TRANSMITTING) == ERROR);//等待数据的发送完成
//EV8事件被检测,发送要存储的数据
I2C_SendData(I2C2,data);//根据前面addr的操作地址 来确认data 是 <对SSD1306操作的指令> 还是 <OLED要显示的内容>
while(I2C_CheckEvent(I2C2, I2C_EVENT_MASTER_BYTE_TRANSMITTED) == ERROR);//等待数据的发送完成
//数据传输完成
I2C_GenerateSTOP (I2C2,ENABLE);//结束发送
}
发送命令/数据
void OLED_WriteCommand(uint8_t IIC_Command)
{
OLED_Byte_Write(0x00, IIC_Command);//先发0x00表示后面的内容是对SSD1306驱动的控制命令;
}
void OLED_WriteData(uint8_t IIC_Data)
{
OLED_Byte_Write(0x40, IIC_Data);//先发0x40表示后面的内容是要显示的数据
}
指定页列显示
void OLED_SetCursor(uint8_t pag, uint8_t x)
{
//命令高五位是10110时,命令低三位则是页地址。页地址范围是0~8
OLED_WriteCommand(0xB0 | pag);//取出数据低四位
//命令高四位为0001时,命令低四位则为x坐标的高四位。
OLED_WriteCommand(0x10 | (x & 0xF0) >> 4);//取出数据高四位
//命令高四位为0000时,命令低四位则为x坐标的低四位
OLED_WriteCommand(0x00 | (x & 0x0F));//取出数据低四位
}
OLED初始化(参考自江科大,但是多次运行后显示起点终点有误,且会自动换页)
void OLED_Init()
{
uint32_t i, j;
for (i = 0; i < 1000; i++)
for (j = 0; j < 1000; j++);//上电延时
MyI2C_Init(); //GPIO端口初始化
OLED_WriteCommand(0xAE); //关闭显示
OLED_WriteCommand(0xD5); //设置显示时钟分频比/振荡器频率
OLED_WriteCommand(0x80);
OLED_WriteCommand(0xA8); //设置多路复用率
OLED_WriteCommand(0x3F);
OLED_WriteCommand(0xD3); //设置显示偏移
OLED_WriteCommand(0x00);
OLED_WriteCommand(0x40); //设置显示开始行
OLED_WriteCommand(0xA1); //设置左右方向,0xA1正常 0xA0左右反置
OLED_WriteCommand(0xC8); //设置上下方向,0xC8正常 0xC0上下反置
OLED_WriteCommand(0xDA); //设置COM引脚硬件配置
OLED_WriteCommand(0x12);
OLED_WriteCommand(0x81); //设置对比度控制
OLED_WriteCommand(0xCF);
OLED_WriteCommand(0xD9); //设置预充电周期
OLED_WriteCommand(0xF1);
OLED_WriteCommand(0xDB); //设置VCOMH取消选择级别
OLED_WriteCommand(0x30);
OLED_WriteCommand(0xA4); //设置整个显示打开/关闭
OLED_WriteCommand(0xA6); //设置正常/倒转显示
OLED_WriteCommand(0x8D); //设置充电泵
OLED_WriteCommand(0x14);
OLED_WriteCommand(0xAF); //开启显示
for (i = 0; i < 1000; i++) //上电延时
for (j = 0; j < 1000; j++);
}