中景园OLED代码例程分析

OLED简介:

        OLED,全称为Organic Light Emitting Diode,即有机发光二极管。它是一种新型的显示技术,与传统的LCD(液晶显示器)相比,OLED具有更快的响应速度、更高的对比度、更广的可视角度、更低的功耗等优点。

        OLED的显示原理是利用有机材料在电场的作用下发光,它可以在每个像素点上独立控制亮度和颜色。因此,OLED可以实现更加细腻的图像和更加鲜艳的色彩效果。与此同时,OLED还具有更加薄、柔性和可制造性等优点,可以应用于各种场合,如智能手机、电视、车载显示器、手表、智能家居等。

        OLED的缺点是寿命短、成本高、易受光照影响等。最重要的是现在还不能做出性能很好的大屏OLED显示屏

        至于OLED原理嘛,我就不介绍了,可以参考部分OLED手册。

我用的是0.96 寸 OLED 显示屏,4脚IIC通讯模块。

OLED代码修改:

       本人在中景园代码基础上做了微小的调整。

void IIC_Start()
{

	OLED_SCLK_Set() ;
	OLED_SDIN_Set();
	OLED_SDIN_Clr();
	OLED_SCLK_Clr();
}
void IIC_Stop()
{
    OLED_SCLK_Set() ;
	OLED_SCLK_Clr();
	OLED_SDIN_Clr();
	OLED_SDIN_Set();	
}
void IIC_Wait_Ack()
{

	//GPIOB->CRH &= 0XFFF0FFFF;	//设置PB12为上拉输入模式
	//GPIOB->CRH |= 0x00080000;
    //	OLED_SDA = 1;
    //	delay_us(1);
	//OLED_SCL = 1;
	//delay_us(50000);
    /*	while(1)
	    {
		    if(!OLED_SDA)				    //判断是否接收到OLED 应答信号
		    {
			    //GPIOB->CRH &= 0XFFF0FFFF;	//设置PB12为通用推免输出模式
			    //GPIOB->CRH |= 0x00030000;
			return;
		    }
	    }
    */
	OLED_SCLK_Set();
	OLED_SCLK_Clr();
}


void Write_IIC_Byte(unsigned char IIC_Byte)
{
	unsigned char i;
	unsigned char m,da;
	da=IIC_Byte;
	OLED_SCLK_Clr();
	for(i=0;i<8;i++)		
	{
		m=da;
	  //OLED_SCLK_Clr();
		m=m&0x80;
		if(m==0x80)
		{OLED_SDIN_Set();}
		else OLED_SDIN_Clr();
			da=da<<1;
		OLED_SCLK_Set();
		OLED_SCLK_Clr();
	}
}
void Write_IIC_Command(unsigned char IIC_Command)
{
   IIC_Start();
   Write_IIC_Byte(0x78);            //Slave address,SA0=0
	IIC_Wait_Ack();	
   Write_IIC_Byte(0x00);			//write command
	IIC_Wait_Ack();	
   Write_IIC_Byte(IIC_Command); 
	IIC_Wait_Ack();	
   IIC_Stop();
}
void Write_IIC_Data(unsigned char IIC_Data)
{
   IIC_Start();
   Write_IIC_Byte(0x78);			//D/C#=0; R/W#=0
	IIC_Wait_Ack();	
   Write_IIC_Byte(0x40);			//write data
	IIC_Wait_Ack();	
   Write_IIC_Byte(IIC_Data);
	IIC_Wait_Ack();	
   IIC_Stop();
}

首先为了方便,把IIC这些函数全部用一个IIC.c和IIC.h封装,不将其放到oled.c里。其次,IIC_Start,IIC_Stop,Write_IIC_Byte,IIC_Wait_Ack这些函数做修改。

0c48fd172213490193b35968ec050bc0.png

c815ad5bc3bd4286a7bfbaa085d65de3.png        空闲状态下:SDA和SCL全为高电平。

        开始信号:当SCL为高电平时,经过SDA由高到低的跳变,加上5us延时函数,表示数据传输的开始。

        停止信号:当SCL为高电平时,经过SDA由低到高的跳变,加上5us延时函数,表示数据的结束。

        注意:为啥我这里要加5us延时函数?这是因为我选择了标准模式(100Kb/s),所以周期为10us,开始和结束函数里面的延时为1个周期,这样感觉更加直观。毕竟是软件模拟嘛!

        当然用官方写的也没问题。

