1404|6

90

帖子

0

资源

一粒金砂(中级)

【国产FPGA高云GW1N-4系列开发板测评】OLED显示 [复制链接]

 

很久没有使用FPGA玩耍了,前段时间一直忙着搬家以及入职新公司的事情,这几天才时间来玩一下高云FPGA,现在使用高云FPGA驱动OLED。先来实验现象

0.jpg

1.jpg

一、硬件

  1. 材料:中景园的0.96寸黄蓝OLED显示屏,OLED驱动芯片是SSD1306
  2. OLED介绍

OLED引脚,7SPI模式。

1). GND 电源地

2). VCC 电源正(35.5V

3). D0 OLED D0 脚,在 SPI IIC 通信中为时钟管脚

4). D1 OLED D1 脚,在 SPI IIC 通信中为数据管脚

5). RES OLED RES#脚,用来复位(低电平复位)

6. DC OLED D/C#E 脚,数据和命令控制管脚

3OLED显示的工作原理

OLED内部有一个GDDRAM,大小为128*64 bits RAM是被分成了8页,从page0page7,使用这种形式驱动点阵列。如图

工作原理1.png

当一个字节数据被写入GDDRAM,所有行数据是被当前页的列(8位)数据填满,低位在在顶部,高位在底部,如图。故因此只需要使用伪双端口去进行读写数据,读写分开。

工作原理2.png

二、软件

程序基本是模拟SPI驱动OLED的时序,时钟为1M

代码如下:

   1)时钟分频

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

 

         2spi写时序

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
 
 

         3OLED初始化

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)读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)写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

6OLED顶层模块

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)伪双端口原语

//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

        


回复

1485

帖子

3

资源

版主

很赞很赞。显示效果还不错


回复

2386

帖子

0

资源

版主

FPGA编程着实有点难,楼主厉害厉害!

点评

也是参考其他大佬的程序    详情 回复 发表于 2022-1-22 15:57

回复

90

帖子

0

资源

一粒金砂(中级)

wangerxian 发表于 2022-1-22 15:49 FPGA编程着实有点难,楼主厉害厉害!

也是参考其他大佬的程序

 

点评

能看懂就非常不错了,看懂了的东西,那就是自己的!  详情 回复 发表于 2022-1-23 09:03

回复

696

帖子

108

资源

纯净的硅(中级)

效果很不错 


回复

2386

帖子

0

资源

版主

怀揣少年梦 发表于 2022-1-22 15:57 也是参考其他大佬的程序  

能看懂就非常不错了,看懂了的东西,那就是自己的!


回复

6

帖子

2

资源

一粒金砂(中级)

Good job! Pro


回复
您需要登录后才可以回帖 登录 | 注册

相关帖子
查找数据手册?

EEWorld Datasheet 技术支持

相关文章 更多>>
    推荐帖子
    【主题月】一光伏并网逆变器防浪涌的问题

    背景描述:这是一个小功率的单相光伏并网逆变器,在市网大面积停电后再次上电时,会烧坏图中的电阻R1(紫色虚线内的是重点),进行 ...

    出售一块Digilent Basys3 FPGA开发板,800元

    本人在美国读书,因为学习需要购入了一块Basys3 FPGA开发板。但由于平时不是主要做FPGA的,该开发板闲置,打算出掉。板子99新, ...

    一文详解Via孔的作用及原理

    http://www.pcbbbs.com/data/attachment/forum/201812/03/171823s4x8ggutgcgq8y8p.png 如图所示在走线的 Via 孔附近加接地 ...

    TI LAUNCHXL-CC1352P测评评选结果公布

    LAUNCHXL-CC1352P测评活动链接:http://bbs.eeworld.com.cn/elecplay/content/114 本次测评活动参与测评的网友及其作品: ...

    连载的电视剧不能换演员,一换就变成其他片子了

    好好的一部【将夜】,看完第一部,觉得蛮好看的,期待着看第二部,结果一下子换了很多演员,主角和关键人物都换人了,完全没有原 ...

    单车改装系列:码表

    本帖最后由 dcexpert 于 2021-10-19 12:06 编辑 没有码表的单车是不完整的,所以就配了一个码表。GPS码表虽说功能多效果好, ...

    关闭
    站长推荐上一条 1/6 下一条

    About Us 关于我们 客户服务 联系方式 器件索引 网站地图 最新更新 手机版

    站点相关: 安防电子 汽车电子 手机便携 工业控制 家用电子 医疗电子 测试测量 网络通信 物联网

    北京市海淀区知春路23号集成电路设计园量子银座1305 电话:(010)82350740 邮编:100191

    电子工程世界版权所有 京B2-20211791 京ICP备10001474号-1 电信业务审批[2006]字第258号函 京公网安备 11010802033920号 Copyright © 2005-2022 EEWORLD.com.cn, Inc. All rights reserved
    快速回复 返回顶部 返回列表