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*643)多种接口方式;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,该指令用于设置显示时的起始列地址高四位。
1 硬件环境搭建
zynq配置gpio
引脚约束:
set_property -dict{ PACKAGE_PIN Y16 IOSTANDARD LVCMOS33 }[get_ports { GPIO_OLED_tri_io[0]}];#CSset_property -dict{ PACKAGE_PIN Y14 IOSTANDARD LVCMOS33 }[get_ports { GPIO_OLED_tri_io[1]}];#DCset_property -dict{ PACKAGE_PIN U12 IOSTANDARD LVCMOS33 }[get_ports { GPIO_OLED_tri_io[2]}];#RESset_property -dict{ PACKAGE_PIN W13 IOSTANDARD LVCMOS33 }[get_ports { GPIO_OLED_tri_io[3]}];#D1 SCKset_property -dict{ PACKAGE_PIN R17 IOSTANDARD LVCMOS33 }[get_ports { GPIO_OLED_tri_io[4]}];#D2 SDINset_property -dict{ PACKAGE_PIN V20 IOSTANDARD LVCMOS33 }[get_ports { GPIO_OLED_tri_io[5]}];#LED
2 软件设计代码
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//连接到EMIO5XGpioPs 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 TES\n\r");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;}
评论区
登录后即可参与讨论
立即登录