1、OLED 简介
OLED,即有机发光二极管( Organic Light Emitting Diode)。 OLED 由于同时具备自发光,不需背光源、对比度高、厚度薄、视角广、反应速度快、可用于挠曲性面板、使用温度范围广、构造及制程较简单等优异之特性,被认为是下一代的平面显示器新兴应用技术。
LCD 都需要背光,而 OLED 不需要,因为它是自发光的。这样同样的显示 OLED 效果要来得好一些。以目前的技术,OLED 的尺寸还难以大型化,但是分辨率确可以做到很高。在此我们使用的是中景园电子的 0.96 寸 OLED 显示屏,该屏有以下特点:
1)0.96 寸 OLED 有黄蓝,白,蓝三种颜色可选;其中黄蓝是屏上 1/4 部分为黄光,下 3/4 为蓝;而且是固定区域显示固定颜色,颜色和显示区域均不能修改;白光则为纯白,也就是黑底白字:蓝色则为纯蓝,也就是黑底蓝字。
2)分辨率为 128*64
3)多种接口方式;OLED 裸屏总共种接口包括:6800、8080 两种并行接口方式、3线或 4 线的串行 SPI 接口方式、 l℃ 接口方式(只需要 2 根线就可以控制 OLED 了!),这五种接口是通过屏上的 BSO~BS2 来配置的。
2、0.96 寸 OLED模块
2.1 SPI/IIC 接口模块
模块接口定义:
1.GND 电源地
2.VCC 电源正(3~5.5V)
3.D0 OLED 的 D0 脚,在 SPI 和 IIC 通信中为时钟管脚
4.D1 OLED 的 D1 脚,在 SPI 和 IIC 通信中为数据管脚
5.RES OLED 的 RES#脚,用来复位(低电平复位)
6.DC OLED 的 D/C#E 脚,数据和命令控制管脚
7. CS OLED 的CS#脚,也就是片选管脚
2.2 SPI模式
在 4 线 SPI 模式下,每个数据长度均为 8 位,在 SCLK 的上升沿,数据从 SDIN 移入到SSD1306,并且是高位在前的。 DC 线还是用作命令/数据的标志线。在 4 线 SPI 模式下,写操
作的时序如图
3、OLED显存
SD1306 的显存总共为 12864bit 大小, SSD1306 将这些显存分为了 8 页, 不使用显存对应的行列的重映射, 其对应关系如表
可以看出, SSD1306 的每页包含了 128 个字节,总共 8 页,这样刚好是 12864 的点阵大小。 当 GRAM 的写入模式为页模式时,需要设置低字节起始的列地址(0x00~0x0F) 和高字节的起始列地址(0x10~0x1F),芯片手册中给出了写入 GRAM 与显示的对应关系,写入列地址在写完一字节后自动按列增长, 如图
常用的命令:
第 0 个命令为 0X81,用于设置对比度的,这个命令包含了两个字节,第一个 0X81 为命令,随后发送的一个字节为要设置的对比度的值。这个值设置得越大屏幕就越亮。
第 1 个命令为 0XAE/0XAF。 0XAE 为关闭显示命令; 0XAF 为开启显示命令。
第 2 个命令为 0X8D,该指令也包含 2 个字节,第一个为命令字,第二个为设置值,第二个字节的 BIT2 表示电荷泵的开关状态,该位为 1,则开启电荷泵,为 0 则关闭。在模块初始化的时候,这个必须要开启,否则是看不到屏幕显示的。
第 3 个命令为 0XB0~B7,该命令用于设置页地址,其低三位的值对应着 GRAM 的页地址。
第 4 个指令为 0X00~0X0F,该指令用于设置显示时的起始列地址低四位。
第 6 个指令为 0X10~0X1F,该指令用于设置显示时的起始列地址高四位。
4、硬件环境搭建
zynq配置gpio
引脚约束:
set_property -dict{ PACKAGE_PIN Y16 IOSTANDARD LVCMOS33 }[get_ports { GPIO_OLED_tri_io[0]}];#CS
set_property -dict{ PACKAGE_PIN Y14 IOSTANDARD LVCMOS33 }[get_ports { GPIO_OLED_tri_io[1]}];#DC
set_property -dict{ PACKAGE_PIN U12 IOSTANDARD LVCMOS33 }[get_ports { GPIO_OLED_tri_io[2]}];#RES
set_property -dict{ PACKAGE_PIN W13 IOSTANDARD LVCMOS33 }[get_ports { GPIO_OLED_tri_io[3]}];#D1 SCK
set_property -dict{ PACKAGE_PIN R17 IOSTANDARD LVCMOS33 }[get_ports { GPIO_OLED_tri_io[4]}];#D2 SDIN
set_property -dict{ PACKAGE_PIN V20 IOSTANDARD LVCMOS33 }[get_ports { GPIO_OLED_tri_io[5]}];#LED
5、软件设计代码
oled.c
#include"myoled.h"
#include"oledfont.h"
/*
* OLED的显存
* 每个字节表示8个像素, 128,表示有128列, 8表示有64行, 高位表示高行数.
* 比如:g_oled_gram[0][0],包含了第一列,第1~8行的数据. g_oled_gram[0][0].0,即表示坐标(0,0)
* 类似的: g_oled_gram[1][0].1,表示坐标(1,1), g_oled_gram[10][1].2,表示坐标(10,10),
*
* 存放格式如下(高位表示高行数).
* [0]0 1 2 3 ... 127
* [1]0 1 2 3 ... 127
* [2]0 1 2 3 ... 127
* [3]0 1 2 3 ... 127
* [4]0 1 2 3 ... 127
* [5]0 1 2 3 ... 127
* [6]0 1 2 3 ... 127
* [7]0 1 2 3 ... 127
*/
staticuint8_t g_oled_gram[128][8];
intemio_init(){
XGpioPs_Config *gpiops_cfg_ptr;//PS端 GPIO 配置信息
//根据器件ID查找配置信息
gpiops_cfg_ptr =XGpioPs_LookupConfig(GPIOPS_ID);
//初始化器件驱动
XGpioPs_CfgInitialize(&gpiops_inst, gpiops_cfg_ptr, gpiops_cfg_ptr->BaseAddr);
//设置LED为输出
XGpioPs_SetDirectionPin(&gpiops_inst, EMIO_CS,1);
XGpioPs_SetDirectionPin(&gpiops_inst, EMIO_DC,1);
XGpioPs_SetDirectionPin(&gpiops_inst, EMIO_RES,1);
XGpioPs_SetDirectionPin(&gpiops_inst, EMIO_D1,1);
XGpioPs_SetDirectionPin(&gpiops_inst, EMIO_D2,1);
XGpioPs_SetDirectionPin(&gpiops_inst, EMIO_LED,1);
//使能LED输出
XGpioPs_SetOutputEnablePin(&gpiops_inst, EMIO_CS,1);
XGpioPs_SetOutputEnablePin(&gpiops_inst, EMIO_DC,1);
XGpioPs_SetOutputEnablePin(&gpiops_inst, EMIO_RES,1);
XGpioPs_SetOutputEnablePin(&gpiops_inst, EMIO_D1,1);
XGpioPs_SetOutputEnablePin(&gpiops_inst, EMIO_D2,1);
XGpioPs_SetOutputEnablePin(&gpiops_inst, EMIO_LED,1);
OLED_SDIN(1);
OLED_SCLK(1);
OLED_CS(1);
OLED_RS(1);
OLED_RST(0);
usleep(100000);
OLED_RST(1);
oled_wr_byte(0xAE, OLED_CMD);/* 关闭显示 */
oled_wr_byte(0xD5, OLED_CMD);/* 设置时钟分频因子,震荡频率 */
oled_wr_byte(80, OLED_CMD);/* [3:0],分频因子;[7:4],震荡频率 */
oled_wr_byte(0xA8, OLED_CMD);/* 设置驱动路数 */
oled_wr_byte(0X3F, OLED_CMD);/* 默认0X3F(1/64) */
oled_wr_byte(0xD3, OLED_CMD);/* 设置显示偏移 */
oled_wr_byte(0X00, OLED_CMD);/* 默认为0 */
oled_wr_byte(0x40, OLED_CMD);/* 设置显示开始行 [5:0],行数. */
oled_wr_byte(0x8D, OLED_CMD);/* 电荷泵设置 */
oled_wr_byte(0x14, OLED_CMD);/* bit2,开启/关闭 */
oled_wr_byte(0x20, OLED_CMD);/* 设置内存地址模式 */
oled_wr_byte(0x02, OLED_CMD);/* [1:0],00,列地址模式;01,行地址模式;10,页地址模式;默认10; */
oled_wr_byte(0xA1, OLED_CMD);/* 段重定义设置,bit0:0,0->0;1,0->127; */
oled_wr_byte(0xC8, OLED_CMD);/* 设置COM扫描方向;bit3:0,普通模式;1,重定义模式 COM[N-1]->COM0;N:驱动路数 */
oled_wr_byte(0xDA, OLED_CMD);/* 设置COM硬件引脚配置 */
oled_wr_byte(0x12, OLED_CMD);/* [5:4]配置 */
oled_wr_byte(0x81, OLED_CMD);/* 对比度设置 */
oled_wr_byte(0xEF, OLED_CMD);/* 1~255;默认0X7F (亮度设置,越大越亮) */
oled_wr_byte(0xD9, OLED_CMD);/* 设置预充电周期 */
oled_wr_byte(0xf1, OLED_CMD);/* [3:0],PHASE 1;[7:4],PHASE 2; */
oled_wr_byte(0xDB, OLED_CMD);/* 设置VCOMH 电压倍率 */
oled_wr_byte(0x30, OLED_CMD);/* [6:4] 000,0.65*vcc;001,0.77*vcc;011,0.83*vcc; */
oled_wr_byte(0xA4, OLED_CMD);/* 全局显示开启;bit0:1,开启;0,关闭;(白屏/黑屏) */
oled_wr_byte(0xA6, OLED_CMD);/* 设置显示方式;bit0:1,反相显示;0,正常显示 */
oled_wr_byte(0xAF, OLED_CMD);/* 开启显示 */
oled_clear();
return XST_SUCCESS;
}
/**
* @brief 更新显存到OLED
* @param 无
* @retval 无
*/
voidoled_refresh_gram(void)
{
uint8_t 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(g_oled_gram[n][i], OLED_DATA);
}
}
}
#ifOLED_MODE ==1/* 使用8080并口驱动OLED */
/**
* @brief 通过拼凑的方法向OLED输出一个8位数据
* @param data: 要输出的数据
* @retval 无
*/
staticvoidoled_data_out(uint8_t data)
{
uint16_t dat = data &0X0F;
GPIOC->ODR &=~(0XF<<6);/* 清空6~9 */
GPIOC->ODR |= dat <<6;/* D[3:0]-->PC[9:6] */
GPIOC->ODR &=~(0X1<<11);/* 清空11 */
GPIOC->ODR |=((data >>4)&0x01)<<11;/* D4 */
GPIOD->ODR &=~(0X1<<3);/* 清空3 */
GPIOD->ODR |=((data >>5)&0x01)<<3;/* D5 */
GPIOB->ODR &=~(0X3<<8);/* 清空8,9 */
GPIOB->ODR |=((data >>6)&0x01)<<8;/* D6 */
GPIOB->ODR |=((data >>7)&0x01)<<9;/* D7 */
}
/**
* @brief 向OLED写入一个字节
* @param data: 要输出的数据
* @param cmd: 数据/命令标志 0,表示命令;1,表示数据;
* @retval 无
*/
staticvoidoled_wr_byte(uint8_t data,uint8_t cmd)
{
oled_data_out(data);
OLED_RS(cmd);
OLED_CS(0);
OLED_WR(0);
OLED_WR(1);
OLED_CS(1);
OLED_RS(1);
}
#else/* 使用SPI驱动OLED */
/**
* @brief 向OLED写入一个字节
* @param data: 要输出的数据
* @param cmd: 数据/命令标志 0,表示命令;1,表示数据;
* @retval 无
*/
staticvoidoled_wr_byte(uint8_t data,uint8_t cmd)
{
uint8_t i;
OLED_RS(cmd);/* 写命令 */
OLED_CS(0);
for(i =0; i <8; i++)
{
OLED_SCLK(0);
if(data &0x80)
{
OLED_SDIN(1);
}
else
{
OLED_SDIN(0);
}
OLED_SCLK(1);
data <<=1;
}
OLED_CS(1);
OLED_RS(1);
}
#endif
/**
* @brief 开启OLED显示
* @param 无
* @retval 无
*/
voidoled_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 */
}
/**
* @brief 关闭OLED显示
* @param 无
* @retval 无
*/
voidoled_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 */
}
/**
* @brief 清屏函数,清完屏,整个屏幕是黑色的!和没点亮一样!!!
* @param 无
* @retval 无
*/
voidoled_clear(void)
{
uint8_t i, n;
for(i =0; i <8; i++)for(n =0; n <128; n++)g_oled_gram[n][i]=0X00;
oled_refresh_gram();/* 更新显示 */
}
/**
* @brief OLED画点
* @param x : 0~127
* @param y : 0~63
* @param dot: 1 填充 0,清空
* @retval 无
*/
voidoled_draw_point(uint8_t x,uint8_t y,uint8_t dot)
{
uint8_t pos, bx, temp =0;
if(x >127|| y >63)return;/* 超出范围了 */
pos = y /8;/* 计算GRAM里面的y坐标所在的字节, 每个字节可以存储8个行坐标 */
bx = y %8;/* 取余数,方便计算y在对应字节里面的位置,及行(y)位置 */
temp =1<< bx;/* 高位表示高行号, 得到y对应的bit位置,将该bit先置1 */
if(dot)/* 画实心点 */
{
g_oled_gram[x][pos]|= temp;
}
else/* 画空点,即不显示 */
{
g_oled_gram[x][pos]&=~temp;
}
}
/**
* @brief OLED填充区域填充
* @note: 注意:需要确保: x1<=x2; y1<=y2 0<=x1<=127 0<=y1<=63
* @param x1,y1: 起点坐标
* @param x2,y2: 终点坐标
* @param dot: 1 填充 0,清空
* @retval 无
*/
voidoled_fill(uint8_t x1,uint8_t y1,uint8_t x2,uint8_t y2,uint8_t dot)
{
uint8_t x, y;
for(x = x1; x <= x2; x++)
{
for(y = y1; y <= y2; y++)oled_draw_point(x, y, dot);
}
oled_refresh_gram();/* 更新显示 */
}
/**
* @brief 在指定位置显示一个字符,包括部分字符
* @param x : 0~127
* @param y : 0~63
* @param size: 选择字体 12/16/24
* @param mode: 0,反白显示;1,正常显示
* @retval 无
*/
voidoled_show_char(uint8_t x,uint8_t y,uint8_t chr,uint8_t size,uint8_t mode)
{
uint8_t temp, t, t1;
uint8_t y0 = y;
uint8_t*pfont =0;
uint8_t csize =(size /8+((size %8)?1:0))*(size /2);/* 得到字体一个字符对应点阵集所占的字节数 */
chr = chr -' ';/* 得到偏移后的值,因为字库是从空格开始存储的,第一个字符是空格 */
if(size ==12)/* 调用1206字体 */
{
pfont =(uint8_t*)oled_asc2_1206[chr];
}
elseif(size ==16)/* 调用1608字体 */
{
pfont =(uint8_t*)oled_asc2_1608[chr];
}
elseif(size ==24)/* 调用2412字体 */
{
pfont =(uint8_t*)oled_asc2_2412[chr];
}
else/* 没有的字库 */
{
return;
}
for(t =0; t < csize; t++)
{
temp = pfont[t];
for(t1 =0; t1 <8; t1++)
{
if(temp &0x80)oled_draw_point(x, y, mode);
elseoled_draw_point(x, y,!mode);
temp <<=1;
y++;
if((y - y0)== size)
{
y = y0;
x++;
break;
}
}
}
}
/**
* @brief 平方函数, m^n
* @param m: 底数
* @param n: 指数
* @retval 无
*/
staticuint32_toled_pow(uint8_t m,uint8_t n)
{
uint32_t result =1;
while(n--)
{
result *= m;
}
return result;
}
/**
* @brief 显示len个数字
* @param x,y : 起始坐标
* @param num : 数值(0 ~ 2^32)
* @param len : 显示数字的位数
* @param size: 选择字体 12/16/24
* @retval 无
*/
voidoled_show_num(uint8_t x,uint8_t y,uint32_t num,uint8_t len,uint8_t size)
{
uint8_t t, temp;
uint8_t 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_show_char(x +(size /2)*t, y,' ', size,1);/* 显示空格,站位 */
continue;/* 继续下个一位 */
}
else
{
enshow =1;/* 使能显示 */
}
}
oled_show_char(x +(size /2)*t, y, temp +'0', size,1);/* 显示字符 */
}
}
/**
* @brief 显示字符串
* @param x,y : 起始坐标
* @param size: 选择字体 12/16/24
* @param *p : 字符串指针,指向字符串首地址
* @retval 无
*/
voidoled_show_string(uint8_t x,uint8_t y,constchar*p,uint8_t size)
{
while((*p <='~')&&(*p >=' '))/* 判断是不是非法字符! */
{
if(x >(128-(size /2)))/* 宽度越界 */
{
x =0;
y += size;/* 换行 */
}
if(y >(64- size))/* 高度越界 */
{
y = x =0;
oled_clear();
}
oled_show_char(x, y,*p, size,1);/* 显示一个字符 */
x += size /2;/* ASCII字符宽度为汉字宽度的一半 */
p++;
}
}
oled.h
#ifndef__OLED_H
#define__OLED_H
#include"xparameters.h"
#include"xgpiops.h"
#include"sleep.h"
/* OLED模式设置
* 0: 4线串行模式 (模块的BS1,BS2均接GND)
* 1: 并行8080模式 (模块的BS1,BS2均接VCC)
*/
#defineOLED_MODE0/* 默认使用SPI模式 */
#defineGPIOPS_IDXPAR_XGPIOPS_0_DEVICE_ID //PS端 GPIO器件 ID
//SPI --EMIO
#defineEMIO_CS54// 连接到EMIO0
#defineEMIO_DC55//连接到EMIO1
#defineEMIO_RES56//连接到EMIO2
#defineEMIO_D157//连接到EMIO3
#defineEMIO_D258//连接到EMIO4
#defineEMIO_LED59//连接到EMIO5
XGpioPs gpiops_inst;//PS端 GPIO 驱动实例
//#define OLED_CS(x) XGpioPs_WritePin(&gpiops_inst, EMIO_CS,x);
#defineLED(x)do{ x ?
XGpioPs_WritePin(&gpiops_inst, EMIO_LED,1):
XGpioPs_WritePin(&gpiops_inst, EMIO_LED,0);
}while(0)/* 设置LED引脚 */
#defineOLED_RST(x)do{ x ?
XGpioPs_WritePin(&gpiops_inst, EMIO_RES,1):
XGpioPs_WritePin(&gpiops_inst, EMIO_RES,0);
}while(0)/* 设置EMIO_RES引脚 */
#defineOLED_CS(x)do{ x ?
XGpioPs_WritePin(&gpiops_inst, EMIO_CS,1):
XGpioPs_WritePin(&gpiops_inst, EMIO_CS,0);
}while(0)/* 设置EMIO_CS引脚 */
#defineOLED_RS(x)do{ x ?
XGpioPs_WritePin(&gpiops_inst, EMIO_DC,1):
XGpioPs_WritePin(&gpiops_inst, EMIO_DC,0);
}while(0)/* 设置EMIO_DC引脚 */
#defineOLED_SDIN(x)do{ x ?
XGpioPs_WritePin(&gpiops_inst, EMIO_D1,1):
XGpioPs_WritePin(&gpiops_inst, EMIO_D1,0);
}while(0)/* 设置EMIO_D1引脚 */
#defineOLED_SCLK(x)do{ x ?
XGpioPs_WritePin(&gpiops_inst, EMIO_D2,1):
XGpioPs_WritePin(&gpiops_inst, EMIO_D2,0);
}while(0)/* 设置EMIO_D1引脚 */
/* 命令/数据 定义 */
#defineOLED_CMD0/* 写命令 */
#defineOLED_DATA1/* 写数据 */
intemio_init();
staticvoidoled_wr_byte(uint8_t data,uint8_t cmd);/* 写一个字节到OLED */
staticuint32_toled_pow(uint8_t m,uint8_t n);/* OLED求平方函数 */
voidoled_clear(void);/* OLED清屏 */
voidoled_display_on(void);/* 开启OLED显示 */
voidoled_display_off(void);/* 关闭OLED显示 */
voidoled_refresh_gram(void);/* 更新显存到OLED */
voidoled_draw_point(uint8_t x,uint8_t y,uint8_t dot);/* OLED画点 */
voidoled_fill(uint8_t x1,uint8_t y1,uint8_t x2,uint8_t y2,uint8_t dot);/* OLED区域填充 */
voidoled_show_char(uint8_t x,uint8_t y,uint8_t chr,uint8_t size,uint8_t mode);/* OLED显示字符 */
voidoled_show_num(uint8_t x,uint8_t y,uint32_t num,uint8_t len,uint8_t size);/* OLED显示数字 */
voidoled_show_string(uint8_t x,uint8_t y,constchar*p,uint8_t size);/* OLED显示字符串 */
#endif
main.c
#include<stdio.h>
#include"xil_printf.h"
#include"myoled.h"
intmain()
{
uint8_t t =0;
emio_init();
print("OLED TESnr");
oled_show_string(0,0,"LEEE FPGA",24);
oled_show_string(0,24,"0.96' OLED TEST",16);
oled_show_string(0,40,"ATOM 2025/7/22",12);
oled_show_string(0,52,"ASCII:",12);
oled_show_string(64,52,"CODE:",12);
oled_refresh_gram();/* 更新显示到OLED */
while(1){
t =' ';
while(1)
{
oled_show_char(36,52, t,12,1);/* 显示ASCII字符 */
oled_show_num(94,52, t,3,12);/* 显示ASCII字符的码值 */
oled_refresh_gram();/* 更新显示到OLED */
t++;
if(t >'~')t =' ';
LED(1);/* LED0闪烁 */
sleep(1);
LED(0);/* LED0闪烁 */
}
}
return0;
}
6、下板效果
源码链接:
链接: https://pan.baidu.com/s/1w3Q-b5KW1A0ezbf5cUC9Zg?pwd=14ck 提取码: 14ck
2245