此实验是在“基于I2C EPPRPM(AT24C02B) + LCD12864实验”基础上,把LCD模块里的一段式状态机改成三段式,I2C EPPROM模块暂时未改出来,一步一步来吧,改完后代码下载到板子上验证是OK的。
三段式状态机里面要注意的是,抽出来reg 如计数器num、lcd_rs,在利用状态作为判断条件时,得注意是用n_state呢还是用c_state,对于我这样的初学者,一时半会弄不清是用哪个作为判断条件好,怎么办,每种情况都试一次吧。结果用n_state能正常显示,用c_state显示乱码。
用c_state作为判断条件的仿真波形如下:
好奇怪哦,左边框中num为什么没有清零呢?这是因为if else产生的优先权导致,一不小心就范这样的错误。
/***************************************************/
reg [:] c_state,n_state;
reg [:] num;
reg lcd_rs;
always @(posedge lcd_clk or negedge rst_n)
if(!rst_n)
lcd_rs <= 'b0;
else if((c_state == WRITE_DATA0)||(c_state == WRITE_DATA1)||(c_state == WRITE_DATA2)||(c_state == WRITE_DATA3))
lcd_rs <= 'b1; //data mode
else
lcd_rs <= 'b0; //cmd mode
/***************************************************/ /***********************************************************/
always @(posedge lcd_clk or negedge rst_n)
if(!rst_n)
num <= 'd0;
else if((c_state == WRITE_DATA0) || (c_state == WRITE_DATA1))
num <= num + 'b1;
else if(num == 'd20) //不能放在状态判断后面,否则num不会归零
num <= 'd0;
/***********************************************************/
用n_state作为判断条件的仿真波形如下:
虽然把c_state改成n_state ,但是这个num能清零,好奇怪哦,有时间在好好研究吧,为了保险起见还是改成如下形式:
/***************************************************/
reg [:] c_state,n_state;
reg [:] num;
reg lcd_rs;
always @(posedge lcd_clk or negedge rst_n)
if(!rst_n)
lcd_rs <= 'b0;
else if((n_state == WRITE_DATA0)||(n_state == WRITE_DATA1)||(n_state == WRITE_DATA2)||(n_state == WRITE_DATA3))
lcd_rs <= 'b1; //data mode
else
lcd_rs <= 'b0; //cmd mode
/***************************************************/ /***********************************************************/
always @(posedge lcd_clk or negedge rst_n)
if(!rst_n)
num <= 'd0;
else if(num == 'd20)
num <= 'd0;
else if((c_state == WRITE_DATA0) || (c_state == WRITE_DATA1))
num <= num + 'b1;
/***********************************************************/
注意看上面两种仿真波形,右边框的位置上是有区别的,c_state要比n_state慢一节拍,但对应输出数据值是c_state,比如当c_state=7时,是对LCD写数据,故lcd_rs=1,输出lcd_data="I",很明显,用c_state作为判断条件的是不对的,输出已经延长一拍。
三段式状态机里还要注意一个地方,在状态机里面既要作为输出,又要作为其他输出信号的判断条件,如下面的en,会产生警告,报出间接指出latch。
/***********************************************************/
reg en;
always @(posedge sys_clk or negedge rst_n)
if(!rst_n) begin
en <= 'b1;
dis_data <= 'h00;
end
else case(num)
//I2C閫氫俊瀹為獙
'd0: dis_data <= "I";
'd1: dis_data <= "2";
.
.
.
/***********************************************************/ assign lcd_en = en ? lcd_clk : 'b0;
/***********************************************************/
“Warning (10240): Verilog HDL Always Construct warning at LCD12864.v(121): inferring latch(es) for variable "en", which holds its previous value in one or more paths through the always construct”
虽然有些警告无关紧要的,但还是应尽量避免这样写,办法也是要把en抽出来,我这里是简单的处理下。
/***************************************************/
wire en;
assign en = 'b1;//(n_state == STOP) ? 1'b0 : 'b1;
assign lcd_en = en ? lcd_clk : 'b0;
/***************************************************/
其实这里还有个问题,程序中在IDLE时,lcd_data <= 8'hzz;但仿真波形中没有看到,跟踪调试时,发现这语句永远不执行,自己屡了屡,确实执行不到,一时半会我也想不明白。比较简单的状态机,差不多先这样吧。
代码实现:
LCD12864.v
module LCD12864(
//input
sys_clk,
rst_n,
dis_data_low,
dis_data_hig, //output
lcd_rs,
lcd_rw,
lcd_en,
lcd_data,
lcd_psb
);
input sys_clk;// 50MHZ
input rst_n;
input [:] dis_data_low;
input [:] dis_data_hig; output lcd_rs;//H:data L:command
output lcd_rw;//H:read module L:write module
output lcd_en;//H active
output [:]lcd_data;
output lcd_psb;//H:parallel module L:SPI module /***************************************************/
parameter T3MS = 'd149_999;
parameter IDLE = 'd0,
INIT_FUN_SET1 = 'd1,
INIT_FUN_SET2 = 'd2,
INIT_DISPLAY = 'd3,
INIT_CLEAR = 'd4,
INIT_DOT_SET = 'd5,
SET_DDRAM = 'd6,
WRITE_DATA0 = 'd7,
SET_DDRAM2 = 'd8,
WRITE_DATA1 = 'd9,
WRITE_DATA2 = 'd10,
WRITE_DATA3 = 'd11;
/***************************************************/
//产生周期为6MS的lcd_clk给LCD
reg [:] cnt;
reg lcd_clk;
always @(posedge sys_clk or negedge rst_n)
if(!rst_n) begin
cnt <= 'd0;
lcd_clk <= 'b0;
end
else if(cnt == T3MS)begin
cnt <= 'd0;
lcd_clk <= ~lcd_clk;
end
else
cnt <= cnt + 'b1;
/***************************************************/
reg lcd_rs;
always @(posedge lcd_clk or negedge rst_n)
if(!rst_n)
lcd_rs <= 'b0;
else if((n_state == WRITE_DATA0)||(n_state == WRITE_DATA1)||(n_state == WRITE_DATA2)||(n_state == WRITE_DATA3))
lcd_rs <= 'b1; //data mode
else
lcd_rs <= 'b0; //cmd mode
/***************************************************/
reg [:] c_state,n_state;
always @(posedge lcd_clk or negedge rst_n)
if(!rst_n)
c_state <= IDLE;
else
c_state <= n_state;
/***************************************************/
always @(*)
case(c_state)
IDLE: n_state = INIT_FUN_SET1;
INIT_FUN_SET1: n_state = INIT_FUN_SET2;
INIT_FUN_SET2: n_state = INIT_DISPLAY;
INIT_DISPLAY: n_state = INIT_CLEAR;
INIT_CLEAR: n_state = INIT_DOT_SET;
INIT_DOT_SET: n_state = SET_DDRAM;
SET_DDRAM: n_state = WRITE_DATA0;
WRITE_DATA0: if(num == 'd11) n_state = SET_DDRAM2;
else n_state = WRITE_DATA0;
SET_DDRAM2: n_state = WRITE_DATA1;
WRITE_DATA1: if(num == 'd20) n_state = WRITE_DATA2;
else n_state = WRITE_DATA1;
WRITE_DATA2: n_state = WRITE_DATA3;
WRITE_DATA3: n_state = SET_DDRAM;
default: n_state = IDLE;
endcase
/***************************************************/
reg [:] lcd_data;
reg [:] dis_data;
always @(posedge lcd_clk or negedge rst_n)
if(!rst_n) lcd_data <= 'h00;
else case(n_state)
IDLE: lcd_data <= 'hzz;
INIT_FUN_SET1: lcd_data <= 'h30; //function setting
INIT_FUN_SET2: lcd_data <= 'h30; //function setting
INIT_DISPLAY: lcd_data <= 'h0c; //display setting
INIT_CLEAR: lcd_data <= 'h01; //clear setting
INIT_DOT_SET: lcd_data <= 'h06; //dot setting
SET_DDRAM: lcd_data <= 'h91; //2 line
WRITE_DATA0: lcd_data <= dis_data;
SET_DDRAM2: lcd_data <= 'h89; //3 line
WRITE_DATA1: lcd_data <= dis_data;
WRITE_DATA2: lcd_data <= dis_data_hig; //high byte
WRITE_DATA3: lcd_data <= dis_data_low; //low byte
default: ;
endcase
/***********************************************************/
reg [:] num;
always @(posedge lcd_clk or negedge rst_n)
if(!rst_n)
num <= 'd0;
else if(num == 'd20)
num <= 'd0;
else if((n_state == WRITE_DATA0) || (n_state == WRITE_DATA1))
num <= num + 'b1;
/***********************************************************/
always @(posedge sys_clk or negedge rst_n)
if(!rst_n)
dis_data <= 'h00;
else case(num)
'd0: dis_data <= "I";
'd1: dis_data <= "2";
'd2: dis_data <= "C";
'd3: dis_data <= " ";//8'hcd;
'd4: dis_data <= "E";//8'ha8;
'd5: dis_data <= "P";//8'hd0;
'd6: dis_data <= "P";//8'hc5;
'd7: dis_data <= "R";//8'hca;
'd8: dis_data <= "O";//8'hb5;
'd9: dis_data <= "M";//8'hd1;
'd10: dis_data <= " ";//8'he9;
'd11: dis_data <= "0";
'd12: dis_data <= "3";
'd13: dis_data <= 8'hb5;
'd14: dis_data <= 8'hd8;
'd15: dis_data <= 8'hd6;
'd16: dis_data <= 8'hb7;
'd17: dis_data <= 8'hd6;
'd18: dis_data <= 8'hb5;
'd19: dis_data <= " ";
default:dis_data <= 'h00;
endcase
/***************************************************/
assign lcd_rw = 'b0;//只有写模式
assign lcd_psb = 'b1;//并口模式
wire en;
assign en = 'b1;//(n_state == STOP) ? 1'b0 : 'b1;
assign lcd_en = en ? lcd_clk : 'b0;
/***************************************************/
endmodule
激励文件(只对LCD模块进行仿真):
lcd_top.v
`timescale 1ns/10ps
module lcd_top;
/*************************************************************/
reg sys_clk;
reg rst_n;
wire start_cnt;
/*************************************************************/
initial begin
sys_clk = 'b0;
rst_n = 'b0;
#;
rst_n = 'b1;
end
/*************************************************************/
always # sys_clk = ~sys_clk;
/*************************************************************/
wire lcd_rs;//H:data L:command
wire lcd_rw;//H:read module L:write module
wire lcd_en;//H active
wire [:] lcd_data;
wire lcd_psb;//H:parallel module L:SPI module
LCD12864 LCD12864_u1(
//input
.sys_clk(sys_clk),
.rst_n(rst_n),
.dis_data_low(""),
.dis_data_hig("c"), //output
.lcd_rs(lcd_rs),
.lcd_rw(lcd_rw),
.lcd_en(lcd_en),
.lcd_data(lcd_data),
.lcd_psb(lcd_psb)
);
endmodule
这里在把一段式状态机的仿真波形贴出来,可以好好的比较比较,这样做有什么不同,但要知道,三段式从输入到输出要比一段式要延时一个节拍。