STM32单片机入门:IIC驱动OLED屏幕实践

在我上篇文章(STM32-软件模拟IIC通信)讲解了软件模拟IIC通信。这篇文章详将细讲解利用软件模拟IIC来控制0.96寸的OLED屏幕(如下图),使其显示字符串。本文将不再对IIC通信原理做详细讲解,所以对IIC通信原理不熟悉的话可以参考我上篇文章(点击上面的链接直接跳转)。

上面这款屏幕是单片机学习中最常用的产品之一,在很多单片机作品中都能见到,首先简单介绍这款OLED屏幕:

分辨率:64*128(最多可显示8行ASCII字符或者4行汉字)

屏幕尺寸:0.96寸

通信方式:IIC

端口:GND、VCC、SCL、SDA(地、3V-5V供电、IIC时钟线、IIC数据线)

驱动芯片:SSD1306


要使用一款屏幕,先要从其驱动芯片下手。在网上找到其使用手册,一般就知道如何使用。

这里我就简单介绍其工作过程。在SSD1306内部有一片显示SRAM缓存区,每个存储单元的状态正好对应屏幕上像素点的状态(0或者1)。控制芯片按照该SRAM的数据扫描OLED屏幕(动态扫描,其原理可参考文章动态扫描显示原理)。所以单片机只需要通过IIC将数据写进SRAM中,刷新屏幕的事全部交给驱动芯片就可以。与单片机内部的SRAM(静态随机存储器)一样,一旦断电,其数据就会丢失,所以该OLED屏幕断电之后再上电,显示SRAM就是空白的。

———————————————————

我们先来了解其屏幕分区。 

 整块屏幕分为8个大区,称之为页(从上到下依次为Page0-到Page7),每一页又分为128列,每一列又分为8个小块。每个小块对应一个像素点,有0与1两种状态(代表像素点的灭与亮),可以用1bit数据来表示。每一列是8个小块,也就是8bit,即1Byte。在IIC数据传输时,正好也是1Byte为一个数据帧,所以发送一次数据正好刷新了SRAM中8个像素点的状态。

在设置OLED的初始坐标时都是通过某页某列进行定位的,定位用的命令下文介绍。

常见的OLED屏幕都是如上图,只有4个引脚,但我这里使用的是7脚的OLED屏,它不仅支持IIC通信,还支持4线SPI、3线SPI。如图

 该OLED出厂默认的是4线SPI,所以如果想用IIC通信,还得改线路(如上图丝印所示),即将R3的4.7K电阻拆下来装在R1的位置上,并将R8短接或者另外找一个0603的4.7K电阻焊上。

其引脚定义如下:

GND:接电源地

VCC:接电源VCC(3V-5V)

D0:IIC-SCL / SPI-CLK。这里接单片机的IIC_SCL

D1:IIC-SDA / MOSI。这里接单片机的IIC-SDA

RES:复位脚。可用一个GPIO来控制

DC:屏幕IIC地址选择线(低电平:地址为0x78;高电平:0x7A),一般直接接地

CS:SPI片选 SPI-CS,这里直接接地

如果是4线的OLED屏,接法则简单多了,与单片机对应连接即可,省去了后面三个引脚,DC在其内部接了地,地址固定为0x78。

其他不多说,一边看程序,一边讲解。


1.头文件及函数声明

这里我把PB10作为IIC的模拟SCL,PB11作为IIC的模拟SDA,PC13作为OLED的复位脚。并对他们的置复位进行声明,方便使用。(之所以这么声明,是因为STM32没有sbit,不能直接将变量映射到引脚)

#include<stm32f10x.h>
#include<stm32f10x_gpio.h>
#include<stm32f10x_rcc.h>

#define Read_IIC_SDA GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_11)	//Read_IIC_SDA读取PB11的值

#define OLED_RES_H() GPIO_SetBits(GPIOC, GPIO_Pin_13)	//OLED RES脚置高电平
#define OLED_RES_L() GPIO_ResetBits(GPIOC, GPIO_Pin_13)	//OLED RES置低电平

