[Evaluation of domestic FPGA Gaoyun GW1N-4 series development board] OLED display
[Copy link]
I haven't played around with FPGA for a long time . I was busy moving and joining a new company recently. I only had time to play around with Gowin FPGA these days . Now I use Gowin FPGA to drive OLED . Let's start with the experimental phenomenon
1. Hardware
- Materials: Zhongjingyuan's 0.96- inch yellow-blue OLED display, the OLED driver chip is SSD1306 .
- Introduction to OLED
OLED pin , is 7 -pin SPI mode.
1). GND power ground
2). VCC power supply positive ( 3 ~ 5.5V )
3). D0 OLED 's D0 pin is the clock pin in SPI and IIC communication
4). D1 OLED 's D1 pin is the data pin in SPI and IIC communication
5). RES OLED 's RES# pin is used for reset (low level reset)
6 ) . DC OLED 's D/C#E pin, data and command control pin
3. Working principle of OLED display
There is a GDDRAM inside the OLED , the size is 128*64 bits , the RAM is divided into 8 pages, from page0 to page7 , and this form is used to drive the dot array.
When a byte of data is written to GDDRAM , all row data is filled with the column ( 8 -bit) data of the current page , with the low bit at the top and the high bit at the bottom, as shown in the figure. Therefore, only the pseudo dual port is needed to read and write data, and read and write are separated.
2. Software
The program basically simulates the timing of SPI driving OLED , with a clock of 1M .
The code is as follows:
1) Clock division
module clk_fenpin(
input clk,
input rst_n,
output reg clk_1m
);
reg [25:0] clk_cnt ; //分频计数器
//得到1Mhz分频时钟
always @ (posedge clk or negedge rst_n) begin
if (!rst_n) begin
clk_cnt <= 5'd0;
clk_1m <= 1'b0;
end
else if (clk_cnt < 26'd24)
clk_cnt <= clk_cnt + 1'b1;
else begin
clk_cnt <= 5'd0;
clk_1m <= ~ clk_1m;
end
end
endmodule
2 ) spi write timing
module spi_writebyte(
input clk, //时钟信号 1m的时钟
input rst_n, //复位信号 按键复位
input ena_write, //spi写使能信号
input [7:0]data, //spi写的数据
output reg sclk, //oled的时钟信号(d0)
output reg mosi, //oled的数据信号(d1)
output write_done //spi写完成信号
);
parameter S0=0,S1=1,S2=2,Done=3;
reg[1:0] state,next_state;
reg[3:0] cnt; //写数据的位计数器
//状态机下一个状态确认
always @(*) begin
if(!rst_n) begin
next_state <= 2'd0;
end
else begin
case(state)
S0: //等待写使能信号
next_state = ena_write ? S1 : S0;
S1:
next_state = S2;
S2: //从s1到s2的位置cnt才加1所以需要cnt到8再到下一个状态
next_state = (cnt == 4'd8) ? Done : S1;
Done://这个状态主要用来产生done信号输出
next_state = S0;
endcase
end
end
//赋值和状态转换分开
//解决reg输出Latch的问题
always @(posedge clk,negedge rst_n) begin
if(!rst_n) begin
sclk = 1'b1;
mosi = 1'b0;
end
else begin
case(state)
S0: begin//等待写使能信号
sclk = 1'b1;
mosi = 1'b0;
end
S1: begin
sclk = 1'b0;
mosi = data[3'd7-cnt] ? 1'b1 : 1'b0;
end
S2: begin//从s1到s2的位置cnt才加1所以需要cnt到8再到下一个状态
sclk = 1'b1;
end
endcase
end
end
//状态流转
always @(posedge clk,negedge rst_n) begin
if(~rst_n)
state <= S0;
else
state <= next_state;
end
//计数器计数
always @(posedge clk,negedge rst_n) begin
if(~rst_n) begin
cnt <= 4'd0;
end
else begin
if(state == S1)
cnt <= cnt + 1'b1;
else if(state == S0)
cnt <= 4'd0;
else
cnt <= cnt;
end
end
assign write_done = (state==Done);//done信号输出
endmodule
3 ) OLED initialization
module oled_init(
input clk, //时钟信号 1m的时钟
input rst_n, //复位信号
input write_done, //spi写完成信号 获得该信号后开启下一次写
output reg oled_rst, //oled的复位引脚信号
output reg oled_dc, //oled的dc写数据 写命令控制信号
output reg [7:0] data, //输出数据用于spi中写入数据
output reg ena_write, //spi写使能信号
output init_done //初始化完成信号
);
reg [20:0] us_cnt; //us计数器 上电延时等待
reg us_cnt_clr; //计数器清零信号
parameter RST_NUM = 10; //1000_000 //等待1s
//状态说明
//复位状态 初始化写命令状态 oled开写命令状态 oled显示清零写命令状态 oled显示清零写数据状态
//等待初始化写命令完成 等待oled开写命令完成 等待清零写命令完成 等待清零写数据完成
parameter Rst=0,Init=1,OledOn=2,ClearCmd=3,ClearData=4,WaitInit=5,WaitOn=6,WaitClearCmd=7,WaitClearData=8,Done=9;
reg[3:0] state,next_state;//状态机的当前状态和下一个状态
reg [7:0] init_cmd[27:0]; //初始化命令存储
reg [4:0] init_cmd_cnt; //初始化命令计数
reg [7:0] oled_on_cmd[2:0];//oled开命令存储
reg [1:0] oled_on_cmd_cnt; //oled开命令计数
reg [7:0] clear_cmd[24:0]; //清零命令存储
reg [4:0] clear_cmd_cnt; //清零命令计数
reg [10:0] clear_data_cnt; //清零写数据计数
//初始化命令
//这个初始化的点更密集
initial begin
init_cmd[0] = 8'hAE; init_cmd[1] = 8'hD5; init_cmd[2] = 8'h80; init_cmd[3] = 8'hA8;
init_cmd[4] = 8'h3F; init_cmd[5] = 8'hD3; init_cmd[6] = 8'h00; init_cmd[7] = 8'h40;
init_cmd[8] = 8'h8D; init_cmd[9] = 8'h10|8'h04; init_cmd[10] = 8'h20; init_cmd[11] = 8'h02;
init_cmd[12] = 8'hA0|8'h01; init_cmd[13] = 8'hC0; init_cmd[14] = 8'hDA; init_cmd[15] = 8'h02|8'h10;
init_cmd[16] = 8'h81; init_cmd[17] = 8'hCF; init_cmd[18] = 8'hD9; init_cmd[19] = 8'hF1;
init_cmd[20] = 8'hDB; init_cmd[21] = 8'h40; init_cmd[22] = 8'hA4|8'h00; init_cmd[23] = 8'hA6|8'h00;
init_cmd[24] = 8'hAE|8'h01;
end
/*
//初始化命令
//这个初始化出来的点比较稀疏
//应该是分辨率的设置不同把(猜测)
initial begin
init_cmd[0] = 8'hAE; init_cmd[1] = 8'h00; init_cmd[2] = 8'h10; init_cmd[3] = 8'h00;
init_cmd[4] = 8'hB0; init_cmd[5] = 8'h81; init_cmd[6] = 8'hFF; init_cmd[7] = 8'hA1;
init_cmd[8] = 8'hA6; init_cmd[9] = 8'hA8; init_cmd[10] = 8'h1F;init_cmd[11] = 8'hC8;
init_cmd[12] = 8'hD3;init_cmd[13] = 8'h00;init_cmd[14] = 8'hD5;init_cmd[15] = 8'h80;
init_cmd[16] = 8'hD9;init_cmd[17] = 8'h1f;init_cmd[18] = 8'hD9;init_cmd[19] = 8'hF1;
init_cmd[20] = 8'hDA;init_cmd[21] = 8'h00;init_cmd[22] = 8'hDB;init_cmd[23] = 8'h40;
end
*/
//oled开命令
initial begin
oled_on_cmd[0] = 8'h8D;oled_on_cmd[1] = 8'h14;oled_on_cmd[2] = 8'hAF;
end
//oled清零命令
//也就是设置页地址,设置显示的低地址和设置显示的高地址
initial begin
clear_cmd[0] = 8'hB0;clear_cmd[1] = 8'h00;clear_cmd[2] = 8'h10;//第0页
clear_cmd[3] = 8'hB1;clear_cmd[4] = 8'h00;clear_cmd[5] = 8'h10;//第1页
clear_cmd[6] = 8'hB2;clear_cmd[7] = 8'h00;clear_cmd[8] = 8'h10;//第2页
clear_cmd[9] = 8'hB3;clear_cmd[10] = 8'h00;clear_cmd[11] = 8'h10;//第3页
clear_cmd[12] = 8'hB4;clear_cmd[13] = 8'h00;clear_cmd[14] = 8'h10;//第4页
clear_cmd[15] = 8'hB5;clear_cmd[16] = 8'h00;clear_cmd[17] = 8'h10;//第5页
clear_cmd[18] = 8'hB6;clear_cmd[19] = 8'h00;clear_cmd[20] = 8'h10;//第6页
clear_cmd[21] = 8'hB7;clear_cmd[22] = 8'h00;clear_cmd[23] = 8'h10;//第7页
end
//1微秒计数器
always @ (posedge clk or negedge rst_n) begin
if (!rst_n)
us_cnt <= 21'd0;
else if (us_cnt_clr)
us_cnt <= 21'd0;
else
us_cnt <= us_cnt + 1'b1;
end
//有一个故事告诉我们 always(*)别乱用(心酸)
//容易出问题。。。虽然也不知道为什么
//别什么东西都挤一起啊 赋值什么的还是和状态转换分开
//放进时序电路里面
//但是状态转换的下一个状态也不能放进时序电路里面
//会造成当前状态到下一个状态延迟一个时钟周期,时序可能就比较乱
always @(*) begin
if(!rst_n) begin
next_state = Rst;
end
else begin
case(state)
//复位等待状态
//等待上电复位
Rst:
next_state = us_cnt > RST_NUM ? Init : Rst;
//初始化状态
Init:
next_state = WaitInit; //进入等待写命令完成的状态
//等待初始化命令写完成状态
//到达这个状态时cmd cnt才加到1,所以要大一个值判断
//是否25个命令写完成 写完成进入下一个状态
//否则是否spi写完成 spi写完成继续写下一个命令 否则就继续等待spi写完成
//记得加&&write_done等待最后一次写完
WaitInit:
next_state = (init_cmd_cnt == 5'd25&&write_done) ? OledOn : (write_done ? Init : WaitInit);
//oled开写命令状态
OledOn:
next_state = WaitOn;
//等待oled开写命令完成状态
//判断命令是否写完 写完进入下一个状态
//否则 再判断是否spi写完成 写完成继续写下一个数据
WaitOn:
next_state = (oled_on_cmd_cnt == 2'd3&&write_done) ? ClearCmd : (write_done ? OledOn : WaitOn);
//清零写命令状态
ClearCmd:
next_state = WaitClearCmd;
//等待清零写命令状态
//每次写三个命令 所以对3取余数
//这里0会造成进入这个状态就跳转了
WaitClearCmd:
next_state = (clear_cmd_cnt % 2'd3 == 0 && write_done) ? ClearData : (write_done ? ClearCmd : WaitClearCmd);
//清零写数据状态
ClearData:
next_state = WaitClearData;
//等待清零写数据
//1页需要写128个数据,写完7页就是1024个数据
//写完1页,也就是每写完128个数据就要写一次命令,所以要对128取余,然后进入写命令的状态
//其中0是不会对状态造成干扰的,因为进入这个状态的时候计数器已经加过1了
WaitClearData:
next_state = (clear_data_cnt == 11'd1024&&write_done) ? Done : (clear_data_cnt % 11'd128 == 0&&write_done ? ClearCmd : (write_done ? ClearData : WaitClearData));
//完成状态
Done:
next_state = Done;
default:
next_state = Rst;
endcase
end
end
//这个切忌不能写入上面的组合逻辑中
//会造成Latch
//至于原因,,我也不知道
always @(posedge clk,negedge rst_n) begin
if(!rst_n) begin
oled_rst <= 1'b0;
us_cnt_clr <= 1'b1;
oled_dc <= 1'b1;
data <= 8'h10;
ena_write <= 1'b0;
end
else begin
case(state)
//复位等待状态
Rst:begin
oled_rst <= 1'b0;
us_cnt_clr <= 1'b0;
end
//初始化状态
Init:begin
oled_rst <= 1'b1;
us_cnt_clr <= 1'b1; //清零计数器
ena_write <= 1'b1; //写使能
oled_dc <= 1'b0; //写命令
data <= init_cmd[init_cmd_cnt];//写数据赋值
end
//等待初始化命令写完成状态
WaitInit: begin
ena_write <= 1'b0; //写失能
end
//oled开写命令状态
OledOn:begin
ena_write <= 1'b1; //写使能
oled_dc <= 1'b0; //写命令
data <= oled_on_cmd[oled_on_cmd_cnt];
end
//等待oled开写命令完成状态
WaitOn:begin
ena_write <= 1'b0; //写失能
end
//清零写命令状态
ClearCmd:begin
ena_write <= 1'b1;
oled_dc <= 1'b0;
data <= clear_cmd[clear_cmd_cnt];
end
//等待清零写命令状态
WaitClearCmd:begin
ena_write <= 1'b0;
end
//清零写数据状态
ClearData:begin
ena_write <= 1'b1;
oled_dc <= 1'b1;
data <= 8'hff;
end
//等待清零写数据
WaitClearData:begin
ena_write <= 1'b0;
end
endcase
end
end
//状态转换
always @(posedge clk,negedge rst_n) begin
if(!rst_n)
state <= Rst;
else
state <= next_state;
end
//计数器计数
always @(posedge clk,negedge rst_n) begin
if(!rst_n) begin
init_cmd_cnt <= 5'd0;
oled_on_cmd_cnt <= 4'd0;
clear_cmd_cnt <=3'd0;
clear_data_cnt <= 11'd0;
end
else begin
case(state)
Init: init_cmd_cnt <= init_cmd_cnt + 1'b1;
OledOn: oled_on_cmd_cnt <= oled_on_cmd_cnt + 1'b1;
ClearCmd: clear_cmd_cnt <= clear_cmd_cnt + 1'b1;
ClearData: clear_data_cnt <= clear_data_cnt + 1'b1;
default:begin
init_cmd_cnt <= init_cmd_cnt;
oled_on_cmd_cnt <= oled_on_cmd_cnt;
clear_cmd_cnt <= clear_cmd_cnt;
clear_data_cnt <= clear_data_cnt;
end
endcase
end
end
assign init_done = (state == Done);
endmodule
4 ) Read RAM
/****************************************
该模块用来不断读取ram中的数据,然后刷新OLED的显示
****************************************/
module ram_read(
input clk, //时钟信号
input rst_n, //按键复位信号
input write_done, //spi写完成信号
input init_done, //初始化完成
input[7:0] ram_data, //读取到的ram数据
output reg rden, //ram ip核的读使能信号
output [9:0] rdaddress, //ram ip核读地址
output reg ena_write, //spi 写使能信号
output reg oled_dc, //oled的dc写数据 写命令控制信号
output reg[7:0] data //传给 spi写的数据
);
parameter DELAY = 100_000; //刷新率1000_000/100_000 = 10Hz
reg [20:0] us_cnt; //us计数器 上电延时等待
reg us_cnt_clr; //计数器清零信号
//状态说明
//等待初始化完成 写命令 等待写命令完成
//读ram数据 写数据 等待写数据完成
//数据读取完成一遍
parameter WaitInit=0,WriteCmd=1,WaitWriteCmd=2,ReadData=3,WriteData=4,WaitWriteData=5,Done=6;
reg[2:0] state,next_state; //当前状态 和 下一个状态
reg [7:0] write_cmd[24:0]; //清零命令存储
reg [4:0] write_cmd_cnt; //清零命令计数
reg [10:0] address_cnt; //地址计数器
//读地址最多到1023 但是状态转换需要1024 所以使用额外的一个计数器来作为状态转换,同时也提供地址信号
//只是在地址计数器超过1024时,读地址就为0
assign rdaddress = (address_cnt >= 11'd1024) ? 10'd0 : address_cnt;
//oled清零命令
//也就是设置页地址,设置显示的低地址和设置显示的高地址
//第7页在靠近引脚的位置,从高页写到地页,这么写方便自己查看
initial begin
write_cmd[0] = 8'hB7;write_cmd[1] = 8'h00;write_cmd[2] = 8'h10;//第7页
write_cmd[3] = 8'hB6;write_cmd[4] = 8'h00;write_cmd[5] = 8'h10;//第6页
write_cmd[6] = 8'hB5;write_cmd[7] = 8'h00;write_cmd[8] = 8'h10;//第5页
write_cmd[9] = 8'hB4;write_cmd[10] = 8'h00;write_cmd[11] = 8'h10;//第4页
write_cmd[12] = 8'hB3;write_cmd[13] = 8'h00;write_cmd[14] = 8'h10;//第3页
write_cmd[15] = 8'hB2;write_cmd[16] = 8'h00;write_cmd[17] = 8'h10;//第2页
write_cmd[18] = 8'hB1;write_cmd[19] = 8'h00;write_cmd[20] = 8'h10;//第1页
write_cmd[21] = 8'hB0;write_cmd[22] = 8'h00;write_cmd[23] = 8'h10;//第0页
end
//1微秒计数器
always @ (posedge clk,negedge rst_n) begin
if (!rst_n)
us_cnt <= 21'd0;
else if (us_cnt_clr)
us_cnt <= 21'd0;
else
us_cnt <= us_cnt + 1'b1;
end
//下一个状态确认
always @(*) begin
if(!rst_n)
next_state = WaitInit;
else begin
case(state)
//等待初始化
WaitInit: next_state = init_done ? WriteCmd : WaitInit;
//写命令
WriteCmd:
next_state = WaitWriteCmd;
//等待写命令
//这些和初始化的地方的写法是一样的
WaitWriteCmd:
next_state = (write_cmd_cnt % 2'd3 == 0 && write_done) ? ReadData : (write_done ? WriteCmd: WaitWriteCmd);
//读数据
ReadData:
next_state = WriteData;
//写数据
WriteData:
next_state = WaitWriteData;
//等待写数据
//这些和初始化的地方的写法是一样的
WaitWriteData:
next_state = (address_cnt == 11'd1024&&write_done) ? Done : (address_cnt % 11'd128 == 0&&write_done ? WriteCmd : (write_done ? ReadData : WaitWriteData));
//一次读写完成,等待100ms,进入下一次读写
Done:begin
if(us_cnt>DELAY)
next_state = WriteCmd;
else
next_state = Done;
end
endcase
end
end
//寄存器赋值和组合逻辑的状态转换分开
always @(posedge clk,negedge rst_n) begin
if(!rst_n) begin
oled_dc <= 1'b1;
ena_write <= 1'b0;
rden <= 1'b0;
us_cnt_clr <= 1'b1;
data <= 8'd0;
end
else begin
case(state)
WriteCmd:begin
ena_write <= 1'b1; //写命令 使能写信号
oled_dc <= 1'b0; //写命令 dc置0
data <= write_cmd[write_cmd_cnt]; //获取写的数据
end
WaitWriteCmd:begin
ena_write <= 1'b0; //写使能信号拉低,等待写完成
end
ReadData: begin
rden <= 1'b1; //ram读使能信号拉高 开始读数据 这个信号可以一直拉高,因为地址不变,读出来的数据都是保持不变的
end
WriteData:begin
ena_write <= 1'b1; //写数据 写使能信号拉高
oled_dc <= 1'b1; //写的是数据 dc置1
data <= ram_data; //为即将要写的数据赋值
end
WaitWriteData: begin
ena_write <= 1'b0; //等待写完成 写使能信号拉低
end
Done:begin
us_cnt_clr <= 1'b0; //计数器复位信号拉低,开始计数
end
endcase
end
end
//状态转换
always @(posedge clk,negedge rst_n) begin
if(!rst_n)
state <= WaitInit;
else
state <= next_state;
end
//计数器计数
always @(posedge clk,negedge rst_n) begin
if(!rst_n) begin
write_cmd_cnt <= 5'd0;
address_cnt <= 11'd0;
end
else begin
case(state)
Done:begin //完成状态 各计数器复位
write_cmd_cnt <= 5'd0;
address_cnt <= 11'd0;
end
WriteCmd: //写命令状态 写命令计数器增加
write_cmd_cnt <= write_cmd_cnt + 1'b1;
ReadData: //读数据状态 读地址增加
address_cnt <= address_cnt + 1'b1;
default:begin//其他状态 计数器值保持不变
write_cmd_cnt <= write_cmd_cnt;
address_cnt <= address_cnt;
end
endcase
end
end
endmodule
5 ) Write RAM
/***************************************
该模块用来向ram中写入显示的数据
地址0~127:第7页
地址128~255:第6页
地址256~383:第5页
地址384~511:第4页
地址512~639:第3页
地址640~767:第2页
地址768~895:第1页
地址896~1023:第0页
****************************************/
module ram_write(
input clk, //时钟信号
input rst_n, //按键复位信号
input en_ram_wr, //模块开始写信号
output reg wren, //ram写使能
output reg [9:0] wraddress, //ram写地址
output reg [7:0] data //写到ram的数据
);
//状态说明
//等待模块使能 写数据 完成
parameter WaitInit=0,WriteData=1,Done=2;
reg[2:0] state,next_state;
reg [7:0] zm[383:0];//写进ram的静态数据
reg [8:0] cnt_zm;//数据计数器
//取模使用PC TO LCD2002 使用逐列式 16进制,阴码 A51 格式取模
//字模数据初始化 字号大小16
initial begin
zm[0]= 8'h20;zm[1]= 8'h08;zm[2]= 8'h24;zm[3]= 8'h10;
zm[4]= 8'h22;zm[5]= 8'h60;zm[6]= 8'h21;zm[7]= 8'h80;
zm[8]= 8'h26;zm[9]= 8'h41;zm[10]= 8'h39;zm[11]= 8'h32;
zm[12]= 8'h02;zm[13]= 8'h04;zm[14]= 8'h0C;zm[15]= 8'h18;
zm[16]= 8'hF0;zm[17]= 8'h60;zm[18]= 8'h13;zm[19]= 8'h80;
zm[20]= 8'h10;zm[21]= 8'h60;zm[22]= 8'h10;zm[23]= 8'h18;
zm[24]= 8'h14;zm[25]= 8'h04;zm[26]= 8'h18;zm[27]= 8'h02;
zm[28]= 8'h00;zm[29]= 8'h01;zm[30]= 8'h00;zm[31]= 8'h00;//"欢",0
zm[32]= 8'h02;zm[33]= 8'h00;zm[34]= 8'h02;zm[35]= 8'h02;
zm[36]= 8'h42;zm[37]= 8'h04;zm[38]= 8'h33;zm[39]= 8'hF8;
zm[40]= 8'h00;zm[41]= 8'h04;zm[42]= 8'h00;zm[43]= 8'h02;
zm[44]= 8'h3F;zm[45]= 8'hF2;zm[46]= 8'h20;zm[47]= 8'h22;
zm[48]= 8'h40;zm[49]= 8'h42;zm[50]= 8'h00;zm[51]= 8'h02;
zm[52]= 8'h3F;zm[53]= 8'hFE;zm[54]= 8'h20;zm[55]= 8'h42;
zm[56]= 8'h20;zm[57]= 8'h22;zm[58]= 8'h3F;zm[59]= 8'hC2;
zm[60]= 8'h00;zm[61]= 8'h02;zm[62]= 8'h00;zm[63]= 8'h00;//"迎",1
zm[64]= 8'h00;zm[65]= 8'h84;zm[66]= 8'h10;zm[67]= 8'h84;
zm[68]= 8'h10;zm[69]= 8'h88;zm[70]= 8'h14;zm[71]= 8'h88;
zm[72]= 8'h13;zm[73]= 8'h90;zm[74]= 8'h10;zm[75]= 8'hA0;
zm[76]= 8'h10;zm[77]= 8'hC0;zm[78]= 8'hFF;zm[79]= 8'hFF;
zm[80]= 8'h10;zm[81]= 8'hC0;zm[82]= 8'h10;zm[83]= 8'hA0;
zm[84]= 8'h11;zm[85]= 8'h90;zm[86]= 8'h16;zm[87]= 8'h88;
zm[88]= 8'h10;zm[89]= 8'h88;zm[90]= 8'h10;zm[91]= 8'h84;
zm[92]= 8'h00;zm[93]= 8'h84;zm[94]= 8'h00;zm[95]= 8'h00;//"来",2
zm[96]= 8'h42;zm[97]= 8'h02;zm[98]= 8'h46;zm[99]= 8'h23;
zm[100]=8'h4A;zm[101]=8'h22;zm[102]=8'h52;zm[103]=8'h22;
zm[104]=8'h63;zm[105]=8'hFE;zm[106]=8'h42;zm[107]=8'h24;
zm[108]=8'h4A;zm[109]=8'h24;zm[110]=8'h46;zm[111]=8'h24;
zm[112]=8'h43;zm[113]=8'h04;zm[114]=8'h00;zm[115]=8'h00;
zm[116]=8'h1F;zm[117]=8'hF0;zm[118]=8'h00;zm[119]=8'h02;
zm[120]=8'h00;zm[121]=8'h01;zm[122]=8'hFF;zm[123]=8'hFE;
zm[124]=8'h00;zm[125]=8'h00;zm[126]=8'h00;zm[127]=8'h00;//"到",3
zm[128]=8'h00;zm[129]=8'h00;zm[130]=8'h00;zm[131]=8'h00;
zm[132]=8'h1F;zm[133]=8'hF8;zm[134]=8'h11;zm[135]=8'h10;
zm[136]=8'h11;zm[137]=8'h10;zm[138]=8'h11;zm[139]=8'h10;
zm[140]=8'h11;zm[141]=8'h10;zm[142]=8'hFF;zm[143]=8'hFE;
zm[144]=8'h11;zm[145]=8'h11;zm[146]=8'h11;zm[147]=8'h11;
zm[148]=8'h11;zm[149]=8'h11;zm[150]=8'h11;zm[151]=8'h11;
zm[152]=8'h1F;zm[153]=8'hF9;zm[154]=8'h00;zm[155]=8'h01;
zm[156]=8'h00;zm[157]=8'h0F;zm[158]=8'h00;zm[159]=8'h00;//"电",4
zm[160]=8'h01;zm[161]=8'h00;zm[162]=8'h41;zm[163]=8'h00;
zm[164]=8'h41;zm[165]=8'h00;zm[166]=8'h41;zm[167]=8'h00;
zm[168]=8'h41;zm[169]=8'h00;zm[170]=8'h41;zm[171]=8'h02;
zm[172]=8'h41;zm[173]=8'h01;zm[174]=8'h47;zm[175]=8'hFE;
zm[176]=8'h45;zm[177]=8'h00;zm[178]=8'h49;zm[179]=8'h00;
zm[180]=8'h51;zm[181]=8'h00;zm[182]=8'h61;zm[183]=8'h00;
zm[184]=8'h41;zm[185]=8'h00;zm[186]=8'h01;zm[187]=8'h00;
zm[188]=8'h01;zm[189]=8'h00;zm[190]=8'h00;zm[191]=8'h00;//"子",5
zm[192]=8'h00;zm[193]=8'h04;zm[194]=8'h20;zm[195]=8'h04;
zm[196]=8'h20;zm[197]=8'h04;zm[198]=8'h20;zm[199]=8'h04;
zm[200]=8'h20;zm[201]=8'h04;zm[202]=8'h20;zm[203]=8'h04;
zm[204]=8'h20;zm[205]=8'h04;zm[206]=8'h3F;zm[207]=8'hFC;
zm[208]=8'h20;zm[209]=8'h04;zm[210]=8'h20;zm[211]=8'h04;
zm[212]=8'h20;zm[213]=8'h04;zm[214]=8'h20;zm[215]=8'h04;
zm[216]=8'h20;zm[217]=8'h04;zm[218]=8'h20;zm[219]=8'h04;
zm[220]=8'h00;zm[221]=8'h04;zm[222]=8'h00;zm[223]=8'h00;//"工",6
zm[224]=8'h24;zm[225]=8'h10;zm[226]=8'h24;zm[227]=8'h60;
zm[228]=8'h25;zm[229]=8'h80;zm[230]=8'h7F;zm[231]=8'hFF;
zm[232]=8'hC4;zm[233]=8'h80;zm[234]=8'h44;zm[235]=8'h60;
zm[236]=8'h00;zm[237]=8'h02;zm[238]=8'h7C;zm[239]=8'h92;
zm[240]=8'h44;zm[241]=8'h92;zm[242]=8'h44;zm[243]=8'h92;
zm[244]=8'h44;zm[245]=8'hFE;zm[246]=8'h44;zm[247]=8'h92;
zm[248]=8'h44;zm[249]=8'h92;zm[250]=8'h7C;zm[251]=8'h92;
zm[252]=8'h00;zm[253]=8'h82;zm[254]=8'h00;zm[255]=8'h00;//"程",7
zm[256]=8'h04;zm[257]=8'h00;zm[258]=8'h04;zm[259]=8'h00;
zm[260]=8'h04;zm[261]=8'h00;zm[262]=8'h7F;zm[263]=8'hFE;
zm[264]=8'h04;zm[265]=8'h02;zm[266]=8'h04;zm[267]=8'h02;
zm[268]=8'hFF;zm[269]=8'hE2;zm[270]=8'h04;zm[271]=8'h22;
zm[272]=8'h04;zm[273]=8'h22;zm[274]=8'h04;zm[275]=8'h22;
zm[276]=8'hFF;zm[277]=8'hE2;zm[278]=8'h04;zm[279]=8'h02;
zm[280]=8'h04;zm[281]=8'h02;zm[282]=8'h04;zm[283]=8'h02;
zm[284]=8'h04;zm[285]=8'h00;zm[286]=8'h00;zm[287]=8'h00;//"世",8
zm[288]=8'h00;zm[289]=8'h10;zm[290]=8'h00;zm[291]=8'h10;
zm[292]=8'h00;zm[293]=8'h20;zm[294]=8'h7F;zm[295]=8'h21;
zm[296]=8'h49;zm[297]=8'h46;zm[298]=8'h49;zm[299]=8'h78;
zm[300]=8'h49;zm[301]=8'h80;zm[302]=8'h7F;zm[303]=8'h00;
zm[304]=8'h49;zm[305]=8'h80;zm[306]=8'h49;zm[307]=8'h7F;
zm[308]=8'h49;zm[309]=8'h40;zm[310]=8'h7F;zm[311]=8'h20;
zm[312]=8'h00;zm[313]=8'h20;zm[314]=8'h00;zm[315]=8'h10;
zm[316]=8'h00;zm[317]=8'h10;zm[318]=8'h00;zm[319]=8'h00;//"界",9
end
//下一个状态确认
always @(*) begin
if(!rst_n)
next_state = WaitInit;
else begin
case(state)
//等待模块使能
WaitInit: next_state = en_ram_wr ? WriteData : WaitInit;
//写数据
WriteData: next_state = (cnt_zm==9'd383) ? Done : WriteData;
//数据写完成
Done: next_state = Done;
endcase
end
end
//每一个状态的逻辑变量赋值
always @(posedge clk,negedge rst_n) begin
if(!rst_n) begin
wren <= 1'b0; //写使能信号复位
data <= 8'd0; //数据值复位
end
else begin
case(state)
WaitInit:begin
wren <= 1'b0; //等待模块使能状态 信号复位
data <= 8'd0;
end
WriteData:begin
wren <= 1'b1; //写使能信号拉高
data <= zm[cnt_zm];//写到ram中的数据赋值
end
Done:begin
wren <= 1'b0;
data <= 8'd0;
end
endcase
end
end
//数据计数器计数
always @(posedge clk,negedge rst_n) begin
if(!rst_n) begin
cnt_zm <= 9'd0;//计数值复位
wraddress <= 10'd0+23;//地址复位,加入偏移量23,使得显示靠中间位置
end
else begin
case(cnt_zm)
9'd126: cnt_zm <= 9'd1; //第1页写完毕 转到第2页
9'd127: cnt_zm <= 9'd128; //第2页写完毕 转到第3页
9'd158: cnt_zm <= 9'd129; //第3页写完毕 转到第4页
9'd159: cnt_zm <= 9'd160; //第4页写完毕 转到第5页
9'd382: cnt_zm <= 9'd161; //第5页写完毕 转到第6页
default:
if(state == WriteData) //写数据状态下,计数器自增,加2是因为一个字模的高度为16,它本页的下一个数据应该在和当前数据间隔着一个
cnt_zm <= cnt_zm + 2'd2;
else
cnt_zm <= cnt_zm; //其他状态保持不变
endcase
//页数说明:主要看你想把字体显示在哪一行
case(cnt_zm)
9'd1: wraddress<=10'd128+24; //进入第2页,地址重新赋值,加入偏移量,显示靠中间位置
9'd128: wraddress<=10'd256; //进入第3页
9'd129: wraddress<=10'd384; //进入第4页
9'd160: wraddress<=10'd512; //进入第5页
9'd161: wraddress<=10'd640; //进入第6页
default:begin
if(state==WriteData) //在写数据的时候地址加1
wraddress <= wraddress + 1'b1;
else
wraddress <= wraddress; //其他状态下地址保持不变
end
endcase
end
end
//状态转换
always @(posedge clk,negedge rst_n) begin
if(!rst_n)
state <= WaitInit;
else
state <= next_state;
end
endmodule
6 ) OLED top module
module oled_drive(
input clk, //时钟信号 50MHz
input rst_n, //按键复位
input ram_rst, //ram复位 高电平复位 92
output oled_rst, //oled res 复位信号 104
output oled_dc, //oled dc 0:写命令 1:写数据 106
output oled_sclk, //oled do 时钟信号 99
output oled_mosi //oled d1 数据信号 101
);
wire clk_1m; //分频后的1M时钟
wire ena_write; //spi写使能信号
wire [7:0] data; //spi写的数据
wire init_done; //初始化完成信号
wire [7:0] init_data;//初始化输出给spi的数据
wire init_ena_wr; //初始化的spi写使能信号
wire init_oled_dc;
wire [7:0] ram_data; //读到的ram数据
wire [7:0] show_data;//输出给spi写的数据
wire rden; //ram的读使能信号
wire [9:0] rdaddress;//ram读地址信号
wire ram_ena_wr; //ram使能写信号
wire ram_oled_dc; //ram模块中的oled dc信号
wire wren; //ram写使能信号
wire [9:0] wraddress;//ram写地址
wire [7:0] wrdata; //写到ram中的数据
//一个信号只能有由一个信号来驱动,所以需要选择一下
assign data = init_done ? show_data : init_data;
assign ena_write = init_done ? ram_ena_wr : init_ena_wr;
assign oled_dc = init_done ? ram_oled_dc : init_oled_dc;
//时钟分频模块 产生1M的时钟
clk_fenpin clk_fenpin_inst(
.clk(clk),
.rst_n(rst_n),
.clk_1m(clk_1m)
);
//spi传输模块
spi_writebyte spi_writebyte_inst(
.clk(clk_1m),
.rst_n(rst_n),
.ena_write(ena_write),
.data(data),
.sclk(oled_sclk),
.mosi(oled_mosi),
.write_done(write_done)
);
//oled初始化模块 产生初始化数据
oled_init oled_init_inst(
.clk(clk_1m),
.rst_n(rst_n),
.write_done(write_done),
.oled_rst(oled_rst),
.oled_dc(init_oled_dc),
.data(init_data),
.ena_write(init_ena_wr),
.init_done(init_done)
);
//ram读模块
ram_read ram_read_inst(
.clk(clk_1m),
.rst_n(rst_n),
.write_done(write_done),
.init_done(init_done),
.ram_data(ram_data),
.rden(rden),
.rdaddress(rdaddress),
.ena_write(ram_ena_wr),
.oled_dc(ram_oled_dc),
.data(show_data)
);
//ram写模块
ram_write ram_write_inst(
.clk(clk_1m),
.rst_n(rst_n),
.en_ram_wr(1'b1),
.wren(wren),
.wraddress(wraddress),
.data(wrdata)
);
assign oce = 1'b1;
//ram ip核
Gowin_SDPB ram_show_inst(
.dout(ram_data), //output [7:0] dout
.clka(clk_1m), //input clka
.cea(wren), //input cea
.reseta(!ram_rst), //input reseta
.clkb(clk_1m), //input clkb
.ceb(rden), //input ceb
.oce(oce), //input oce
.resetb(!ram_rst), //input resetb
.ada(wraddress), //input [9:0] ada
.din(wrdata), //input [7:0] din
.adb(rdaddress) //input [9:0] adb
);
endmodule
7) Pseudo dual-port primitive
//Copyright (C)2014-2021 Gowin Semiconductor Corporation.
//All rights reserved.
//File Title: IP file
//GOWIN Version: V1.9.8.01
//Part Number: GW1N-LV4LQ144C6/I5
//Device: GW1N-4B
//Created Time: Sat Jan 22 12:41:55 2022
module Gowin_SDPB (dout, clka, cea, reseta, clkb, ceb, resetb, oce, ada, din, adb);
output [7:0] dout;
input clka;
input cea;
input reseta;
input clkb;
input ceb;
input resetb;
input oce;
input [9:0] ada;
input [7:0] din;
input [9:0] adb;
wire gw_gnd;
assign gw_gnd = 1'b0;
SDPB sdpb_inst_0 (
.DO(dout[7:0]),
.CLKA(clka),
.CEA(cea),
.RESETA(reseta),
.CLKB(clkb),
.CEB(ceb),
.RESETB(resetb),
.OCE(oce),
.BLKSELA({gw_gnd,gw_gnd,gw_gnd}),
.BLKSELB({gw_gnd,gw_gnd,gw_gnd}),
.ADA({ada[9:0],gw_gnd,gw_gnd,gw_gnd,gw_gnd}),
.DI({gw_gnd,gw_gnd,gw_gnd,gw_gnd,gw_gnd,gw_gnd,gw_gnd,gw_gnd,gw_gnd,gw_gnd,gw_gnd,gw_gnd,
gw_gnd,gw_gnd,gw_gnd,gw_gnd,gw_gnd,gw_gnd,gw_gnd,gw_gnd,gw_gnd,gw_gnd,gw_gnd,gw_gnd,din[7:0]}),
.ADB({adb[9:0],gw_gnd,gw_gnd,gw_gnd,gw_gnd})
);
defparam sdpb_inst_0.READ_MODE = 1'b0;
defparam sdpb_inst_0.BIT_WIDTH_0 = 8;
defparam sdpb_inst_0.BIT_WIDTH_1 = 8;
defparam sdpb_inst_0.BLK_SEL_0 = 3'b000;
defparam sdpb_inst_0.BLK_SEL_1 = 3'b000;
defparam sdpb_inst_0.RESET_MODE = "SYNC";
endmodule //Gowin_SDPB
|