目录
- 1 OLED模块介绍
- 1.1 模块
- 1.2 SSD1306简介
- 1.2 SSD1306引脚
- 1.3 SSD1306接口配置
- 2 驱动(oled_ctrl.v)
- 3 一个简单的应用
- 附件
很久以前玩的OLED,现在整理一下。
1 OLED模块介绍
1.1 模块
OLED:organic/polymer light emitting diode 高分子有机电激发光二极管
OLED模块原理图:
OLED模块结构图:
市面上统一尺寸的配置和设计大差不差的,OLED生产商大部分都是中景园电子,官网可以下载OLED 显示屏裸屏的资料。
以驱动芯片为SSD1306的屏为例:
OLED 显示屏裸屏外观:
驱动芯片SSD1306尺寸很小,6.76*0.86mm。仔细看在液晶屏下面一点点可以看到一个长条就是了。
1.2 SSD1306简介
SSD1306是一款带控制器的单片CMOS OLED/PLED驱动,用于OLED点阵图形显示系统。它由128个segment和64个common组成。该集成电路是为普通阴极型OLED面板设计的。
SSD1306内置了对比度控制、显示RAM和振荡器,减少了外部组件的数量和功耗。它有256级亮度控制。数据/命令从通用MCU通过硬件可选的6800/8000串行兼容并行接口,I2C接口或串行外设接口发送。它适用于许多小型便携式应用,如手机子显示器、MP3播放器和计算器等。
1.2 SSD1306引脚
SSD1306单片机接口由8个数据引脚和5个控制引脚组成。不同接口模式下的引脚分配如下表所示。
1.3 SSD1306接口配置
不同的MCU模式可以通过BS[2:0]引脚上的硬件选择来设置。根据原理图可知这款的BS[2:0]=3’b010。设置为I2C驱动。
I2C写数据与命令的时序:
contorl byte: 8’h40(数据),8’h00(指令)
关于I2C时序,这里注意几个关键参数就行,其他的都是标准接口:
- I2C时钟周期最小为2.5us,即400KHz。
- 刷新率:
取I2C时钟频率=350KHz。I2C驱动模块系统时钟为4倍I2C时钟频率=1.4MHz。
从触发一次写到写完成大概需要130个系统时钟。
完成一次刷新需要1048次I2C写,其中1024个数据以及24个指令。
所以刷新率为:
关于配置空间,注意:
- 显存地址与数据排列
将整个显示像素点128*64分为8行,127列的8bit数据,数据的低位对应低坐标点。 - 设置页寻址模式的下列起始地址(00h~0Fh)
- 设置页面寻址模式的高列起始地址(10h~1Fh)
- 设置页面寻址方式的页面起始地址(B0h~B7h)
2 驱动(oled_ctrl.v)
初始化主要指令:
case (R_init_cmd_cnt)
7'd0 :O_i2c_data <= {8'h00,8'hAE};//--turn off oled panel
7'd1 :O_i2c_data <= {8'h00,8'h00};//---set low column address
7'd2 :O_i2c_data <= {8'h00,8'h10};//---set high column address
7'd3 :O_i2c_data <= {8'h00,8'h40};//--set start line address Set Mapping RAM Display Start Line (0x00~0x3F)
7'd4 :O_i2c_data <= {8'h00,8'h81};//--set contrast control register
7'd5 :O_i2c_data <= {8'h00,8'hCF};// Set SEG Output Current Brightness
7'd6 :O_i2c_data <= {8'h00,8'hA1};//--Set SEG/Column Mapping 0xa0左右反置 0xa1正常
7'd7 :O_i2c_data <= {8'h00,8'hC8};//Set COM/Row Scan Direction 0xc0上下反置 0xc8正常
7'd8 :O_i2c_data <= {8'h00,8'hA6};//--set normal display
7'd9 :O_i2c_data <= {8'h00,8'hA8};//--set multiplex ratio(1 to 64)
7'd10:O_i2c_data <= {8'h00,8'h3F};//--1/64 duty
7'd11:O_i2c_data <= {8'h00,8'hD3};//-set display offset Shift Mapping RAM Counter (0x00~0x3F)
7'd12:O_i2c_data <= {8'h00,8'h00};//-not offset
7'd13:O_i2c_data <= {8'h00,8'hD5};//--set display clock divide ratio/oscillator frequency
7'd14:O_i2c_data <= {8'h00,8'h80};//--set divide ratio, Set Clock as 100 Frames/Sec
7'd15:O_i2c_data <= {8'h00,8'hD9};//--set pre-charge period
7'd16:O_i2c_data <= {8'h00,8'hF1};//Set Pre-Charge as 15 Clocks & Discharge as 1 Clock
7'd17:O_i2c_data <= {8'h00,8'hDA};//--set com pins hardware configuration
7'd18:O_i2c_data <= {8'h00,8'h12};//
7'd19:O_i2c_data <= {8'h00,8'hDB};//--set vcomh
7'd20:O_i2c_data <= {8'h00,8'h40};//Set VCOM Deselect Level
7'd21:O_i2c_data <= {8'h00,8'h20};//-Set Page Addressing Mode (0x00/0x01/0x02)
7'd22:O_i2c_data <= {8'h00,8'h02};//
7'd23:O_i2c_data <= {8'h00,8'h8D};//--set Charge Pump enable/disable
7'd24:O_i2c_data <= {8'h00,8'h14};//--set(0x10) disable
7'd25:O_i2c_data <= {8'h00,8'hA4};// Disable Entire Display On (0xa4/0xa5)
7'd26:O_i2c_data <= {8'h00,8'hA6};// Disable Inverse Display On (0xa6/a7)
7'd27:O_i2c_data <= {8'h00,8'hAF};
default : /* default */;
endcase
数据刷新:
case (R_data_cnt)
11'd0:O_i2c_data <= {8'h00,8'hB0};
11'd1:O_i2c_data <= {8'h00,8'h00};
11'd2:O_i2c_data <= {8'h00,8'h10};
// page 0
11'd131:O_i2c_data <= {8'h00,8'hB1};
11'd132:O_i2c_data <= {8'h00,8'h00};
11'd133:O_i2c_data <= {8'h00,8'h10};
// page 1
11'd262:O_i2c_data <= {8'h00,8'hB2};
11'd263:O_i2c_data <= {8'h00,8'h00};
11'd264:O_i2c_data <= {8'h00,8'h10};
// page 2
11'd393:O_i2c_data <= {8'h00,8'hB3};
11'd394:O_i2c_data <= {8'h00,8'h00};
11'd395:O_i2c_data <= {8'h00,8'h10};
// page 3
11'd524:O_i2c_data <= {8'h00,8'hB4};
11'd525:O_i2c_data <= {8'h00,8'h00};
11'd526:O_i2c_data <= {8'h00,8'h10};
// page 4
11'd655:O_i2c_data <= {8'h00,8'hB5};
11'd656:O_i2c_data <= {8'h00,8'h00};
11'd657:O_i2c_data <= {8'h00,8'h10};
// page 5
11'd786:O_i2c_data <= {8'h00,8'hB6};
11'd787:O_i2c_data <= {8'h00,8'h00};
11'd789:O_i2c_data <= {8'h00,8'h10};
// page 6
11'd917:O_i2c_data <= {8'h00,8'hB7};
11'd918:O_i2c_data <= {8'h00,8'h00};
11'd919:O_i2c_data <= {8'h00,8'h10};
// page 7
default : O_i2c_data <= R_i2c_data; //binary data
endcase
刷新的过程。设置好page的地址,然后从第一列开始写128个数据到显存,完成这个page的刷新。
- 首先B0B7:设置GDDRAM页面起始地址(PAGE0PAGE7)用于使用X[2:0]的页面寻址模式。
- 8’h00:设置页面寻址模式的列起始地址的低四位为4’b0000
- 8’h10:设置页面寻址模式的列起始地址的高四位为4’b0000
关于I2C的读写,采用的是正点原子写的I2C读写模块,正点原子针不戳。
完整代码见附件。
3 一个简单的应用
上位机使用Python实现,代码:
import serial
import time
import pyautogui
import cv2
# import matplotlib.pyplot as plt
import numpy as np
import threading
serialPort = "COM3"
baudRate = 921600
timeout1 = 0.5
frame_cnt = 0
try:
ser = serial.Serial(serialPort, baudRate, timeout=timeout1)
except:
print("串口打开失败!")
else:
print("参数设置:串口=%s ,波特率=%d" % (serialPort, baudRate))
def fun_refresh():
global frame_cnt
print("frame_cnt:%d" % frame_cnt)
frame_cnt = frame_cnt + 1
img = pyautogui.screenshot() # x,y,w,h
img_128x64 = np.asarray(img)[0:1024:16, 0:1920:15, :]
# cv2.imshow(img_128x64)
gray = cv2.cvtColor(np.asarray(img_128x64), cv2.COLOR_RGB2GRAY)
ret, binary = cv2.threshold(gray, 0, 255, cv2.THRESH_OTSU)
head_hex_str = bytes.fromhex('55 55 55')
ser.write(head_hex_str)
for i in range(0, 8):
for j in range(0, 128):
a = np.array(binary[i * 8:(i + 1) * 8, j] / 255).astype(int)
c = a[::-1]
Bi_conver_op = 2 ** np.arange(a.shape[0]) # shape=[1,6]
b = c.dot(Bi_conver_op[::-1].T)
string = str(hex(b))
if len(string) == 3:
string1 = '0' + string[2]
else:
string1 = string[2] + string[3]
ser.write(bytes.fromhex(string1))
t = time.time()
print("time(ms): %d" % int(round(t * 1000)))
global timer
timer = threading.Timer(0.1, fun_refresh)
timer.start()
timer = threading.Timer(1, fun_refresh)
timer.start()
time.sleep(600)
timer.cancel()
ser.close()
上位机功能:每隔0.1s截取屏幕1920*1080p,并转换成128*64p,并使用opencv完成灰度转化以及二值化处理,最后通过串口发送数据。帧头为0x55_55_55。
oled_display:缓存数据帧并对数据帧采用乒乓处理。
oled_ctrl:初始化OLED并完成OLED的刷新,刷新时输出每个page数据的地址,从oled_display获得要显示的数据。
i2c_dri:I2C驱动。
效果:
附件
- 工程:
链接:https://pan.baidu.com/s/1jRdPJOrJ0g3FqO9D6fZyPg
提取码:open - 资料:
链接:https://pan.baidu.com/s/1QmSNjwLDqDJCRPrtUOtIYA
提取码:open