FPGA驱动OLED动态显示(Verilog代码)——向OLED写数据(关键)

1、代码太长贴到了‘代码笔记’里,链接:向OLED写数据write_data.v

这段代码实现的是:Freq:xxxxxHz; x是动态数据,取模软件PCtoLCD2002设置:宋体,字宽16,字高16,逆向取模,列行式;实际得到的字模是8x16的,对应到OLED上是8列16行;

2、部分释义:

2.1、

reg [27:0] cnt;
	reg [15:0]  num;
	always @(posedge clk_1m or negedge rst_n)
	begin
		if(!rst_n)
		begin
			cnt <= 28'd0;
			num <= 16'd43400;
		end
		else
			if(cnt == 28'h4C4B40)
			begin
				num <= num + 1'b1;
				cnt <= 28'd0;
			end
			else
				if(num == 16'd45000)
					num <= 16'd44000;
				else
					cnt <= cnt + 1'b1;				
	end

目的:产生43400-45000计数,做测试用,实际中‘num’是由其他模块采集进来的数据;

2.2、

	reg [3:0] ge;
	reg [3:0] shi;
	reg [3:0] bai;
	reg [3:0] qian;
	reg [3:0] wan;
	always @(posedge clk_1m or negedge rst_n)
	begin
		if(!rst_n)
		begin
			ge  <= 4'd0;
			shi <= 4'd0;
			bai <= 4'd0;
			qian<= 4'd0;
			wan <= 4'd0;
		end
		else
		begin
			ge  <= num % 10;
			shi <= num / 10    % 10;
			bai <= num / 100   % 10;
			qian<= num / 1000  % 10;
			wan <= num / 10000;
		end
	end
目的:很显然是为了实现个、十、百、千、万的取值,分别显示在OLED的不同位置上。

2.3、

//----------------------------------------------------
//清屏
 8'd0,8'd4,8'd8,8'd12,8'd16,8'd20,8'd24,8'd28:
	if(spi_write_done) 
		begin start <= 1'b0; i <= i + 1'b1; end
	else
		begin data <= {2'b00,4'hb,y}; start <= 1'b1; end
//列高位		
8'd1,8'd5,8'd9,8'd13,8'd17,8'd21,8'd25,8'd29:
	if(spi_write_done)
		begin start <= 1'b0; i <= i + 1'b1; end
	else
		begin data <= {2'b00,4'h1,4'h0}; start <= 1'b1; end
//列低位
	6'd2,8'd6,8'd10,8'd14,8'd18,8'd22,8'd26,8'd30:
	if(spi_write_done)
		begin start <= 1'b0; i <= i + 1'b1; end
	else
		begin data <= {2'b00,4'h0,4'h0}; start <= 1'b1; end
8'd3,8'd7,8'd11,8'd15,8'd19,8'd23,8'd27,8'd31:
	if(x==8'd128)
		begin y <= y + 1'b1; x <= 8'd0; i <= i + 1'b1; end
	else 
	if(spi_write_done)
		begin start <= 1'b0; x <= x + 1'b1; end
	else	
		begin data <= {2'b01,8'd00}; start <= 1'b1; end  
//---------------------------------------------------------------

目的:很重要的一端代码,其目的是为了实现清屏;

x:表示列=128列,先写高4位,再写第4位;高低地址都用8bit表示,高地址命令是0x1?;地地址命令是0x0?;

y:表示页=8页,8'hb0~8'hb7=第0页~第7页;

e.g.:如果想在第3页的第29列开始显示数据,那么需要写入的数据为:页:data <={2'b00,8'hb2};列高地址:data<={2'b00,4'h1,4'h1},列地4位:data<={2'b00,4'h0,4'hD},前两位2'b00是CS和RES位,29=8'h1D;

i=0~3:填充0页的128列,i= 4~7填充1页的128列,以此类推。

注明:开始向OLED写数据时建议先清屏,我在实际操作过程中,发现如果没有清零步骤的话原先写过的位置还是会显示。

2.4、

8'd32:
		begin y <= 4'h0; i <= i + 1'b1; end
//----------------------------------------------------------------------------	
8'd33:
		begin F_flag <= 1'b1; i <= i + 1'b1; end

注释:8'd32步是将页地址寄存器y的值清零;清零完之后y的值会等于7,如果没有第32步实际效果会从OLED的第7页开始显示,而我们是想让它从0页开始显示;

注释:第33步开始显示字符‘F’,但必须得有一个标志位F_flag,目的是管理使用ROM的次序,毕竟有那么多字符都共用一个ROM,不管理怎么能行。

2.5、

8'd34:	//'F'
	case(j)
//设置页地址			
	6'd0,6'd4:
	if(spi_write_done) 
		begin start <= 1'b0; j <= j + 1'b1; end
	else
		begin data <= {2'b00,4'hb,y}; start <= 1'b1; end
//高地址	
	6'd1,6'd5:
	if(spi_write_done)
		begin start <= 1'b0; j <= j + 1'b1; end
	else
		begin data <= {2'b00,4'h1,4'h0}; start <= 1'b1; end
//低地址	
	6'd2,6'd6:
	if(spi_write_done)
		begin start <= 1'b0; j <= j + 1'b1; end
	else
		begin data <= {2'b00,4'h0,4'h0}; start <= 1'b1; end
//填充8次		
	6'd3,6'd7:
	if(x==8'd7)
		begin y <= y + 1'b1; x <= 8'd0; j <= j + 1'b1; end
	else 
	if(spi_write_done)
		begin start <= 1'b0; x <= x + 1'b1; end
	else	
		begin data <= {2'b01,rom_data}; start <= 1'b1; end
	6'd8:
		begin y <= 4'h0; j <= 6'd0;  i <= i + 1'b1; end
	endcase
注释:

这段代码跟清零代码基本是相似的,因为他们的执行原理和过程是一样的。不同的是,清零的时候是if(x==127),因为是要填充整整一页,那x就是127;这里的代码是if(x==7),原因是这个字符‘F’取模时只占用了OLED的8列,16行,所以x只需要填充8列即可,填充完之后就开始换页。‘F’占用了第0页第1页。

这段代码实现字符‘F’,从0页的0列开始显示,F的页以及高低地址设置都是0;

2.6、

8'd35:
	begin F_flag <= 1'b0; r_flag <= 1'b1; i <= i + 1'b1; end 
注释:这段代码目的是结束字符‘F’占用ROM,F_flag=0,开始让字符‘r’使用ROM,因为下面要开始显示字符‘r’了;

在每个字符显示完之后都要结束当前ROM占用,释放给下一个字符;字符的显示代码跟字符‘F’的代码除了高低地址不同,其他不变。

2.7、

当显示动态数字时有所变化。以显示个位为列,其他位置的数字显示跟个位显示相同。

8'd47:
begin z_flag <= 1'b0; ge_flag <= 1'b1; i <= i + 1'b1; end 	
8'd48:
	case(ge)	
	4'd0,4'd1,4'd2,4'd3,4'd4,4'd5,4'd6,4'd7,4'd8,4'd9:
		case(j)
//设置页地址			
		6'd0,6'd4:
		if(spi_write_done) 
			begin start <= 1'b0; j <= j + 1'b1; end
		else
			begin data <= {2'b00,4'hb,y}; start <= 1'b1; end
//高地址	
		6'd1,6'd5:
		if(spi_write_done)
			begin start <= 1'b0; j <= j + 1'b1; end
		else
			begin data <= {2'b00,4'h1,4'h4}; start <= 1'b1; end
//低地址	
		6'd2,6'd6:
		if(spi_write_done)
			begin start <= 1'b0; j <= j + 1'b1; end
		else
			begin data <= {2'b00,4'h0,4'h6}; start <= 1'b1; end
//填充8次		
		6'd3,6'd7:
		if(x==8'd7)
			begin y <= y + 1'b1; x <= 8'd0; j <= j + 1'b1; end
		else 
		if(spi_write_done)
			begin start <= 1'b0; x <= x + 1'b1; end
		else	
			begin data <= {2'b01,rom_data}; start <= 1'b1; end
		6'd8:
			begin y <= 4'h0; j <= 6'd0; i <= i + 1'b1; end
		endcase
	default: ;
	endcase
8'd49:
	begin ge_flag <= 1'b0; shi_flag <= 1'b1; i <= i + 1'b1; end


注释:

个位0-9的数字显示代码相同,共用一段代码;十位0-9的数字显示跟个位的代码相同,也是共用,但是要修改一些列地址,因为十位跟个位的显示位置不同,百、十、千、万的显示也是如此。注意列地址。

2.8、

8'd57:
	begin wan_flag <= 1'b0;  i <= i + 1'b1; end			
8'd58:
	i <= 6'd47;

当显示完所有的数据和字符后,要有第58步骤,转到第47步,看看第47步是不是ge_flag开始的地方,意思就是数字位循环更新显示,那不就是动态实时了吗。

源码中的第59、60步可以忽略,我没删掉。

2.9:、

assign rom_addr = 	F_flag  ? (y ? (x + 8'd168) : (x + 8'd160)) : 
			(r_flag ? (y ? (x + 8'd184) : (x + 8'd176)) : 
			(e_flag ? (y ? (x + 8'd200) : (x + 8'd192)) : 
			(q_flag ? (y ? (x + 8'd216) : (x + 8'd208)) : 
			(maohao_flag ? (y ? (x + 8'd232) : (x + 8'd224)) :
			(H_flag ? (y ? (x + 8'd248) : (x + 8'd240))	:
			(z_flag ? (y ? (x + 12'd264) : (x + 12'd256))	:
			(ge_flag   ? (y ?  (x + (ge   << 4) + 4'd8) : (x + (ge   << 4))): 
			(shi_flag  ? (y ?  (x + (shi  << 4) + 4'd8) : (x + (shi  << 4))):
			(bai_flag  ? (y ?  (x + (bai  << 4) + 4'd8) : (x + (bai  << 4))):
			(qian_flag ? (y ?  (x + (qian << 4) + 4'd8) : (x + (qian << 4))):
			(y ? (x + (wan << 4) + 4'd8) : (x + (wan << 4))))))))))))); 
注释:这里就是通过标志位x_flag对ROM的管理,嵌套的‘ ? :’语句,清屏时是直接写入的0,没有调用ROM,也就没有定义clear_flag;

注释:敲黑板的时候到了。

首先说一说ROM。根据OLED的显示特点:共8页,每页128列,每列8bit。因此ROM设置为:8bit*1024。

ROM中的数据是先写入0-9的字模值,再写‘F’‘r’‘e’‘q’‘:’‘H’‘z’的字模。

e.g:字符‘F’的字模是:{0x08,0xF8,0x88,0x88,0xE8,0x08,0x10,0x00,
    0x20,0x3F,0x20,0x00,0x03,0x00,0x00,0x00}。

在ROM中填入:

地址160对应‘F’的上半部分字模,也就是第0页的显示,起地址168对应‘F’的下半部分字模,也就是第1页的显示。所以F_flag  ? (y ? (x + 8'd168) : (x + 8'd160))这句话的意思是:F_flag=1时表示现在要显示字符‘F’,然后判断y值,y=0时表示正在显示第0页,那么x+8'd160,x=0~7,刚好把0页的8列数据显示完毕,这是y+1=>y=1,表示要显示第1页,那么x+8'd168,x=0~7,刚好把第1页的8列数据显示完。显示完之后F_flag就置0了,前面已经说过这个flag置1置0的意义。接下来r_flag就置1了,就开始执行这句话:(r_flag ? (y ? (x + 8'd184) : (x + 8'd176)),x在这里加的8'd184和8'd176是因为字符‘r’的字模就放在ROM的这个起地址处,以此类推,x要加的数取决于你把字符字模数据放在了ROM的哪个地址。字符的rom_addr管理都可以这样理解。

数字的rom_addr管理稍有不同。

以‘个’位的显示为列:

注:个、十、百、千、万位的数字显示都是共用ROM中的0-9字模;

注:我将0-9的字模在ROM的起始地址0处依次写入;

(ge_flag   ? (y ?  (x + (ge   << 4) + 4'd8) : (x + (ge   << 4)));y=0时表示显示第0页,开始执行代码 (x + (ge   << 4))

假设程序case(ge)时判断此时ge=1,这时要显示‘1’所对应的字模数据,‘1’的第0页字模起始地址是16,第1页起始地址是24,(x + (ge   << 4) = x + (1*16) = x+16(x=0~7),这样是不是依次把‘1’的第0页也就是上半部分的字模数据从ROM里取走了。同样y=1时,表明要显示第1页的字模数据了,执行x + (ge   << 4) + 4'd8,即x+16+8=x+24(x=0~7),这样是不是依次把‘1’的下半部分的字模数据从ROM里取走了。其他的十、百、千、万位的ROM管理跟个位的管理是相同的。可以对照着下面ROM里的字模内容计算一下。

到这里FPGA驱动OLED实现动态实时显示的关键部分就说完了。这个Demo的工程压缩包可以在前面的文章中找下载链接。

posted @ 2017-10-23 10:38  王纯配  阅读(1058)  评论(0编辑  收藏  举报