FPGA调用OLED
因为参加硬禾学堂的寒假在家一起练的活动,所以记录一些中间遇到的困难和解决方法,当然我因为刚刚接触FPGA,所以在了解了一定的驱动芯片的指令之后,发现电子森林中有驱动TFT_LCD的程序,所以直接拿过来修改了
OLED介绍
样式介绍
OLED屏幕的特点为:0.91寸七针,SPI协议通信;其中我的板子上面的七针有CLK(有些地方是用D0代替),MOSI(也可以用D1)
个人经验
我建议大家如果说是刚刚入手OLED,直接先把通信协议调好,然后把OLED初始化的程序发送给OLED再去调整,不然很容易产生挫败,因为刚入手OLED的话,有很多的指令不清楚,并且一开始想让OLED亮起来,需要发送的指令比较多
OLED驱动芯片指令介绍--SSD1306
先放一下我看过觉得好的指令介绍网址:
一:版权声明:本文为CSDN博主「专注于无线通信的蓬勃」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/XiaoXiaoPengBo/java/article/details/81329770二:http://blog.sina.com.cn/s/blog_57ad1bd20102wtq8.html,这个博主的文章最下面的指令比较不常用(还是我用的少,所以我觉得不常用)
在本次项目中,因为我上面说了我主要是修改了电子森林的程序,所以具体的指令并没有了解太多,主要是了解了一下Addressing Setting Command Table(地址设定命令表)中的指令,具体的指令如图1所示:
按照我了解的顺序介绍一下指令:
最上面的两条指令(Set Lower Column Start Address for Page Addressing和Set Higher Column Start Address for Page Addressing Mode)是说的是在页地址模式下,设置起始地址的低四位和高四位,这样子加在一起刚好就是8位;
然后在看第三条指令(Set Memory Addressing Mode),这条指令是设置内存寻址方式,其中我们可以用三种模式,分别都是:水平地址模式;垂直地址模式;页地址模式(我目前采用的多是这种方式,因为要显示数据,一般不会是在一页上面写数据,需要写 在不同页的同几列上)
之后在看最后一条指令(Set Page Start Address for Page Addressing Mode),这条指令是在页地址模式下,设置页的起始地址
然后我觉得光是说明指令和指令的作用,大家可能对指令的直观印象还是不太清楚,我来举个例字,我们先看图2:
这是OLED的第三页(因为页码是从0开始算的),这时候我们假如给OLED发送0xB2,之后给OLED发送命令是0x03(设置起始地址的低四位)之后再次给OLED发送命令0x10(设置起始地址的高四位),这样子就把我们的起始位置确定在第三页的SEG3了,就是说之后在把具体的数据发送到OLED就会从第三页的第三列的LED灯开始亮或者灭。
至于设置内存寻址的三种方式:页地址模式:就是从一页的起始列地址开始知道这一页的结束列地址,在这个过程中一列的数据写完,列地址指针会自动增加1跳到随后的一列
水平模式:就是页地址的升级版本,一开始和页地址模式一样,就是从设定的某一页的起始列地址(具体位置根据指令)开始,每次当一列数据写完,列地址指针都会自动增加1,当列地址的指针到达我们设定的结束列地址,列地址指针就会恢复到设定的起始列地址指针,并且页地址指针会增加1,具体图像,请看下图
垂直模式:和之前的两种模式不太一样,具体的表现是:它是从某一页的一个起始列地址开始,当输入完一列数据后,它的列地址指针并不会增加,首先增加的页地址指针,之后当页地址指针增加到设定的结束页地址之后,才会增加列地址指针,并且页地址指针重新恢复到起始页地址
0.91寸OLED初始化指令
放一下我的小伙伴给我的一开始的网页参照:
https://blog.csdn.net/p1279030826/article/details/107234646,我一开始是参考这位博主写的
initial //OLED初始化的命令及数据
begin
reg_init[0] = {1'b0,8'hAE}; // 关OLED显示
reg_init[1] = {1'b0,8'h00}; // 正常显示(1亮0灭)
reg_init[2] = {1'b0,8'h10}; // 启用电荷泵
reg_init[3] = {1'b0,8'h40}; // 启用电荷泵
reg_init[4] = {1'b0,8'h81}; // 开OLED显示
reg_init[5] = {1'b0,8'hFF};//全部显示
reg_init[6] = {1'b0,8'hA1};//
reg_init[7] = {1'b0,8'hC8};
reg_init[8] = {1'b0,8'hA6};
reg_init[9] = {1'b0,8'hA8};
reg_init[10] = {1'b0,8'h1f};//OLED的尺寸为0.91时
// reg_init[10] = {1'b0,8'h3f};//OLED的尺寸为0.96时
reg_init[11] = {1'b0,8'hD3};
reg_init[12] = {1'b0,8'h00};
reg_init[13] = {1'b0,8'hd5};
reg_init[14] = {1'b0,8'h80};
reg_init[15] = {1'b0,8'hD9};
reg_init[16] = {1'b0,8'hF1};
reg_init[17] = {1'b0,8'hDA};
reg_init[18] = {1'b0,8'h02};//OLED的尺寸为0.91时
// reg_init[18] = {1'b0,8'h12};//OLED的尺寸为0.96时
reg_init[19] = {1'b0,8'hDB};
reg_init[20] = {1'b0,8'h40};
reg_init[21] = {1'b0,8'h20};
reg_init[22] = {1'b0,8'h02};
reg_init[23] = {1'b0,8'h8D};
reg_init[24] = {1'b0,8'h14};
reg_init[25] = {1'b0,8'hA4};//这个地方建议一开始调试的新手,发送A5
//A5指令的命令是全屏点亮开启,可以方便的调
// 试SPI协议,但是发送A5之后,会无视你向
//OLED的RAM写入的其他数据,如果可以点亮全
//屏,用A4(全屏点亮关闭)即可
reg_init[26] = {1'b0,8'hA6};
reg_init[27] = {1'b0,8'hAF};
reg_init[28] = {1'b0,8'hA7};
end
引脚介绍,芯片介绍
我的FPGA的芯片是于Lattice XO2-4000HC-4MG132,具体使用的引脚是
OLED_CLK对应E12 | OLED_MOSI对应F12 | OLED_RES对应G12 | OLED_DC对应F13 | OLED_CS对应F14 |
程序介绍
首先编写程序之前,我们需要先去了解SPI这个协议,可以通过下面的文章来了解SPI协议的具体细节:
当我们了解了SPI协议的大致过程,我们还需要观察控制SSD1306的时序,这样子才能控制FPGA通过正确的拉高拉低传送正确的命令和数据给SSD1306,图6是SSD1306的开启时序
之后我又找到一张图,图7是关于4线SPI的时序介绍
因为是不够熟悉FPGA,所以我直接修改了电子森林中调动TFT_LCD的程序,电子森林TFT_LCD的程序具体链接地址:https://www.eetree.cn/wiki/lcd%E6%98%BE%E7%A4%BA%E6%A8%A1%E5%9D%97
module LCD_RGB
(
input clk_in, //12MHz系统时钟
input rst_n_in, //系统复位,低有效
output reg OLED_RES, //OLED复位
output reg OLED_CS, //OLED片选控制
output reg OLED_DC, //OLED数据指令控制
output reg OLED_SCK, //OLED时钟信号
output reg OLED_MOSI //OLED数据信号
);
localparam INIT_DEPTH = 16'd28; //LCD初始化的命令及数据的数量
localparam IDLE = 3'd0;
localparam MAIN = 3'd1;
localparam INIT = 3'd2;
localparam SCAN = 3'd3;
localparam WRITE = 3'd4;
localparam DELAY = 3'd5;
localparam LOW = 1'b0;
localparam HIGH = 1'b1;
//assign lcd_bl_out = HIGH; // backlight active high level
//wire [15:0] color_t = YELLOW; //顶层色为黄色
//wire [15:0] color_b = BLACK; //背景色为黑色
//reg [7:0] x_cnt;
//reg [7:0] y_cnt;
reg [8:0] data_reg; //
reg [8:0] reg_clear [10:0];
reg [8:0] reg_setxy [30:0];
reg [8:0] reg_init [72:0];
reg [2:0] cnt_main;
reg [2:0] cnt_init;
reg [2:0] cnt_scan;
reg [5:0] cnt_write;
reg [15:0] cnt_delay;
reg [15:0] num_delay;
reg [15:0] cnt;
///reg [15:0] cnt_divide_4;
//reg high_word;
reg [2:0] state = IDLE;
reg [2:0] state_back = IDLE;
reg [7:0] cnt_clear;
reg [7:0] cnt_setxy;
reg [2:0] cnt_finnal_second;
//wire clk_in_1MHz;divide divide_1MHz (.clk(clk_in),.rst_n(rst_n_in),.clkout(clk_in_1MHz));
always@(posedge clk_in or negedge rst_n_in) begin
if(!rst_n_in) begin
//x_cnt <= 8'd0;
//y_cnt <= 8'd0;
OLED_RES <= 0;
cnt_main <= 3'd0;
cnt_init <= 3'd0;
cnt_scan <= 3'd0;
cnt_write <= 6'd0;
cnt_delay <= 16'd0;
num_delay <= 16'd50;
cnt <= 16'd0;
cnt_finnal_second <= 3'd0;
cnt_clear <= 8'd0;
cnt_setxy <= 8'd0;
//high_word <= 1'b1;
state <= IDLE;
state_back <= IDLE;
end else begin
case(state)
IDLE:begin
//x_cnt <= 8'd0;
//y_cnt <= 8'd0;
cnt_main <= 3'd0;
cnt_init <= 3'd0;
cnt_scan <= 3'd0;
cnt_write <= 6'd0;
cnt_delay <= 16'd0;
num_delay <= 16'd50;
cnt_finnal_second <= 3'd0;
cnt_clear <= 8'd0;
cnt_setxy <= 8'd0;
//high_word <= 1'b1;
state <= MAIN;
state_back <= MAIN;
end
MAIN:begin
case(cnt_main) //MAIN状态
3'd0: begin state <= INIT; cnt_main <= cnt_main + 1'b1; end
3'd1: begin state <= SCAN; cnt_main <= cnt_main + 1'b1; end
3'd2: begin cnt_main <= 1'b1; end
default: state <= IDLE;
endcase
end
INIT:begin //初始化状态
case(cnt_init)
3'd0: begin OLED_RES <= 1'b0; cnt_init <= cnt_init + 1'b1; end //复位有效
3'd1: begin num_delay <= 16'd120;state <= DELAY; state_back <= INIT; cnt_init <= cnt_init + 1'b1; end //延时超过3微秒
3'd2: begin OLED_RES <= 1'b1; cnt_init <= cnt_init + 1'b1;end //复位恢复
3'd3: begin num_delay <= 16'd120; state <= DELAY; state_back <= INIT; cnt_init <= cnt_init + 1'b1; end //延时超过3微秒
3'd4: begin
if(cnt>=INIT_DEPTH) begin //当73条指令及数据发出后,配置完成
cnt <= 16'd0;
cnt_init <= cnt_init + 1'b1;
end else begin
data_reg <= reg_init[cnt];
num_delay <= 16'd6;
cnt <= cnt + 16'd1;
state <= WRITE;
state_back <= INIT;
OLED_CS <= 1'b0;
end
end
3'd5: begin cnt_init <= 1'b0; state <= MAIN; end //初始化完成,返回MAIN状态
default: state <= IDLE;
endcase
end
SCAN:begin //清屏状态和写数据状态
case(cnt_scan)
3'd0: begin //清空屏幕
if(cnt == 16'd32) begin
cnt_scan <= cnt_scan + 1'b1;
cnt <= 16'd0;
cnt_finnal_second <= 3'd0;
end else begin
case(cnt_finnal_second[1:0])
2'd0:begin
data_reg <= reg_clear[cnt_finnal_second[1:0]] + cnt / 4;//写页地址
cnt <= cnt + 16'd1;
cnt_finnal_second <= cnt_finnal_second + 3'd1;
num_delay <= 16'd6;
state <= WRITE;
state_back <= SCAN;
OLED_CS <= 1'b0;
end
2'd1,2'd2:begin
data_reg <= reg_clear[cnt_finnal_second[1:0]];//写列起始地址,低和高位
cnt <= cnt + 16'd1;
cnt_finnal_second <= cnt_finnal_second + 3'd1;
num_delay <= 16'd6;
state <= WRITE;
state_back <= SCAN;
OLED_CS <= 1'b0;
end
2'd3:begin//自动循环128次
if(cnt_clear == 8'd127)begin
cnt_clear <= 8'd0;
cnt <= cnt + 16'd1;
cnt_finnal_second <= cnt_finnal_second + 3'd1;
end else begin
cnt_clear <= cnt_clear + 8'd1;
end
data_reg <= reg_clear[cnt_finnal_second[1:0]];//填0,填128次
num_delay <= 16'd6;
state <= WRITE;
state_back <= SCAN;
OLED_CS <= 1'b0;
end
endcase
end
end
3'd1: begin //负责写数据
case(cnt_finnal_second)
3'd0:begin
data_reg <= reg_setxy[0];//写页地址
cnt_finnal_second <= cnt_finnal_second + 3'd1;
num_delay <= 16'd6;
state <= WRITE;
state_back <= SCAN;
OLED_CS <= 1'b0;
end
3'd1:begin
data_reg <= reg_setxy[1];//写列起始地址,低位
cnt_finnal_second <= cnt_finnal_second + 3'd1;
num_delay <= 16'd6;
state <= WRITE;
state_back <= SCAN;
OLED_CS <= 1'b0;
end
3'd2:begin
data_reg <= reg_setxy[2];//写列起始地址,高位
cnt_finnal_second <= cnt_finnal_second + 3'd1;
num_delay <= 16'd6;
state <= WRITE;
state_back <= SCAN;
OLED_CS <= 1'b0;
end
3'd3:begin
if(cnt == 8'd7)begin
cnt_finnal_second <= cnt_finnal_second + 3'd1;
end else begin
cnt <= cnt + 16'd1;
end
data_reg <= reg_setxy[cnt + 3];//写需要显示数据的第一页中的数据
num_delay <= 16'd6;
state <= WRITE;
state_back <= SCAN;
OLED_CS <= 1'b0;
end
3'd4:begin
data_reg <= reg_setxy[cnt + 4];//写页地址
cnt_finnal_second <= cnt_finnal_second + 3'd1;
num_delay <= 16'd6;
state <= WRITE;
state_back <= SCAN;
OLED_CS <= 1'b0;
end
3'd5:begin
data_reg <= reg_setxy[cnt + 5];//写列起始地址,低位
cnt_finnal_second <= cnt_finnal_second + 3'd1;
num_delay <= 16'd6;
state <= WRITE;
state_back <= SCAN;
OLED_CS <= 1'b0;
end
3'd6:begin
data_reg <= reg_setxy[cnt + 6];//写列起始地址,高位
cnt_finnal_second <= cnt_finnal_second + 3'd1;
num_delay <= 16'd6;
state <= WRITE;
state_back <= SCAN;
OLED_CS <= 1'b0;
end
3'd7:begin
if(cnt == 8'd16)begin
cnt <= 16'd0;
cnt_scan <= cnt_scan + 1'b1;
cnt_finnal_second <= 3'd0;
end else begin
cnt <= cnt + 16'd1;
end
data_reg <= reg_setxy[cnt + 7];//写需要显示数据的第二页中的数据
num_delay <= 16'd6;
state <= WRITE;
state_back <= SCAN;
OLED_CS <= 1'b0;
end
endcase
end
3'd2: begin cnt_scan <= 1'b0; state <= MAIN; end
default: state <= IDLE;
endcase
end
WRITE:begin //WRITE状态,将数据按照SPI时序发送给屏幕
if(cnt_write >= 6'd18) cnt_write <= 1'b0;
else cnt_write <= cnt_write + 1'b1;
case(cnt_write)//**可以考虑10位数据,第10位为CS片选位**
6'd0: begin OLED_DC <= data_reg[8];end //9位数据最高位为命令数据控制位
6'd1: begin OLED_SCK <= LOW; OLED_MOSI <= data_reg[7];end //先发高位数据
6'd2: begin OLED_SCK <= HIGH;end
6'd3: begin OLED_SCK <= LOW; OLED_MOSI <= data_reg[6];end
6'd4: begin OLED_SCK <= HIGH;end
6'd5: begin OLED_SCK <= LOW; OLED_MOSI <= data_reg[5];end
6'd6: begin OLED_SCK <= HIGH;end
6'd7: begin OLED_SCK <= LOW; OLED_MOSI <= data_reg[4];end
6'd8: begin OLED_SCK <= HIGH; end
6'd9: begin OLED_SCK <= LOW; OLED_MOSI <= data_reg[3];end
6'd10: begin OLED_SCK <= HIGH;end
6'd11: begin OLED_SCK <= LOW; OLED_MOSI <= data_reg[2];end
6'd12: begin OLED_SCK <= HIGH;end
6'd13: begin OLED_SCK <= LOW; OLED_MOSI <= data_reg[1];end
6'd14: begin OLED_SCK <= HIGH; end
6'd15: begin OLED_SCK <= LOW; OLED_MOSI <= data_reg[0];end //后发低位数据
6'd16: begin OLED_SCK <= HIGH;end
6'd17: begin OLED_CS <= 1; OLED_DC <=1;end
6'd18: begin
//OLED_SCK <= LOW;
state <= DELAY;end
default: state <= IDLE;
endcase
end
DELAY:begin //延时状态
if(cnt_delay >= num_delay) begin
cnt_delay <= 16'd0;
state <= state_back;
end else cnt_delay <= cnt_delay + 1'b1;
end
default:state <= IDLE;
endcase
end
end
// data for scan_clear
initial //设定显示区域指令及数据
begin
reg_clear[0] = {1'b0,8'hb0};
reg_clear[1] = {1'b0,8'h00};
reg_clear[2] = {1'b0,8'h10};
reg_clear[3] = {1'b1,8'h00};
end
// data for scan_char
initial //设定显示区域指令及数据
begin
reg_setxy[0] = {1'b0,8'hb1};
reg_setxy[1] = {1'b0,8'h01};
reg_setxy[2] = {1'b0,8'h10};
reg_setxy[11] = {1'b0,8'hb2};
reg_setxy[12] = {1'b0,8'h01};
reg_setxy[13] = {1'b0,8'h10};
//上面的指令代表对第1页的第4行开始写数据
//以下数据取自0.96寸的OLEDC51的SPI的程序的字符库中,字体未知,大小相同都是8*16
/*
//0x00,0x00,0xC0,0x38,0xE0,0x00,0x00,0x00, 0x20,0x3C,0x23,0x02,0x02,0x27,0x38,0x20
//这段数据代表A,显示的大小为8*16(长*宽)
reg_setxy[3] = {1'b1,8'h00};
reg_setxy[4] = {1'b1,8'h00};
reg_setxy[5] = {1'b1,8'hC0};
reg_setxy[6] = {1'b1,8'h38};
reg_setxy[7] = {1'b1,8'hE0};
reg_setxy[8] = {1'b1,8'h00};
reg_setxy[9] = {1'b1,8'h00};
reg_setxy[10] = {1'b1,8'h00};
reg_setxy[14] = {1'b1,8'h20};
reg_setxy[15] = {1'b1,8'h3C};
reg_setxy[16] = {1'b1,8'h23};
reg_setxy[17] = {1'b1,8'h02};
reg_setxy[18] = {1'b1,8'h02};
reg_setxy[19] = {1'b1,8'h27};
reg_setxy[20] = {1'b1,8'h38};
reg_setxy[21] = {1'b1,8'h20};
*/
/*
//0xE0,0x10,0x08,0x08,0x08,0x10,0xE0,0x00, 0x0F,0x10,0x20,0x20,0x20,0x10,0x0F,0x00
//这段数据代表O,显示的大小为8*16(长*宽)
reg_setxy[3] = {1'b1,8'hE0};
reg_setxy[4] = {1'b1,8'h10};
reg_setxy[5] = {1'b1,8'h08};
reg_setxy[6] = {1'b1,8'h08};
reg_setxy[7] = {1'b1,8'h08};
reg_setxy[8] = {1'b1,8'h10};
reg_setxy[9] = {1'b1,8'hE0};
reg_setxy[10] = {1'b1,8'h00};
reg_setxy[14] = {1'b1,8'h0F};
reg_setxy[15] = {1'b1,8'h10};
reg_setxy[16] = {1'b1,8'h20};
reg_setxy[17] = {1'b1,8'h20};
reg_setxy[18] = {1'b1,8'h20};
reg_setxy[19] = {1'b1,8'h10};
reg_setxy[20] = {1'b1,8'h0F};
reg_setxy[21] = {1'b1,8'h00};
*/
/*
//0x00,0x00,0x80,0x80,0x80,0x80,0x00,0x00, 0x00,0x19,0x24,0x22,0x22,0x22,0x3F,0x20,
//这段数据代表a,显示的大小为8*16(长*宽)
reg_setxy[3] = {1'b1,8'h00};
reg_setxy[4] = {1'b1,8'h00};
reg_setxy[5] = {1'b1,8'h80};
reg_setxy[6] = {1'b1,8'h80};
reg_setxy[7] = {1'b1,8'h80};
reg_setxy[8] = {1'b1,8'h80};
reg_setxy[9] = {1'b1,8'h00};
reg_setxy[10] = {1'b1,8'h00};
reg_setxy[14] = {1'b1,8'h00};
reg_setxy[15] = {1'b1,8'h19};
reg_setxy[16] = {1'b1,8'h24};
reg_setxy[17] = {1'b1,8'h22};
reg_setxy[18] = {1'b1,8'h22};
reg_setxy[19] = {1'b1,8'h22};
reg_setxy[20] = {1'b1,8'h3F};
reg_setxy[21] = {1'b1,8'h20};
*/
/*
//0xC0,0x30,0xC8,0x28,0xE8,0x10,0xE0,0x00, 0x07,0x18,0x27,0x24,0x23,0x14,0x0B,0x00
//这段数据代表O,显示的大小为8*16(长*宽)
reg_setxy[3] = {1'b1,8'hC0};
reg_setxy[4] = {1'b1,8'h30};
reg_setxy[5] = {1'b1,8'hC8};
reg_setxy[6] = {1'b1,8'h28};
reg_setxy[7] = {1'b1,8'hE8};
reg_setxy[8] = {1'b1,8'h10};
reg_setxy[9] = {1'b1,8'hE0};
reg_setxy[10] = {1'b1,8'h00};
reg_setxy[14] = {1'b1,8'h07};
reg_setxy[15] = {1'b1,8'h18};
reg_setxy[16] = {1'b1,8'h27};
reg_setxy[17] = {1'b1,8'h24};
reg_setxy[18] = {1'b1,8'h23};
reg_setxy[19] = {1'b1,8'h14};
reg_setxy[20] = {1'b1,8'h0B};
reg_setxy[21] = {1'b1,8'h00};
*/
//以下程序是取用PCtoLCD2002完美版,字体为宋体,大小为8*16(长*宽),其中第一个DB不用放在数组中,根据长度划分组,这是以8(长度)为1组
//DB 00H 00H 80H 80H 80H 00H 00H 00H 00H 19H 24H 24H 12H 3FH 20H 00H;"a",0
reg_setxy[3] = {1'b1,8'h00};
reg_setxy[4] = {1'b1,8'h00};
reg_setxy[5] = {1'b1,8'h80};
reg_setxy[6] = {1'b1,8'h80};
reg_setxy[7] = {1'b1,8'h80};
reg_setxy[8] = {1'b1,8'h00};
reg_setxy[9] = {1'b1,8'h00};
reg_setxy[10] = {1'b1,8'h00};
reg_setxy[14] = {1'b1,8'h00};
reg_setxy[15] = {1'b1,8'h19};
reg_setxy[16] = {1'b1,8'h24};
reg_setxy[17] = {1'b1,8'h24};
reg_setxy[18] = {1'b1,8'h12};
reg_setxy[19] = {1'b1,8'h3F};
reg_setxy[20] = {1'b1,8'h20};
reg_setxy[21] = {1'b1,8'h00};
//以下程序是取用PCtoLCD2002完美版,字体为宋体,大小为12*32(长*宽),其中第一个DB不用放在数组中
//DB 00H 00H 00H 00H 00H C0H C0H 00H 00H 00H 00H 00H 00H 00H 00H C0H;
//DB 3EH 01H 1FH F8H 00H 00H 00H 00H 00H 00H F0H 0FH 08H 08H 08H 0FH;
//DB FFH C0H 00H 00H 08H 0EH 0DH 08H 00H 00H 00H 08H 09H 0FH 0CH 08H;"A",0
end
// data for init
initial //OLED初始化的命令及数据
begin
reg_init[0] = {1'b0,8'hAE}; // 关OLED显示
reg_init[1] = {1'b0,8'h00}; // 正常显示(1亮0灭)
reg_init[2] = {1'b0,8'h10}; // 启用电荷泵
reg_init[3] = {1'b0,8'h40}; // 启用电荷泵
reg_init[4] = {1'b0,8'h81}; // 开OLED显示
reg_init[5] = {1'b0,8'hFF};//全部显示
reg_init[6] = {1'b0,8'hA1};//
reg_init[7] = {1'b0,8'hC8};
reg_init[8] = {1'b0,8'hA6};
reg_init[9] = {1'b0,8'hA8};
reg_init[10] = {1'b0,8'h1f};//OLED的尺寸为0.91时
// reg_init[10] = {1'b0,8'h3f};//OLED的尺寸为0.96时
reg_init[11] = {1'b0,8'hD3};
reg_init[12] = {1'b0,8'h00};
reg_init[13] = {1'b0,8'hd5};
reg_init[14] = {1'b0,8'h80};
reg_init[15] = {1'b0,8'hD9};
reg_init[16] = {1'b0,8'hF1};
reg_init[17] = {1'b0,8'hDA};
reg_init[18] = {1'b0,8'h02};//OLED的尺寸为0.91时
// reg_init[18] = {1'b0,8'h12};//OLED的尺寸为0.96时
reg_init[19] = {1'b0,8'hDB};
reg_init[20] = {1'b0,8'h40};
reg_init[21] = {1'b0,8'h20};
reg_init[22] = {1'b0,8'h02};
reg_init[23] = {1'b0,8'h8D};
reg_init[24] = {1'b0,8'h14};
reg_init[25] = {1'b0,8'hA4};
reg_init[26] = {1'b0,8'hA6};//设置屏幕正常显示,即1为点亮
reg_init[27] = {1'b0,8'hAF};
// reg_init[28] = {1'b0,8'hA7};//设置屏幕反向显示
end
endmodule
这段代码存在一个问题,它会让FPGA一直刷新我们填写的数据,所以屏幕就会一直抖动显得很不稳定。在一开始的时候,因为没有调试过OLED,所以认为是正常现象,之后观察了一个同期的电子工程师的屏幕发现屏幕抖动是我自己程序编写的问题。解决办法就是只刷新一次,当然这次刷新最好是全屏的刷新,如果不是全屏的刷新会导致某一页有些部分会显示小点点。
说明一下,我怎么使用的取模软件吧
我使用的取模软件是PCtoLCD2002。
首先我们需要知道在程序中是1 亮还是 0 亮,观察初始化中的命令发现有两个A6,说明设置的是屏幕正常显示,如果我们想要点亮对应的点就应该输入1。之后在观察到我们设定的是页模式,所以数据是从我们设定的列起始地址最上面的灯开始,到这一列的最后一个灯,之后列地址加一到后面1列的最上面的灯,循环往复。因此取模软件中,我们也应该这么设定。
选择如下图的设定,主要关注画圈的地方。最终设置完之后的结果可以观察右下角的取模演示,其数据输入的结果应该和我们设定的页地址模式一样。之后就是自行取模了。