#define IIC_SCL_H() GPIO_SetBits(GPIOB,GPIO_Pin_10)			//    IIC_SCL置高
#define IIC_SCL_L() GPIO_ResetBits(GPIOB,GPIO_Pin_10)		//	 IIC_SCL置低

#define IIC_SDA_H() GPIO_SetBits(GPIOB,GPIO_Pin_11)			//    IIC_SDA置高
#define IIC_SDA_L() GPIO_ResetBits(GPIOB,GPIO_Pin_11)		//    IIC_SDA置低

2.ASCII字库与OLED缓存

ASCII共有127个字符,前32个为特殊功能(回车、换行、Tab等)。我们把字库放在一个127×6的数组里,里面的元素是无符号8位二进制数(u8或者unsigned char),数组的每一行正好构成一个ASCII字符(能完整显示所有ASCII字符所需的分辨率最小为8×6,在文章动态扫描显示原理中有详细介绍)。这里我简化了特殊功能,全部显示为空白,或者说为空格。

这里还需要声明一个数组作为OLED内部SRAM的缓存。提高刷新的效果。

unsigned char Dictionary_ASCII8x6[127][6]=
{
	0,0,0,0,0,0,//0	
	0,0,0,0,0,0,//1	
	0,0,0,0,0,0,//2	
	0,0,0,0,0,0,//3	
	0,0,0,0,0,0,//4	
	0,0,0,0,0,0,//5	
	0,0,0,0,0,0,//6	
	0,0,0,0,0,0,//7	
	0,0,0,0,0,0,//8	
	0,0,0,0,0,0,//9	
	0,0,0,0,0,0,//10	
	0,0,0,0,0,0,//11	
	0,0,0,0,0,0,//12	
	0,0,0,0,0,0,//13	
	0,0,0,0,0,0,//14	
	0,0,0,0,0,0,//15	
	0,0,0,0,0,0,//16	
	0,0,0,0,0,0,//17	
	0,0,0,0,0,0,//18	
	0,0,0,0,0,0,//19	
	0,0,0,0,0,0,//20	
	0,0,0,0,0,0,//21	
	0,0,0,0,0,0,//22	
	0,0,0,0,0,0,//23	
	0,0,0,0,0,0,//24	
	0,0,0,0,0,0,//25	
	0,0,0,0,0,0,//26	
	0,0,0,0,0,0,//27	
	0,0,0,0,0,0,//28	
    0,0,0,0,0,0,//29	
	0,0,0,0,0,0,//30	
	0,0,0,0,0,0,//31                    0-31为特殊功能,如换行、回车
	
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00,//32	空格
	0x00, 0x00, 0x00, 0x2f, 0x00, 0x00,//33 !	
	0x00, 0x00, 0x07, 0x00, 0x07, 0x00,//34 "	
	0x00, 0x14, 0x7f, 0x14, 0x7f, 0x14,//35 #	
	0x00, 0x24, 0x2a, 0x7f, 0x2a, 0x12,//36 $	
	0x00, 0x23, 0x13, 0x08, 0x64, 0x62,//37 %	
	0x00, 0x36, 0x49, 0x55, 0x22, 0x50,//38 &	
	0x00, 0x00, 0x05, 0x03, 0x00, 0x00,//39 '	
	0x00, 0x00, 0x1c, 0x22, 0x41, 0x00,//40 (	
	0x00, 0x00, 0x41, 0x22, 0x1c, 0x00,//41 )	
	0x00, 0x14, 0x08, 0x3E, 0x08, 0x14,//42 *	
	0x00, 0x08, 0x08, 0x3E, 0x08, 0x08,//43 +	
	0x00, 0x00, 0x00, 0xA0, 0x60, 0x00,//44 ,	
	0x00, 0x08, 0x08, 0x08, 0x08, 0x08,//45 -	
	0x00, 0x00, 0x60, 0x60, 0x00, 0x00,//46 .	
	0x00, 0x20, 0x10, 0x08, 0x04, 0x02,//47 /	
	0x00, 0x3E, 0x51, 0x49, 0x45, 0x3E,//48 0
	0x00, 0x00, 0x42, 0x7F, 0x40, 0x00,//49 1	
	0x00, 0x42, 0x61, 0x51, 0x49, 0x46,//50 2	
	0x00, 0x21, 0x41, 0x45, 0x4B, 0x31,//51 3	
	0x00, 0x18, 0x14, 0x12, 0x7F, 0x10,//52 4	
	0x00, 0x27, 0x45, 0x45, 0x45, 0x39,//53 5	
	0x00, 0x3C, 0x4A, 0x49, 0x49, 0x30,//54 6	
	0x00, 0x01, 0x71, 0x09, 0x05, 0x03,//55 7	
	0x00, 0x36, 0x49, 0x49, 0x49, 0x36,//56 8	
	0x00, 0x06, 0x49, 0x49, 0x29, 0x1E,//57 9	
	0x00, 0x00, 0x36, 0x36, 0x00, 0x00,//58 :	
	0x00, 0x00, 0x56, 0x36, 0x00, 0x00,//59 ;	
	0x00, 0x08, 0x14, 0x22, 0x41, 0x00,//60 <	
	0x00, 0x14, 0x14, 0x14, 0x14, 0x14,//61 =	
	0x00, 0x00, 0x41, 0x22, 0x14, 0x08,//62 >	
	0x00, 0x02, 0x01, 0x51, 0x09, 0x06,//63 ?	
	0x00, 0x32, 0x49, 0x59, 0x51, 0x3E,//64 @	
	0x00, 0x7C, 0x12, 0x11, 0x12, 0x7C,//65 A	
	0x00, 0x7F, 0x49, 0x49, 0x49, 0x36,//66 B	
	0x00, 0x3E, 0x41, 0x41, 0x41, 0x22,//67 C	
	0x00, 0x7F, 0x41, 0x41, 0x22, 0x1C,//68 D	
	0x00, 0x7F, 0x49, 0x49, 0x49, 0x41,//69 E	
	0x00, 0x7F, 0x09, 0x09, 0x09, 0x01,//70 F	
	0x00, 0x3E, 0x41, 0x49, 0x49, 0x7A,//71 G	
	0x00, 0x7F, 0x08, 0x08, 0x08, 0x7F,//72 H	
	0x00, 0x00, 0x41, 0x7F, 0x41, 0x00,//73 I	
	0x00, 0x20, 0x40, 0x41, 0x3F, 0x01,//74 J	
	0x00, 0x7F, 0x08, 0x14, 0x22, 0x41,//75 K	
	0x00, 0x7F, 0x40, 0x40, 0x40, 0x40,//76 L	
	0x00, 0x7F, 0x02, 0x0C, 0x02, 0x7F,//77 M	
	0x00, 0x7F, 0x04, 0x08, 0x10, 0x7F,//78 N	
	0x00, 0x3E, 0x41, 0x41, 0x41, 0x3E,//79 O	
	0x00, 0x7F, 0x09, 0x09, 0x09, 0x06,//80 P	
	0x00, 0x3E, 0x41, 0x51, 0x21, 0x5E,//81 Q	
	0x00, 0x7F, 0x09, 0x19, 0x29, 0x46,//82 R	
	0x00, 0x46, 0x49, 0x49, 0x49, 0x31,//83 S	
	0x00, 0x01, 0x01, 0x7F, 0x01, 0x01,//84 T	
	0x00, 0x3F, 0x40, 0x40, 0x40, 0x3F,//85 U	
	0x00, 0x1F, 0x20, 0x40, 0x20, 0x1F,//86 V	
	0x00, 0x3F, 0x40, 0x38, 0x40, 0x3F,//87 W	
	0x00, 0x63, 0x14, 0x08, 0x14, 0x63,//88 X	
	0x00, 0x07, 0x08, 0x70, 0x08, 0x07,//89 Y	
	0x00, 0x61, 0x51, 0x49, 0x45, 0x43,//90 Z	
	0x00, 0x00, 0x7F, 0x41, 0x41, 0x00,//91 [	
	0x00, 0x55, 0x2A, 0x55, 0x2A, 0x55,/*92 \	*/
	0x00, 0x00, 0x41, 0x41, 0x7F, 0x00,//93 ]	
	0x00, 0x04, 0x02, 0x01, 0x02, 0x04,//94 ^	
	0x00, 0x40, 0x40, 0x40, 0x40, 0x40,//95 _	
	0x00, 0x00, 0x01, 0x02, 0x04, 0x00,//96 '	
	0x00, 0x20, 0x54, 0x54, 0x54, 0x78,//97 a	
	0x00, 0x7F, 0x48, 0x44, 0x44, 0x38,//98 b	
	0x00, 0x38, 0x44, 0x44, 0x44, 0x20,//99 c	
	0x00, 0x38, 0x44, 0x44, 0x48, 0x7F,//100 d	
	0x00, 0x38, 0x54, 0x54, 0x54, 0x18,//101 e	
	0x00, 0x08, 0x7E, 0x09, 0x01, 0x02,//102 f	
	0x00, 0x18, 0xA4, 0xA4, 0xA4, 0x7C,//103 g	
	0x00, 0x7F, 0x08, 0x04, 0x04, 0x78,//104 h	
	0x00, 0x00, 0x44, 0x7D, 0x40, 0x00,//105 i	
	0x00, 0x40, 0x80, 0x84, 0x7D, 0x00,//106 j	
	0x00, 0x7F, 0x10, 0x28, 0x44, 0x00,//107 k	
	0x00, 0x00, 0x41, 0x7F, 0x40, 0x00,//108 l	
	0x00, 0x7C, 0x04, 0x18, 0x04, 0x78,//109 m	
	0x00, 0x7C, 0x08, 0x04, 0x04, 0x78,//110 n	
	0x00, 0x38, 0x44, 0x44, 0x44, 0x38,//111 o	
	0x00, 0xFC, 0x24, 0x24, 0x24, 0x18,//112 p	
	0x00, 0x18, 0x24, 0x24, 0x18, 0xFC,//113 q	
	0x00, 0x7C, 0x08, 0x04, 0x04, 0x08,//114 r	
	0x00, 0x48, 0x54, 0x54, 0x54, 0x20,//115 s	
	0x00, 0x04, 0x3F, 0x44, 0x40, 0x20,//116 t	
	0x00, 0x3C, 0x40, 0x40, 0x20, 0x7C,//117 u	
	0x00, 0x1C, 0x20, 0x40, 0x20, 0x1C,//118 v	
	0x00, 0x3C, 0x40, 0x30, 0x40, 0x3C,//119 w	
	0x00, 0x44, 0x28, 0x10, 0x28, 0x44,//120 x	
	0x00, 0x1C, 0xA0, 0xA0, 0xA0, 0x7C,//121 y	
	0x00, 0x44, 0x64, 0x54, 0x4C, 0x44,//122 z	
	0x00, 0x08, 0x77, 0x41, 0x00, 0x00,//123	{
	0x00, 0x08,	0x7F, 0x00,	0x00, 0x00,//124	|
	0x00, 0x41,	0x77, 0x08,	0x00, 0x00,//125	}
	0x00, 0x08,	0x04, 0x08,	0x10, 0x08 //126	~
};
u8 SDRAM[8][128]={0};		//SRAM缓存