接下来介绍如何写字节fc34c92159e54fb5995d83b476bade6c.png

void Write_IIC_Byte(uchar byte)
{
	uchar i;
	for(i = 0;i < 8;i++)
	{
        //
		byte <<= 1;
		IIC_SDA = CY;
        /*
        IIC_SDA =  (byte & (0x80 >> i));
        */
		IIC_SCL = 1;
		Delay5us();
		IIC_SCL = 0;
		Delay5us();		
	}
}

数据传输:

        在I2C总线上,数据传输是通过IIC_SDA引脚上的电平变化来实现的,而时钟同步则是通过IIC_SCL引脚上的电平变化来实现的。每当IIC_SCL引脚电平从低电平变为高电平时,IIC_SDA引脚上的数据才会被主机读取或写入(主机接收数据前必须要释放SDA) 。

        且根据时序图可知,当SCL为高电平时SDA的数据必须要保持稳定(读取或写入),SCL为低电平时,此时SDA的数据才能有机会跳变。

        由于在IIC通讯中,每一个字节的传输,传输的数据是从高位到低位依次传输的,即先传输最高位,最后传输最低位。因此,在将一个字节的数据写入IIC_SDA线中时,需要将最高位的数据先写入,才能保证数据的正确性,这个必须要了解!

 

        所以在for循环里,

        1、首先我们将发送的字节左移一位,将CY寄存器的值(也就是最高位)写入IIC_SDA引脚上。根据图,将SCL拉高半个周期(5us)此时是写入这个位,然后SCL拉低半个周期(5us),8次后从机控制应答,告诉主机是否成功读取数据。

        2、也可按照第二种方法。图中说明非常清楚

 

        至于应答,是在第九个周期里判断(假设就写入1个字节),将SCL拉高半个周期(5us),此时是读取这个字节,然后SCL拉低半个周期(5us)。

void IIC_Wait_Ack()
{
	IIC_SCL = 1;
	Delay5us();
	IIC_SCL = 0;
	Delay5us();	
}

        其实IIC里应该还有接收字节的函数,OLED这里没有用到,原因是因为我们不需要OLED读取数据,并触发额外的业务,比如在蓝桥杯单片机中,要是读取光敏电阻和电位器上的电压,这个时候要调用Read_IIC_Byte()这个函数。

uchar Read_IIC_Byte()
{
    uchar i = 0;
    uchar byte = 0x00;
    IIC_SDA = 1;
    for(i = 0;i < 8;i++)
    {
        IIC_SCL = 1;//
        if(IIC_SDA)
        {
            byte |= 0x80 >> i;
        }
        IIC_SCL = 0;        
    }
    return byte;
}

         解释一下,这里很巧妙,请看,我将要读取的字节设成byte,并让它初始化为0x00,之后我通过八次扫描,一旦读取的数据线内的数据为1,那么我就将byte相对应的那一位变成1,因此经过八次循环我们最终的byte就是我们读到的真实数据。

        有的朋友发现为啥要加IIC_SDA = 1;这句话?具体来说,IIC_SDA = 1语句是将SDA信号置为1,表示当前总线空闲,准备接收数据。同时,在读取数据的过程中,如果IIC_SDA = 1,表示从器件返回的数据位为1,主机需要将SDA置1,表示已经读取完了这个字节,可以进行下一个数据的读取了。

OLED代码分析:

IIC

void Write_IIC_Command(unsigned char IIC_Command)
{
   IIC_Start();
   Write_IIC_Byte(0x78);
	IIC_Wait_Ack();	
   Write_IIC_Byte(0x00);
	IIC_Wait_Ack();	
   Write_IIC_Byte(IIC_Command); 
	IIC_Wait_Ack();	
   IIC_Stop();
}

void Write_IIC_Data(unsigned char IIC_Data)
{
   IIC_Start();
   Write_IIC_Byte(0x78);
	IIC_Wait_Ack();	
   Write_IIC_Byte(0x40);
	IIC_Wait_Ack();	
   Write_IIC_Byte(IIC_Data);
	IIC_Wait_Ack();	
   IIC_Stop();
}

Write_IIC_Command函数:

        首先调用IIC_Start函数发送IIC总线的开始信号,数据开始发送。

        然后向从设备发送地址字节0x78,表示要写入数据到从设备。在这个地址字节中,SA0=0,表示从设备的地址为0x78。然后,向从设备发送命令写入标志位0x00,表示接下来要写入的是一个命令而不是数据。

        接着,向从设备发送要写入的IIC_Command命令数据。

        最后,发送IIC总线的停止信号,表示数据停止发送

 

