0.96OLED显示原理及FPGA驱动程序


很久以前玩的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时钟频率 f I 2 C f_{I^2C} fI2C=350KHz。I2C驱动模块系统时钟为4倍I2C时钟频率 f s y s f_{sys} fsys=1.4MHz。
    从触发一次写到写完成大概需要130个系统时钟。
    完成一次刷新需要1048次I2C写,其中1024个数据以及24个指令。
    所以刷新率为:
    f r e f r e s h = f s y s / 130 / 1048 ≈ 10.276 H z f_{refresh} = f_{sys} / 130 / 1048 \approx 10.276Hz frefresh=fsys/130/104810.276Hz

关于配置空间,注意:

  1. 显存地址与数据排列
    在这里插入图片描述
    在这里插入图片描述
    将整个显示像素点128*64分为8行,127列的8bit数据,数据的低位对应低坐标点。
  2. 设置页寻址模式的下列起始地址(00h~0Fh)
  3. 设置页面寻址模式的高列起始地址(10h~1Fh)
  4. 设置页面寻址方式的页面起始地址(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
  • 20
    点赞
  • 117
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

lu-ming.xyz

觉得有用的话点个赞吧 :)

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值