3.GPIO初始化

这里我定义一个函数,用来给IIC的软件模拟引脚SCL(PB10)、SDA(PB11)、res复位脚初始化。模式全部为推挽输出。

void IIC_UserInit(void)
{
   GPIO_InitTypeDef GPIO_InitStructure;
   RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB| RCC_APB2Periph_GPIOC, ENABLE);
   GPIO_InitStructure.GPIO_Pin=GPIO_Pin_10 | GPIO_Pin_11; //10--SCL   11--SDA
   GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
   GPIO_InitStructure.GPIO_Mode=GPIO_Mode_Out_PP;
   GPIO_Init(GPIOB, &GPIO_InitStructure);
	 
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13; 			//作为屏幕的RSE脚,4线OLED屏幕可省略
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_10MHz;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
	GPIO_Init(GPIOC,&GPIO_InitStructure);	
}

4.延时函数

为了方便理解,这里就不使用定时器了,直接Delay

void Delay_us(int Time)    //us延时函数
{
	int i=0;
	while(Time--)
	{
		i=8;
		while(i--);
	}
	return;
}

5.IIC-SDA输入输出模式切换函数

IIC通讯时,单片机发送数据后需要等待应答,这时模拟SDA要切换到输入模式(上拉输入),收到应答后又要切回输出模式(推挽输出)。具体过程可参靠文章软件模拟IIC通信,里面有详细介绍。