Write_IIC_Data函数:

        首先调用IIC_Start函数发送起始信号,数据开始发送。

        调用Write_IIC_Byte函数向I2C总线上写入从机地址和写数据命令,从机地址为0x78,写数据命令为0x40.

        调用Write_IIC_Byte函数向I2C总线上写入要发送的数据(IIC_Data)。

        调用IIC_Wait_Ack函数等待从机的应答信号。

        调用IIC_Stop函数发送停止信号(Stop)。

OLED

void OLED_WR_Byte(unsigned dat,unsigned cmd)
{
	if(cmd)
	{
        Write_IIC_Data(dat);   
	}
	else 
    {
        Write_IIC_Command(dat);	
	}
}

OLED_WR_Byte函数: 作用是通过IIC总线向OLED屏幕发送数据或命令。

        参数dat表示要发送的数据,参数cmd表示数据类型,如果cmd为1,则表示要发送的是数据;如果cmd为0,则表示要发送的是命令。

        在发送命令时,首先通过OLED_WR_Byte函数将命令类型设置为0,然后将具体的命令数据发送给OLED屏幕;在发送数据时,将数据类型设置为1,然后将具体的数据发送给OLED屏幕。

void fill_picture(unsigned char fill_Data)
{
	unsigned char m,n;
	for(m=0;m<8;m++)
	{
		OLED_WR_Byte(0xb0+m,0);	
		OLED_WR_Byte(0x00,0);
		OLED_WR_Byte(0x10,0);
		for(n=0;n<128;n++)
		{
            OLED_WR_Byte(fill_Data,1);
		}
	}
}

fill_picture函数:作用是将整个屏幕都填充为指定的数据。

        使用for循环,循环8次,其中m表示页地址,范围为0到7。

        在每次循环中,通过OLED_WR_Byte函数先向OLED发送页地址,然后向OLED发送列地址的低八位和高八位,从而确定每次循环要填充的位置。

        使用for循环,循环128次,其中n表示列地址,范围为0到127。

        在每次循环中,向OLED发送指定的填充数据fill_Data。

因此这种方式可以将整个屏幕都填充为指定的数据,从而清空屏幕或者初始化屏幕。

 

#define OLED_CMD  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),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
}

OLED_Set_Pos函数:作用是设置OLED屏幕上要显示的位置。即向OLED发送页地址和列地址的低八位和高八位,从而确定要显示的位置。

        调用OLED_WR_Byte函数向OLED发送页地址,地址值为0xb0 + y,其中y表示页地址。

        调用OLED_WR_Byte函数向OLED发送列地址的高八位,地址值为((x & 0xf0) >> 4) | 0x10,其中x表示列地址。

        调用OLED_WR_Byte函数向OLED发送列地址的低八位,地址值为(x & 0x0f)。

 

OLED_Display_On函数:作用是开启OLED显示。

        调用OLED_WR_Byte函数向OLED发送SET DCDC命令,地址值为0X8D。

        调用OLED_WR_Byte函数向OLED发送DCDC ON命令,地址值为0X14。

        调用OLED_WR_Byte函数向OLED发送DISPLAY ON命令,地址值为0XAF。

OLED_Display_Off函数:作用是关闭OLED显示。:

        调用OLED_WR_Byte函数向OLED发送SET DCDC命令,地址值为0X8D。

        调用OLED_WR_Byte函数向OLED发送DCDC OFF命令,地址值为0X10。

        调用OLED_WR_Byte函数向OLED发送DISPLAY OFF命令,地址值为0XAE。

//清屏函数,清完屏,整个屏幕是黑色的!和没点亮一样!!!	  
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 (0x00,OLED_CMD);      //设置显示位置—列低地址
		OLED_WR_Byte (0x10,OLED_CMD);      //设置显示位置—列高地址   
		for(n=0;n<128;n++)OLED_WR_Byte(0,OLED_DATA); 
	} //更新显示
}
void OLED_On(void)  
{  
	u8 i,n;		    
	for(i=0;i<8;i++)  
	{  
		OLED_WR_Byte (0xb0+i,OLED_CMD);    //设置页地址(0~7)
		OLED_WR_Byte (0x00,OLED_CMD);      //设置显示位置—列低地址
		OLED_WR_Byte (0x10,OLED_CMD);      //设置显示位置—列高地址   
		for(n=0;n<128;n++)OLED_WR_Byte(1,OLED_DATA); 
	} //更新显示
}

 

