3268 views|6 replies

270

Posts

0

Resources
The OP
 

[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

  1. Materials: Zhongjingyuan's 0.96- inch yellow-blue OLED display, the OLED driver chip is SSD1306 .
  2. 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

This post is from Domestic Chip Exchange

Latest reply

Good job!   Details Published on 2022-11-11 02:51
 
 

1412

Posts

3

Resources
2
 

Very good. The display effect is not bad

This post is from Domestic Chip Exchange
 
 
 

6773

Posts

2

Resources
3
 

FPGA programming is really difficult, the OP is really awesome!

This post is from Domestic Chip Exchange

Comments

Also refer to the procedures of other big guys   Details Published on 2022-1-22 15:57
 
 
 

270

Posts

0

Resources
4
 
wangerxian posted on 2022-1-22 15:49 FPGA programming is really difficult, the OP is really awesome!

Also refer to the procedures of other big guys

This post is from Domestic Chip Exchange

Comments

It’s very good if you can understand it. What you understand is yours!  Details Published on 2022-1-23 09:03
 
 
 

664

Posts

104

Resources
5
 

The effect is very good

This post is from Domestic Chip Exchange
 
 
 

6773

Posts

2

Resources
6
 
Posted with a teenage dream on 2022-1-22 15:57 Also refer to the programs of other big guys

It’s very good if you can understand it. What you understand is yours!

This post is from Domestic Chip Exchange
 
 
 

6

Posts

2

Resources
7
 

Good job!

This post is from Domestic Chip Exchange
 
 
 

Find a datasheet?

EEWorld Datasheet Technical Support

Related articles more>>

EEWorld
subscription
account

EEWorld
service
account

Automotive
development
circle

Copyright © 2005-2024 EEWORLD.com.cn, Inc. All rights reserved 京B2-20211791 京ICP备10001474号-1 电信业务审批[2006]字第258号函 京公网安备 11010802033920号
快速回复 返回顶部 Return list