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

来源:互联网 发布:鹰视眼监控软件app 编辑:程序博客网 时间:2024/05/16 10:53

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)beginif(!rst_n)begincnt <= 28'd0;num <= 16'd43400;endelseif(cnt == 28'h4C4B40)beginnum <= num + 1'b1;cnt <= 28'd0;endelseif(num == 16'd45000)num <= 16'd44000;elsecnt <= 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)beginif(!rst_n)beginge  <= 4'd0;shi <= 4'd0;bai <= 4'd0;qian<= 4'd0;wan <= 4'd0;endelsebeginge  <= num % 10;shi <= num / 10    % 10;bai <= num / 100   % 10;qian<= num / 1000  % 10;wan <= num / 10000;endend
目的:很显然是为了实现个、十、百、千、万的取值,分别显示在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; endelsebegin 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; endelsebegin 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; endelsebegin data <= {2'b00,4'h0,4'h0}; start <= 1'b1; end8'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; endelse if(spi_write_done)begin start <= 1'b0; x <= x + 1'b1; endelsebegin 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; endelsebegin 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; endelsebegin 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; endelsebegin 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; endelse if(spi_write_done)begin start <= 1'b0; x <= x + 1'b1; endelsebegin data <= {2'b01,rom_data}; start <= 1'b1; end6'd8:begin y <= 4'h0; j <= 6'd0;  i <= i + 1'b1; endendcase
注释:

这段代码跟清零代码基本是相似的,因为他们的执行原理和过程是一样的。不同的是,清零的时候是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; endelsebegin 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; endelsebegin 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; endelsebegin 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; endelse if(spi_write_done)begin start <= 1'b0; x <= x + 1'b1; endelsebegin data <= {2'b01,rom_data}; start <= 1'b1; end6'd8:begin y <= 4'h0; j <= 6'd0; i <= i + 1'b1; endendcasedefault: ;endcase8'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; end8'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的工程压缩包可以在前面的文章中找下载链接。

原创粉丝点击