在我上篇文章(
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文件中
觉得本文有用,可以点个赞哦~