void SDA_OUT(void)    //输出模式
{
    GPIO_InitTypeDef GPIO_InitStructure;
    GPIO_InitStructure.GPIO_Pin= GPIO_Pin_11;
    GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
    GPIO_InitStructure.GPIO_Mode=GPIO_Mode_Out_PP;        //推挽输出
    GPIO_Init(GPIOB, &GPIO_InitStructure);
}


void SDA_IN(void)    //输入模式
{
    GPIO_InitTypeDef GPIO_InitStructure;
    GPIO_InitStructure.GPIO_Pin= GPIO_Pin_11;
    GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
    GPIO_InitStructure.GPIO_Mode=GPIO_Mode_IPU;			//上拉输入模式
	GPIO_Init(GPIOB, &GPIO_InitStructure);
}

6.IIC软件模拟信号

软件模拟起始信号、停止信号、应答信号、数据发送


void IIC_Start(void)    //起始信号
{
    SDA_OUT();		//SDA切换输出模式
    IIC_SDA_H();		//SDA置高
    IIC_SCL_H();		//SCL置高
    Delay_us(1);
    IIC_SDA_L();		//SDA置低
    Delay_us(1);
    IIC_SCL_L();		//SCL置低
    Delay_us(1);
}

void IIC_Stop(void)    //停止信号
{
    IIC_SCL_H();		
    IIC_SDA_L();		
    Delay_us(1);
    IIC_SDA_H();		
    Delay_us(1);
}