OLED_Clear函数:作用是清空屏幕,将整个屏幕都填充为0,从而实现清屏效果。

        调用for循环,循环8次,其中i表示页地址,范围为0到7。

        在每次循环中,通过OLED_WR_Byte函数先向OLED发送页地址,然后向OLED发送列地址的低八位和高八位,从而确定每次循环要清空的位置。

        调用for循环,循环128次,其中n表示列地址,范围为0到127。

        在每次循环中,向OLED发送0,表示填充为黑色。

        循环结束后,屏幕灭掉。

 

OLED_On函数:作用是将整个屏幕点亮,将整个屏幕都填充为1,从而实现屏幕点亮效果。

        调用for循环,循环8次,其中i表示页地址,范围为0到7。

        在每次循环中,通过OLED_WR_Byte函数先向OLED发送页地址,然后向OLED发送列地址的低八位和高八位,从而确定每次循环要填充的位置。

        调用for循环,循环128次,其中n表示列地址,范围为0到127。

        在每次循环中,向OLED发送1,表示填充为亮色。

        循环结束后,屏幕整个被点亮,更新显示

void OLED_ShowChar(u8 x,u8 y,u8 chr,u8 Char_Size)
{      	
	unsigned char c=0,i=0;	
		c=chr-' ';//得到偏移后的值			
		if(x > Max_Column-1){x = 0;y = y+2;}
		if(Char_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);
            for(i=0;i<6;i++)
            OLED_WR_Byte(F6x8[c][i],OLED_DATA);				
		}
}

 

OLED_ShowChar函数:        作用在OLED屏幕上显示一个字符,支持两种字体大小:8x16和6x8。

 

x:横坐标         y:纵坐标         chr:显示的字符        Char_Size:字体大小。

        计算出偏移后的字符值,即将ASCII码转化为F8X16或者F6x8字库中的索引值。

        判断字符显示位置是否超出屏幕范围,如果超出则调整显示位置。

        根据选择的字体大小,使用OLED_Set_Pos函数设置显示位置。

        使用for循环,循环8次或者6次,向OLED发送对应的字符数据,以实现字符的显示。其中,F8X16和F6x8分别是8x16和6x8字体的字库数组。

u32 oled_pow(u8 m,u8 n)
{
	u32 result=1;	 
	while(n--) result*=m;    
	return result;
}				  
		  
void OLED_ShowNum(u8 x,u8 y,u32 num,u8 len,u8 size2)
{         	
	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 + (size2 / 2) * t,y,' ',size2);
				continue;
			}else enshow = 1; 
		 	 
		}
	 	OLED_ShowChar(x + (size2 / 2) * t,y,temp + '0',size2); 
	}
} 

OLED_ShowNum函数:        作用是在OLED屏幕上显示一个数字


       x:横坐标         y:纵坐标        len :数字的位数          num:数值(0~4294967295) 比如我写123,就是3位数,对应len为3。        size2:还是字的大小,选16。
       

        使用for循环,循环len次,其中t表示数字的位数。

        计算出每一位数字的值,具体方法是将num除以10的len-t-1次方后取余数。

        判断是否需要填充空格。当第一次出现非零数字后,后面的数字不再填充空格。

        使用OLED_ShowChar函数,分别在不同位置显示每一位数字。     

void OLED_ShowString(u8 x,u8 y,u8 *chr,u8 Char_Size)
{
	unsigned char j=0;
	while (chr[j] != '\0')
	{		OLED_ShowChar(x,y,chr[j],Char_Size);
			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;
    }					
}

 OLED_ShowString函数:        作用是在指定位置显示一个字符串。

 

x:横坐标        y:纵坐标         *chr:(很明显)是常量字符串的首地址,所以这个参数写要发送的字符串        Char_Size:字的大小,选16就好(因为是16*16),也可以改。

 

        使用while循环,将每个字符都显示出来。

        使用OLED_ShowChar函数,在不同位置显示每个字符。

        每个字符之间的间隔为8个列,如果当前行显示不下,则换行,调整显示位置。

其中,chr表示要显示的字符串,Char_Size表示字符的字体大小。

 

OLED_ShowChinese函数:        作用是在指定位置显示一个汉字

 

