一、概述:
首先展示一下成果图,使用RAM的读写来完成,下面依次介绍各个模块
二、OLED显示原理(部分)
oled分为7页,每一页有128个字节用来显示
首先先设置页地址,然后设置列的低地址和高地址(这里不是很理解,设置扫描方向吗?)
设置好后依次写入128个显示数据,完成一页的显示,如果你的字模为8*8的,那这样就能完整显示出一个字了(如下图x一样)
但是如果字体的高度大于8位怎么办了?
就需要通过两页或者多页来进行显示了,字体被分成两半或者多半,然后在两页或者多页显示拼接出来(有时候会出现错位的问题,那就要注意写进去数据的位置了)
三、RAM IP核的创建
1、使用双端口的RAM,读写地址分开
2、使用1024个字节
3、勾选读使能信号
4、取消勾选(这个好像会让输出延时一个时钟)
5、没有提到的步骤就一路next然后finsh
四、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
这个模块就是重复的写命令,读数据和写数据,写完3个命令,读1个数据,写1个数据,然后继续读写,直到写完128个数据,然后开始下一次的写命令,读数据和写数据直到1024个数据全部写完,然后等待100ms开始下一次读写。这个和之前初始化里面的读写是一样的,只是加入RAM的读数据,也就是在写数据前增加一次地址,读取一次数据,读使能信号可以一直拉高的。因为地址没有改变,读出的数据也是保持不变的。
五、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;//数据计数器
//字模数据初始化 字号大小16
initial begin
zm[0]=8'h07;zm[1]=8'hF0;zm[2]=8'h08;zm[3]=8'h08;
zm[4]=8'h10;zm[5]=8'h04;zm[6]=8'h10;zm[7]=8'h04;
zm[8]=8'h10;zm[9]=8'h04;zm[10]=8'h08;zm[11]=8'h08;
zm[12]=8'h07;zm[13]=8'hF0;zm[14]=8'h00;zm[15]=8'h00;//"O",0
zm[16]=8'h10;zm[17]=8'h04;zm[18]=8'h1F;zm[19]=8'hFC;
zm[20]=8'h10;zm[21]=8'h04;zm[22]=8'h00;zm[23]=8'h04;
zm[24]=8'h00;zm[25]=8'h04;zm[26]=8'h00;zm[27]=8'h04;
zm[28]=8'h00;zm[29]=8'h0C;zm[30]=8'h00;zm[31]=8'h00;//"L",1
zm[32]=8'h10;zm[33]=8'h04;zm[34]=8'h1F;zm[35]=8'hFC;
zm[36]=8'h11;zm[37]=8'h04;zm[38]=8'h11;zm[39]=8'h04;
zm[40]=8'h17;zm[41]=8'hC4;zm[42]=8'h10;zm[43]=8'h04;
zm[44]=8'h08;zm[45]=8'h18;zm[46]=8'h00;zm[47]=8'h00;//"E",2
zm[48]=8'h10;zm[49]=8'h04;zm[50]=8'h1F;zm[51]=8'hFC;
zm[52]=8'h10;zm[53]=8'h04;zm[54]=8'h10;zm[55]=8'h04;
zm[56]=8'h10;zm[57]=8'h04;zm[58]=8'h08;zm[59]=8'h08;
zm[60]=8'h07;zm[61]=8'hF0;zm[62]=8'h00;zm[63]=8'h00;//"D",3
zm[64]=8'h00;zm[65]=8'h02;zm[66]=8'h00;zm[67]=8'h42;
zm[68]=8'h00;zm[69]=8'h22;zm[70]=8'h7F;zm[71]=8'h1A;
zm[72]=8'h49;zm[73]=8'h02;zm[74]=8'h49;zm[75]=8'hFE;
zm[76]=8'h49;zm[77]=8'h02;zm[78]=8'h49;zm[79]=8'h02;
zm[80]=8'h49;zm[81]=8'h02;zm[82]=8'h49;zm[83]=8'hFE;
zm[84]=8'h49;zm[85]=8'h02;zm[86]=8'h7F;zm[87]=8'h0A;
zm[88]=8'h00;zm[89]=8'h12;zm[90]=8'h00;zm[91]=8'h62;
zm[92]=8'h00;zm[93]=8'h02;zm[94]=8'h00;zm[95]=8'h00;//"显",4
zm[96]=8'h02;zm[97]=8'h04;zm[98]=8'h02;zm[99]=8'h08;
zm[100]=8'h42;zm[101]=8'h10;zm[102]=8'h42;zm[103]=8'h60;
zm[104]=8'h42;zm[105]=8'h00;zm[106]=8'h42;zm[107]=8'h02;
zm[108]=8'h42;zm[109]=8'h01;zm[110]=8'h43;zm[111]=8'hFE;
zm[112]=8'h42;zm[113]=8'h00;zm[114]=8'h42;zm[115]=8'h00;
zm[116]=8'h42;zm[117]=8'h00;zm[118]=8'h42;zm[119]=8'h40;
zm[120]=8'h42;zm[121]=8'h20;zm[122]=8'h02;zm[123]=8'h10;
zm[124]=8'h02;zm[125]=8'h0C;zm[126]=8'h00;zm[127]=8'h00;//"示",5
zm[128]=8'h10;zm[129]=8'h04;zm[130]=8'h1F;zm[131]=8'hFC;
zm[132]=8'h11;zm[133]=8'h04;zm[134]=8'h11;zm[135]=8'h04;
zm[136]=8'h11;zm[137]=8'h04;zm[138]=8'h0E;zm[139]=8'h88;
zm[140]=8'h00;zm[141]=8'h70;zm[142]=8'h00;zm[143]=8'h00;//"B",6
zm[144]=8'h01;zm[145]=8'h00;zm[146]=8'h01;zm[147]=8'h81;
zm[148]=8'h01;zm[149]=8'h61;zm[150]=8'h00;zm[151]=8'h1E;
zm[152]=8'h00;zm[153]=8'h18;zm[154]=8'h01;zm[155]=8'h60;
zm[156]=8'h01;zm[157]=8'h80;zm[158]=8'h01;zm[159]=8'h00;//"y",7
zm[160]=8'h20;zm[161]=8'h80;zm[162]=8'h28;zm[163]=8'h91;
zm[164]=8'h35;zm[165]=8'h11;zm[166]=8'hE2;zm[167]=8'h12;
zm[168]=8'h25;zm[169]=8'h14;zm[170]=8'h28;zm[171]=8'h98;
zm[172]=8'h30;zm[173]=8'h70;zm[174]=8'h00;zm[175]=8'h90;
zm[176]=8'h38;zm[177]=8'h90;zm[178]=8'h25;zm[179]=8'h12;
zm[180]=8'h22;zm[181]=8'h11;zm[182]=8'h25;zm[183]=8'h12;
zm[184]=8'h29;zm[185]=8'h1C;zm[186]=8'h30;zm[187]=8'h80;
zm[188]=8'h00;zm[189]=8'h80;zm[190]=8'h00;zm[191]=8'h00;//"努",8
zm[192]=8'h00;zm[193]=8'h00;zm[194]=8'h08;zm[195]=8'h01;
zm[196]=8'h08;zm[197]=8'h02;zm[198]=8'h08;zm[199]=8'h04;
zm[200]=8'h08;zm[201]=8'h18;zm[202]=8'h08;zm[203]=8'h60;
zm[204]=8'hFF;zm[205]=8'h80;zm[206]=8'h08;zm[207]=8'h00;
zm[208]=8'h08;zm[209]=8'h04;zm[210]=8'h08;zm[211]=8'h02;
zm[212]=8'h08;zm[213]=8'h01;zm[214]=8'h08;zm[215]=8'h02;
zm[216]=8'h0F;zm[217]=8'hFC;zm[218]=8'h00;zm[219]=8'h00;
zm[220]=8'h00;zm[221]=8'h00;zm[222]=8'h00;zm[223]=8'h00;//"力",9
zm[224]=8'h00;zm[225]=8'h00;zm[226]=8'h1F;zm[227]=8'hFF;
zm[228]=8'h10;zm[229]=8'h00;zm[230]=8'h10;zm[231]=8'h00;
zm[232]=8'h30;zm[233]=8'h00;zm[234]=8'h53;zm[235]=8'hF8;
zm[236]=8'h92;zm[237]=8'h10;zm[238]=8'h12;zm[239]=8'h10;
zm[240]=8'h12;zm[241]=8'h10;zm[242]=8'h13;zm[243]=8'hF8;
zm[244]=8'h10;zm[245]=8'h00;zm[246]=8'h10;zm[247]=8'h02;
zm[248]=8'h10;zm[249]=8'h01;zm[250]=8'h1F;zm[251]=8'hFE;
zm[252]=8'h00;zm[253]=8'h00;zm[254]=8'h00;zm[255]=8'h00;//"向",10
zm[256]=8'h10;zm[257]=8'h00;zm[258]=8'h10;zm[259]=8'h00;
zm[260]=8'h17;zm[261]=8'hFF;zm[262]=8'h94;zm[263]=8'h90;
zm[264]=8'h74;zm[265]=8'h92;zm[266]=8'h14;zm[267]=8'h91;
zm[268]=8'h17;zm[269]=8'hFE;zm[270]=8'h10;zm[271]=8'h00;
zm[272]=8'h10;zm[273]=8'h00;zm[274]=8'h13;zm[275]=8'hF0;
zm[276]=8'h30;zm[277]=8'h02;zm[278]=8'hD0;zm[279]=8'h01;
zm[280]=8'h17;zm[281]=8'hFE;zm[282]=8'h10;zm[283]=8'h00;
zm[284]=8'h10;zm[285]=8'h00;zm[286]=8'h00;zm[287]=8'h00;//"前",11
zm[288]=8'h00;zm[289]=8'h00;zm[290]=8'h1F;zm[291]=8'hFE;
zm[292]=8'h30;zm[293]=8'h84;zm[294]=8'hD0;zm[295]=8'h84;
zm[296]=8'h10;zm[297]=8'h84;zm[298]=8'h10;zm[299]=8'h84;
zm[300]=8'h1F;zm[301]=8'hFE;zm[302]=8'h02;zm[303]=8'h00;
zm[304]=8'h0C;zm[305]=8'h00;zm[306]=8'hF1;zm[307]=8'h00;
zm[308]=8'h10;zm[309]=8'hC2;zm[310]=8'h10;zm[311]=8'h01;
zm[312]=8'h10;zm[313]=8'h02;zm[314]=8'h1F;zm[315]=8'hFC;
zm[316]=8'h00;zm[317]=8'h00;zm[318]=8'h00;zm[319]=8'h00;//"的",12
zm[320]=8'h00;zm[321]=8'h10;zm[322]=8'h00;zm[323]=8'h20;
zm[324]=8'h00;zm[325]=8'hC0;zm[326]=8'h07;zm[327]=8'h00;
zm[328]=8'h00;zm[329]=8'h00;zm[330]=8'h00;zm[331]=8'h02;
zm[332]=8'h00;zm[333]=8'h01;zm[334]=8'hFF;zm[335]=8'hFE;
zm[336]=8'h00;zm[337]=8'h00;zm[338]=8'h00;zm[339]=8'h00;
zm[340]=8'h00;zm[341]=8'h00;zm[342]=8'h04;zm[343]=8'h00;
zm[344]=8'h02;zm[345]=8'h00;zm[346]=8'h01;zm[347]=8'h80;
zm[348]=8'h00;zm[349]=8'h70;zm[350]=8'h00;zm[351]=8'h00;//"小",13
zm[352]=8'h08;zm[353]=8'h80;zm[354]=8'h11;zm[355]=8'h00;
zm[356]=8'h23;zm[357]=8'hFF;zm[358]=8'hCC;zm[359]=8'h00;
zm[360]=8'h00;zm[361]=8'h04;zm[362]=8'h04;zm[363]=8'h88;
zm[364]=8'h08;zm[365]=8'hB0;zm[366]=8'h14;zm[367]=8'h82;
zm[368]=8'h24;zm[369]=8'h81;zm[370]=8'hC7;zm[371]=8'hFE;
zm[372]=8'h24;zm[373]=8'h80;zm[374]=8'h14;zm[375]=8'hA0;
zm[376]=8'h08;zm[377]=8'h90;zm[378]=8'h04;zm[379]=8'h8C;
zm[380]=8'h04;zm[381]=8'h00;zm[382]=8'h00;zm[383]=8'h00;//"徐",14
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+48; //进入第3页
9'd129: wraddress<=10'd384+48; //进入第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
这个模块需要注意的就是什么时候换页(这个看你自己的需求),然后就是注意换页后应该从RAM的哪一个地址开始写,可以加入一些地址偏移量,每一页不同的位置显示,但是如果两页或多页显示一个字的话就需要注意地址对齐,不然字体会错位。
在顶层模块里面例化这几个模块,然后连接模块(注意信号的输入只能有一个来源)这样就可以实现上面最开始的效果了
顶层模块代码
module oled_drive(
input clk, //时钟信号 50MHz
input rst_n, //按键复位
input ram_rst, //ram复位 高电平复位
output oled_rst, //oled res 复位信号
output oled_dc, //oled dc 0:写命令 1:写数据
output oled_sclk, //oled do 时钟信号
output oled_mosi //oled d1 数据信号
);
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)
);
//ram ip核
ram_show ram_show_inst(
.clock(clk_1m),
.aclr(!ram_rst),
.data(wrdata),
.rdaddress(rdaddress),
.rden(rden),
.wraddress(wraddress),
.wren(wren),
.q(ram_data)
);
endmodule
下面介绍字模的生成和一个不用手打数组下标的方法(java语言 菜鸡随便写的)
六、字模生成
使用PCtoLCD2002生成字模
设置生成选项
选择16*16,生成字模并保存字模
使用下面的java代码填充数组下标(把str里面的字符串换成你的就好了)
public static void main(String[] args) {
StringBuffer str = new StringBuffer("zm[]=8'h07;zm[]=8'hF0;zm[]=8'h08;zm[]=8'h08;zm[]=8'h10;zm[]=8'h04;zm[]=8'h10;zm[]=8'h04;zm[]=8'h10;zm[]=8'h04;zm[]=8'h08;zm[]=8'h08;zm[]=8'h07;zm[]=8'hF0;zm[]=8'h00;zm[]=8'h00;\"O\",0\r\n"
+ "zm[]=8'h10;zm[]=8'h04;zm[]=8'h1F;zm[]=8'hFC;zm[]=8'h10;zm[]=8'h04;zm[]=8'h00;zm[]=8'h04;zm[]=8'h00;zm[]=8'h04;zm[]=8'h00;zm[]=8'h04;zm[]=8'h00;zm[]=8'h0C;zm[]=8'h00;zm[]=8'h00;\"L\",1\r\n"
+ "zm[]=8'h10;zm[]=8'h04;zm[]=8'h1F;zm[]=8'hFC;zm[]=8'h11;zm[]=8'h04;zm[]=8'h11;zm[]=8'h04;zm[]=8'h17;zm[]=8'hC4;zm[]=8'h10;zm[]=8'h04;zm[]=8'h08;zm[]=8'h18;zm[]=8'h00;zm[]=8'h00;\"E\",2\r\n"
+ "zm[]=8'h10;zm[]=8'h04;zm[]=8'h1F;zm[]=8'hFC;zm[]=8'h10;zm[]=8'h04;zm[]=8'h10;zm[]=8'h04;zm[]=8'h10;zm[]=8'h04;zm[]=8'h08;zm[]=8'h08;zm[]=8'h07;zm[]=8'hF0;zm[]=8'h00;zm[]=8'h00;\"D\",3\r\n"
+ "zm[]=8'h00;zm[]=8'h02;zm[]=8'h00;zm[]=8'h42;zm[]=8'h00;zm[]=8'h22;zm[]=8'h7F;zm[]=8'h1A;zm[]=8'h49;zm[]=8'h02;zm[]=8'h49;zm[]=8'hFE;zm[]=8'h49;zm[]=8'h02;zm[]=8'h49;zm[]=8'h02;zm[]=8'h49;zm[]=8'h02;zm[]=8'h49;zm[]=8'hFE;zm[]=8'h49;zm[]=8'h02;zm[]=8'h7F;zm[]=8'h0A;\r\n"
+ "zm[]=8'h00;zm[]=8'h12;zm[]=8'h00;zm[]=8'h62;zm[]=8'h00;zm[]=8'h02;zm[]=8'h00;zm[]=8'h00;\"显\",4\r\n"
+ "zm[]=8'h02;zm[]=8'h04;zm[]=8'h02;zm[]=8'h08;zm[]=8'h42;zm[]=8'h10;zm[]=8'h42;zm[]=8'h60;zm[]=8'h42;zm[]=8'h00;zm[]=8'h42;zm[]=8'h02;zm[]=8'h42;zm[]=8'h01;zm[]=8'h43;zm[]=8'hFE;zm[]=8'h42;zm[]=8'h00;zm[]=8'h42;zm[]=8'h00;zm[]=8'h42;zm[]=8'h00;zm[]=8'h42;zm[]=8'h40;\r\n"
+ "zm[]=8'h42;zm[]=8'h20;zm[]=8'h02;zm[]=8'h10;zm[]=8'h02;zm[]=8'h0C;zm[]=8'h00;zm[]=8'h00;\"示\",5\r\n"
+ "zm[]=8'h10;zm[]=8'h04;zm[]=8'h1F;zm[]=8'hFC;zm[]=8'h11;zm[]=8'h04;zm[]=8'h11;zm[]=8'h04;zm[]=8'h11;zm[]=8'h04;zm[]=8'h0E;zm[]=8'h88;zm[]=8'h00;zm[]=8'h70;zm[]=8'h00;zm[]=8'h00;\"B\",6\r\n"
+ "zm[]=8'h01;zm[]=8'h00;zm[]=8'h01;zm[]=8'h81;zm[]=8'h01;zm[]=8'h61;zm[]=8'h00;zm[]=8'h1E;zm[]=8'h00;zm[]=8'h18;zm[]=8'h01;zm[]=8'h60;zm[]=8'h01;zm[]=8'h80;zm[]=8'h01;zm[]=8'h00;\"y\",7\r\n"
+ "zm[]=8'h20;zm[]=8'h80;zm[]=8'h28;zm[]=8'h91;zm[]=8'h35;zm[]=8'h11;zm[]=8'hE2;zm[]=8'h12;zm[]=8'h25;zm[]=8'h14;zm[]=8'h28;zm[]=8'h98;zm[]=8'h30;zm[]=8'h70;zm[]=8'h00;zm[]=8'h90;zm[]=8'h38;zm[]=8'h90;zm[]=8'h25;zm[]=8'h12;zm[]=8'h22;zm[]=8'h11;zm[]=8'h25;zm[]=8'h12;\r\n"
+ "zm[]=8'h29;zm[]=8'h1C;zm[]=8'h30;zm[]=8'h80;zm[]=8'h00;zm[]=8'h80;zm[]=8'h00;zm[]=8'h00;\"努\",8\r\n"
+ "zm[]=8'h00;zm[]=8'h00;zm[]=8'h08;zm[]=8'h01;zm[]=8'h08;zm[]=8'h02;zm[]=8'h08;zm[]=8'h04;zm[]=8'h08;zm[]=8'h18;zm[]=8'h08;zm[]=8'h60;zm[]=8'hFF;zm[]=8'h80;zm[]=8'h08;zm[]=8'h00;zm[]=8'h08;zm[]=8'h04;zm[]=8'h08;zm[]=8'h02;zm[]=8'h08;zm[]=8'h01;zm[]=8'h08;zm[]=8'h02;\r\n"
+ "zm[]=8'h0F;zm[]=8'hFC;zm[]=8'h00;zm[]=8'h00;zm[]=8'h00;zm[]=8'h00;zm[]=8'h00;zm[]=8'h00;\"力\",9\r\n"
+ "zm[]=8'h00;zm[]=8'h00;zm[]=8'h1F;zm[]=8'hFF;zm[]=8'h10;zm[]=8'h00;zm[]=8'h10;zm[]=8'h00;zm[]=8'h30;zm[]=8'h00;zm[]=8'h53;zm[]=8'hF8;zm[]=8'h92;zm[]=8'h10;zm[]=8'h12;zm[]=8'h10;zm[]=8'h12;zm[]=8'h10;zm[]=8'h13;zm[]=8'hF8;zm[]=8'h10;zm[]=8'h00;zm[]=8'h10;zm[]=8'h02;\r\n"
+ "zm[]=8'h10;zm[]=8'h01;zm[]=8'h1F;zm[]=8'hFE;zm[]=8'h00;zm[]=8'h00;zm[]=8'h00;zm[]=8'h00;\"向\",10\r\n"
+ "zm[]=8'h10;zm[]=8'h00;zm[]=8'h10;zm[]=8'h00;zm[]=8'h17;zm[]=8'hFF;zm[]=8'h94;zm[]=8'h90;zm[]=8'h74;zm[]=8'h92;zm[]=8'h14;zm[]=8'h91;zm[]=8'h17;zm[]=8'hFE;zm[]=8'h10;zm[]=8'h00;zm[]=8'h10;zm[]=8'h00;zm[]=8'h13;zm[]=8'hF0;zm[]=8'h30;zm[]=8'h02;zm[]=8'hD0;zm[]=8'h01;\r\n"
+ "zm[]=8'h17;zm[]=8'hFE;zm[]=8'h10;zm[]=8'h00;zm[]=8'h10;zm[]=8'h00;zm[]=8'h00;zm[]=8'h00;\"前\",11\r\n"
+ "zm[]=8'h00;zm[]=8'h00;zm[]=8'h1F;zm[]=8'hFE;zm[]=8'h30;zm[]=8'h84;zm[]=8'hD0;zm[]=8'h84;zm[]=8'h10;zm[]=8'h84;zm[]=8'h10;zm[]=8'h84;zm[]=8'h1F;zm[]=8'hFE;zm[]=8'h02;zm[]=8'h00;zm[]=8'h0C;zm[]=8'h00;zm[]=8'hF1;zm[]=8'h00;zm[]=8'h10;zm[]=8'hC2;zm[]=8'h10;zm[]=8'h01;\r\n"
+ "zm[]=8'h10;zm[]=8'h02;zm[]=8'h1F;zm[]=8'hFC;zm[]=8'h00;zm[]=8'h00;zm[]=8'h00;zm[]=8'h00;\"的\",12\r\n"
+ "zm[]=8'h00;zm[]=8'h10;zm[]=8'h00;zm[]=8'h20;zm[]=8'h00;zm[]=8'hC0;zm[]=8'h07;zm[]=8'h00;zm[]=8'h00;zm[]=8'h00;zm[]=8'h00;zm[]=8'h02;zm[]=8'h00;zm[]=8'h01;zm[]=8'hFF;zm[]=8'hFE;zm[]=8'h00;zm[]=8'h00;zm[]=8'h00;zm[]=8'h00;zm[]=8'h00;zm[]=8'h00;zm[]=8'h04;zm[]=8'h00;\r\n"
+ "zm[]=8'h02;zm[]=8'h00;zm[]=8'h01;zm[]=8'h80;zm[]=8'h00;zm[]=8'h70;zm[]=8'h00;zm[]=8'h00;\"小\",13\r\n"
+ "zm[]=8'h08;zm[]=8'h80;zm[]=8'h11;zm[]=8'h00;zm[]=8'h23;zm[]=8'hFF;zm[]=8'hCC;zm[]=8'h00;zm[]=8'h00;zm[]=8'h04;zm[]=8'h04;zm[]=8'h88;zm[]=8'h08;zm[]=8'hB0;zm[]=8'h14;zm[]=8'h82;zm[]=8'h24;zm[]=8'h81;zm[]=8'hC7;zm[]=8'hFE;zm[]=8'h24;zm[]=8'h80;zm[]=8'h14;zm[]=8'hA0;\r\n"
+ "zm[]=8'h08;zm[]=8'h90;zm[]=8'h04;zm[]=8'h8C;zm[]=8'h04;zm[]=8'h00;zm[]=8'h00;zm[]=8'h00;\"徐\",14");
int j = 0;
for(int i=0;i<str.length();i++) {
if(str.charAt(i) == ']') {
str.insert(i, j);
//插入字符后,索引需要增加,不然会卡死
if(j<=9)
i++;
else if (j<=99){
i+=2;
}
else {
i+=3;
}
j++;
}
}
System.out.println(str);
}
在Eclipse或其他的软件中运行后,在控制台可以得到有数组下表标的字符串
在放入Quartus中时记得把后面的字模注释用“//”注释了,然后嫌格式不好看可以自己更改一下,就可以开始使用了。但是记得更改RAM地址跳转的位置
七、小结
在遇到显示不正常的时候一定要多去查查写进去的数据有没有写错,读出来的数据有没有问题,多看看仿真,不行降低时钟速度,使用signal tap看看数据的值,有的时候仿真的和实际的值可能会存在一些偏差。
贴一下RAM读写+spi写的testbench代码(可以增删一些模块来实现你想要的仿真)
`timescale 1ns/1ns //仿真单位为1ns,精度为1ns
module ram_read_tb();
reg clk;
reg rst_n;
wire write_done;
reg init_done;
wire [7:0]ram_data;
wire rden;
wire[9:0] rdaddress;
wire ena_write;
wire oled_dc;
wire[7:0] data;
wire wren;
wire [9:0] wraddress;
wire [7:0] wrdata;
wire oled_sclk;
wire oled_mosi;
ram_read ram_read_inst(
.clk(clk),
.rst_n(rst_n),
.write_done(write_done),
.init_done(init_done),
.rden(rden),
.rdaddress(rdaddress),
.ena_write(ena_write),
.oled_dc(oled_dc),
.ram_data(ram_data),
.data(data)
);
ram_write ram_write_inst(
.clk(clk),
.rst_n(rst_n),
.init_done(1'b1),
.wren(wren),
.wraddress(wraddress),
.data(wrdata)
);
ram_show ram_show_inst(
.clock(clk),
.aclr(1'b0),
.data(wrdata),
.rdaddress(rdaddress),
.rden(rden),
.wraddress(wraddress),
.wren(wren),
.q(ram_data)
);
//spi传输模块
spi_writebyte spi_writebyte_inst(
.clk(clk),
.rst_n(rst_n),
.ena_write(ena_write),
.data(data),
.sclk(oled_sclk),
.mosi(oled_mosi),
.write_done(write_done)
);
initial begin
#0 clk = 0;
rst_n = 0;
init_done = 1;
#20 rst_n = 1;
end
always #5 clk = ~clk;
endmodule