u8 IIC_Wait_Ask(void)    //应答信号
{
    int count=0;
    IIC_SDA_H();
		SDA_IN();
    IIC_SCL_H();
    Delay_us(1);
    while(Read_IIC_SDA)
    {
        count++;
        if(count>250)
        {
            IIC_Stop();				//无应答则发停止信号,停止通信
            return 1;
        }   
    }
    IIC_SCL_L();
    Delay_us(1);
    return 0;
}


void IIC_WriteByte(u8 data)			//发送1Byte数据
{
    u8 i;
    SDA_OUT();			
    for(i=0;i<8;i++)		//循环传输,一次传输1bit数据,循环8次		
    {
        IIC_SCL_L();				
        if(data & 0x80)        //如果最高位为1 
            IIC_SDA_H();		//SDA置高
        else
            IIC_SDA_L();	    //否则置低
        IIC_SCL_H();				
        Delay_us(1);				
        IIC_SCL_L();				
        data<<=1;				//Deta左移一位		
    }
}

7.写命令与写数据

因为OLED屏幕内部既有命令(OLED的参数设置)也有数据(显示缓存),所以需要区分开来。

在SSD1306使用手册中有说明:

如果芯片收到一个0x00的字节信息,说明下一个收到的数据是命令;

如果芯片收到一个0x40的字节信息,说明下一个收到的数据是显示SRAM数据。