x:横坐标        y:纵坐标        no:第几个字 (0~7)

 

        使用OLED_Set_Pos函数设置显示位置。

        使用for循环,循环16次,分别向OLED发送汉字数据。

        其中,no表示要显示的汉字在HZK数组中的索引值,HZK数组存储了所有汉字的字库数据。

        每个汉字占用2行16列,如果当前行显示不下,则换行,调整显示位置。

void OLED_Init(void)
{ 	
 
	OLED_WR_Byte(0xAE,OLED_CMD);//--display off
	OLED_WR_Byte(0x00,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  
	OLED_WR_Byte(0xB0,OLED_CMD);//--set page address
	OLED_WR_Byte(0x81,OLED_CMD); // contract control
	OLED_WR_Byte(0xFF,OLED_CMD);//--128   
	OLED_WR_Byte(0xA1,OLED_CMD);//set segment remap 
	OLED_WR_Byte(0xA6,OLED_CMD);//--normal / reverse
	OLED_WR_Byte(0xA8,OLED_CMD);//--set multiplex ratio(1 to 64)
	OLED_WR_Byte(0x3F,OLED_CMD);//--1/32 duty
	OLED_WR_Byte(0xC8,OLED_CMD);//Com scan direction
	OLED_WR_Byte(0xD3,OLED_CMD);//-set display offset
	OLED_WR_Byte(0x00,OLED_CMD);//
	
	OLED_WR_Byte(0xD5,OLED_CMD);//set osc division
	OLED_WR_Byte(0x80,OLED_CMD);//
	
	OLED_WR_Byte(0xD8,OLED_CMD);//set area color mode off
	OLED_WR_Byte(0x05,OLED_CMD);//
	
	OLED_WR_Byte(0xD9,OLED_CMD);//Set Pre-Charge Period
	OLED_WR_Byte(0xF1,OLED_CMD);//
	
	OLED_WR_Byte(0xDA,OLED_CMD);//set com pin configuartion
	OLED_WR_Byte(0x12,OLED_CMD);//
	
	OLED_WR_Byte(0xDB,OLED_CMD);//set Vcomh
	OLED_WR_Byte(0x30,OLED_CMD);//
	
	OLED_WR_Byte(0x8D,OLED_CMD);//set charge pump enable
	OLED_WR_Byte(0x14,OLED_CMD);//
	
	OLED_WR_Byte(0xAF,OLED_CMD);//--turn on oled panel
}  

这个初始化其实也没什么好说的,调用任何一个模块,都要对其进行初始化配置。


OLED_WR_Byte函数:作用是向OLED发送一系列命令,设置OLED的各种参数,包括显示方向、亮度、滚动等。

 

- 0xAE:关闭OLED显示。

- 0x00和0x10:设置列地址范围。

- 0x40:设置起始行地址。

- 0xB0:设置页地址范围。

- 0x81和0xFF:设置对比度。

- 0xA1:设置左右反转。

- 0xA6:设置正常/反转显示。

- 0xA8和0x3F:设置多路复用比率和扫描方式。

- 0xC8:设置COM扫描方向。

- 0xD3和0x00:设置显示偏移。

- 0xD5和0x80:设置时钟分频。

- 0xD8和0x05:设置区域颜色模式。

- 0xD9和0xF1:设置预充电周期。

- 0xDA和0x12:设置COM引脚配置。

- 0xDB和0x30:设置VCOMH。

- 0x8D和0x14:设置内部充电泵。

- 0xAF:打开OLED显示。

 

其中,OLED_CMD表示发送的是命令,OLED_DATA表示发送的是显示数据。

现象

显示汉字

70c3e7e3db0a4c6b80377293a45f49f9.png

 

我的汉字上下间距至少是2,不然上下的字会覆盖,这个是可以试出来的。

请看现象:0bc0f3f9a9e14607bbf5a544640801db.jpeg

显示字符串

6b9c764304004415ac2ce15c95cbd48e.png

请看现象:

 6045848791b14078a11c4fc3dc9a9e09.jpeg

        其他就不做示范了。

总结

        别忘了,在主函数先初始化一次,来初步配置OLED屏幕,为了确保不受干扰,每一次界面切换内容,都要clear一遍(调用OLED_Clear)。

        博主也是菜狗,当然不会写这个OLED代码,初学者要知道如何显示一个汉字、字符串、字符以及数字就好,了解他们的参数,会调用就可以了,毕竟博主以后也不会从事底层驱动开发的哈哈。就这样,希望你们有所收获!!!!

 

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值