void WriteCmd(u8 command)    //写命令
{
    IIC_Start();
    IIC_WriteByte(0x78);//OLED地址
    IIC_Wait_Ask();
    IIC_WriteByte(0x00);写了0x00后代表后面发送的信息都是参数信息
    IIC_Wait_Ask();
    IIC_WriteByte(command);
    IIC_Wait_Ask();
    IIC_Stop();
}


void WriteDat(u8 data)    //写数据
{
    IIC_Start();
    IIC_WriteByte(0x78);//OLED地址
    IIC_Wait_Ask();
    IIC_WriteByte(0x40);//写了0x40后代表后面发送的信息都是数据信息
    IIC_Wait_Ask();
    IIC_WriteByte(data);
    IIC_Wait_Ask();
    IIC_Stop();
}

8.OLED初始化

设置OLED的基本参数,具体参数设置查阅SSD1306使用手册,这里只设置用得到的

void OLED_Init(void)
{
	OLED_RES_L();
	Delay_ms(100); //复位OLED 
	OLED_RES_H();
	
    WriteCmd(0x20); //Set Memory Addressing Mode  设置内存地址模式
    WriteCmd(0x10); //00,Horizontal Addressing Mode;01,Vertical Addressing Mode;10,Page Addressing Mode (RESET);11,Invalid
	
	WriteCmd(0xc8); //Set COM Output Scan 设置行扫描顺序为从上到下,c0为从下到上
    WriteCmd(0xa1); //--set segment re-map 0 to 设置列扫描顺序为从左到右,a0为从右到左

	WriteCmd(0xb0); //Set Page Start Address for Page Addressing Mode,0-7    设置页地址0-7
	WriteCmd(0x00); //---set low column address				列低位00-0F	
    WriteCmd(0x10); //---set high column address				列高位10-1F
    WriteCmd(0x40); //--set start line address				起始行
    
	WriteCmd(0x81); //--set contrast control register    对比度调节
    WriteCmd(0xff); //00-ff 由暗到亮
	  
    WriteCmd(0xa6); //--set normal display
    WriteCmd(0xa8); //--set multiplex ratio(1 to 64)
    WriteCmd(0x3F); //
   
	WriteCmd(0xa4); //0xa4,Output follows RAM content;0xa5,Output ignores RAM content
		
    WriteCmd(0xd3); //-set display offset显示便宜
    WriteCmd(0x00); //-not offset  00为无偏移
		
    WriteCmd(0xd5); //--set display clock divide ratio/oscillator frequency时钟分频率、振荡器频率
    WriteCmd(0xf0); //--set divide ratio	f0刷新率最大
   
	WriteCmd(0xd9); //--set pre-charge period
    WriteCmd(0x22); //
   
	WriteCmd(0xda); //--set com pins hardware configuration
    WriteCmd(0x12);
    
	WriteCmd(0xdb); //--set vcomh
    WriteCmd(0x20); //0x20,0.77xVcc
    
	WriteCmd(0x8d); //--set DC-DC enable		DC-DC使能
    WriteCmd(0x14); //			
	
	OLED_FullScreen_Refresh();					//
    WriteCmd(0xaf); //--turn on oled panel,打开显示		,0xAE 关闭显示
}

一般在刷新OLED显示SRAM之前都要对OLED初始化,说白了,就是设置OLED的参数,当然使用中也是可以初始化的。这是因为上次设置的OLED参数断电之后就会丢失。

函数中的复位OLED是针对7针的OLED屏幕,如果是4针的,上电时内部会自动复位,也就不需要自己再复位了,可省略这个步骤。

参数设置方法:SSD1306每个设置都对应一个命令,发送特定的命令之后,下一个或者多个命令则表示其要设置的值。如:内存模式地址设置,该设置对应命令0x20。单片机想要设置该参数的话,首先发送命令0x20,表示我想设置0x20的参数,接着再发送一个命令,这个命令就对应着要设置的值。上面的程序,意思就是把0x20对应的内存地址模式设置成0x10。

内存地址模式设置(0x20):有三种模式可选择

00,Horizontal Addressing Mode(水平模式),按照从做到右,从上到下的顺序刷新屏幕(如下),扫描完一行,又会回到下一行的开头继续扫描,这种模式下,没有页的概念

01,Vertical Addressing Mode(垂直模式)按照从上到下,从左到右的顺序刷新屏幕,刷新完一列,又回到下一列的开头继续扫描。图中没有画出完整的列。

10,Page Addressing Mode (RESET)(页模式),从第一页开始,从上到下,从左到右,扫完一页,回到下一页的开头继续扫描。这是默认的模式,这里我采用的也是该模式;

 采用何种模式,按对应的方式取字模。否则就会乱码。

11,Invalid(无效设置)

———————————————————————-

行扫描顺序(0xc_):设置屏幕扫描是从上到下(0xc8)还是从下到上(0xc0),可实现画面上下翻转

列扫描顺序(0xa_):设置屏幕扫描是从左到右(0xa1)还是从右到左(0xa0),可实现画面左右翻转(镜像效果)

———————————————————————

-设置页地址(0xb0); b0-b7    分别对应page1-page7,页模式下有效
设置列地址低位(0x00); 列低位00-0F分别代表列地址的低四位为0-F    
设置列地址高位(0x10); 列高位10-1F分别代表列地址的高四位为0-F    

这两个参数是配合使用的,例如,0x01,0x1A,取低位1、高位A,组合就是0xA1,对应十进制的161,也就是定位到第161列。

设置起始行(0x40);  

上面几个地址参数用于设置刷新屏幕的起点坐标。设置之后,后面发送的数据就会从该地址开始刷新显示SRAM。

———————————————————————-

对比度调节(0x81),这个参数用来设置屏幕对比度,也可所以说是调亮度,从00-FF共128个等级可以设置,值越大,对比度越高

其它参数暂不详细介绍,可查阅使用手册。在函数末尾,调用了一个全屏刷新函数,该函数在下文有申明。作用就是每次启动OLED都清一次屏,以免上次的画面残留在屏幕上。

9.全屏刷新函数与局部刷新函数

全屏刷新函数用于刷新整个屏幕,局部刷新则只刷新某部分区域,因为刷新的像素点较全局少,所以刷新一次的时间更短。

void OLED_FullScreen_Refresh(void)		//全屏刷新
	{
		unsigned char Page,Column;
		for(Page=0;Page<8;Page++)		//按页循环刷新,8次循环,刷新8页
		{
			WriteCmd(0xb0+Page);       //page0-page7定位到第Page页
			WriteCmd(0x00);    			 //定位到第一列
			WriteCmd(0x10);     //
			
			IIC_Start();
			IIC_WriteByte(0x78);//OLED地址
			IIC_Wait_Ask();
			IIC_WriteByte(0x40);//写数据
			IIC_Wait_Ask();
			
			for(Column=0;Column<128;Column++)	//按列循环,128列,128次循环
			{
				IIC_WriteByte(SDRAM[Page][Column]);//将缓存数组的数据全部发送过去
				IIC_Wait_Ask();
			}
			IIC_Stop();    //发送完则发出停止信号
		}		
	}	

	void OLED_PartScreen_Refresh(u8 Page,u8 Column,u8 Length)		//局部刷新函数(输入页,列,宽度)
	{
		unsigned char i;
		WriteCmd(0xb0+Page);       //page0-page7,定位到第page页
		WriteCmd(Column%16);    			 //low column start address	  定位到列
		WriteCmd((Column/16)+16);     //high column start address 
		
		IIC_Start();
		IIC_WriteByte(0x78);
		IIC_Wait_Ask();
		IIC_WriteByte(0x40);
		IIC_Wait_Ask();
		
		for(i=Column;i<(Column+Length);i++)	//按列刷新
		{
				IIC_WriteByte(SDRAM[Page][i]);    //将缓存数组的数据部分发送过去
				IIC_Wait_Ask();
		}
		IIC_Stop();
	}	
	

10.自封装Printf与Printf_part函数

Printf函数用于在某一页输出字符,会改变整页显示的内容。Printf_part用于在某一部分输出字符,而不会影响该页其他部分的内容。

注意这个函数只能显示ASCII字符,想要显示中文汉字,还需要加入中文字库,且中文需要占用两页,即8行。

void Printf(u8 Page,char Word_ASCII[])		//在某一页输出字符串
{
	u8 w,x;
	for(w=0;w<21;w++)
	{
		for(x=0;x<6;x++)
		{
			SDRAM[Page-1][6*w+x]=Dictionary_ASCII8x6[Word_ASCII[w]][x];		//将字库写入到缓存数组
		}
	}
	OLED_PartScreen_Refresh(Page-1,0,128);	//刷新整页

}

void Printf_part(u8 Page,u8 Column,u8 Words,char Word_ASCII[])		//在某一区域输出字符串
{
	u8 w,x;
	for(w=0;w<Words;w++)
	{
		for(x=0;x<6;x++)
		{
			SDRAM[Page-1][Column+w*6+x]=Dictionary_ASCII8x6[Word_ASCII[w]][x];
		}
	}
	OLED_PartScreen_Refresh(Page-1,Column,Words*6);				//刷新该区域的内容
}

11.动态变量输出函数,用于显示变化的数字

void Printf_Dynamic(u8 Page,u8 Column,u8 Words,u16 Variable)		//显示变化的数字
{
	u8 w,x,Date[5];				//Date5位 0-65535 Date[0, 1,  2,  3,  4]														
	Date[4]=Variable/10000;						//65535/10000=6				计算各位的值
	Date[3]=Variable/1000-Date[4]*10;				//	65535/1000=65,65-6*10=5
	Date[2]=Variable/100-Date[3]*10-Date[4]*100;			//	655-50-600=5
	Date[1]=Variable/10-Date[2]*10-Date[3]*100-Date[4]*1000;		//6553-50-500-6000=3
	Date[0]=Variable-Date[1]*10-Date[2]*100-Date[3]*1000-Date[4]*10000;		//65535-30-500-5000-60000=5
	
	for(w=0;w<Words;w++)		
	{
		for(x=0;x<6;x++)
		{
			SDRAM[Page-1][Column+w*6+x]=Dictionary_ASCII8x6[Date[Words-w-1]+48][x];
		}	
	}
	OLED_PartScreen_Refresh(Page-1,Column,Words*6);
}

12.屏幕整体上移函数

当屏幕不够显示内容时,可将所有内容上移,再末行继续使用Printf。按此思路,也可自定义整体下移,这里暂不展示。

void MoveUP(u8 Line)				//整体上移Line行
{
	u8 x,y;
	for (y=0;y<(8-Line);y++)				
	{
		for(x=0;x<128;x++)
		{
			SDRAM[y][x]=SDRAM[y+Line][x];
		}
	}
	for(y=(8-Line);y<8;y++)	
	{
		for(x=0;x<128;x++)
			{
				SDRAM[y][x]=0;
			}
	OLED_FullScreen_Refresh();				//全屏刷新
}

13.主函数

调用子函数,初始化GPIO,初始化OLED。

输出Hello world…(字符串要用“  ”包括)

void main(void)
{
    IIC_UserInit();    //GPIO初始化
    OLED_Init();        //OLED初始化
    Printf(1,"Hello world...");    //输出Hello world...

}

最终效果如下:

还可以加入各种自定义图标以及动画,对比输出ASCII码,只是把字库变成图形库。

为了方便理解,这里我把所有函数放在一个.c文件中,实际项目中,一般将除主函数以外的函数按功能分别放在不同的.c文件中


觉得本文有用,可以点个赞哦~

物联沃分享整理
物联沃-IOTWORD物联网 » STM32单片机入门:IIC驱动OLED屏幕实践

发表评论