在参加比赛时做的
也是第一次用modelsim详细做仿真
内容是基于apb3片内系统总线的lcd驱动电路
rtl代码:
// LCD_Driver.v
//功能简述:在1602液晶模块上显示字符串,其中第一行显示“Welcome To ZLG”,
// 在第二行显示“www.zlgmcu.com”。
//液晶模块为TC1602A,相关特性请参考其数据手册
module LCD_Driver(clk_LCD,rst,LCD_EN,RS,RW,DB8,
pclk,
paddr,
pwdata,
pwrite,
psel,
penable,
pready,
data_end);
input clk_LCD,rst; //rst为全局复位信号(高电平有效)
output LCD_EN,RS,RW;
//apb3片内总线的接口信号
input [4:0] paddr;
input [7:0] pwdata;
input pwrite,psel,penable,pclk;
output reg pready;
//数据缓冲区已经完成写任务
output reg data_end;
//LCD_EN为LCD模块的使能信号(下降沿触发)
//RS=0时为写指令;RS=1时为写数据
//RW=0时对LCD模块执行写操作;RW=1时对LCD模块执行读操作
output [7:0] DB8; //8位指令或数据总线
reg [7:0] DB8;
reg[255:0] buffer; //液晶显示的数据缓存(以供32字节,之用31:4字节,后3:0字节不用)
reg RS,LCD_EN_Sel;
reg [3:0] disp_count;
reg [3:0] state;
reg sent_data;
reg en,mod;
reg[7:0] one_data,one_addr;
//解决不同时钟问题
reg[1:0] count;
always @(posedge pclk or posedge rst)
begin
if(rst)
count<=2'b00;
else
count<={count[0],disp_count[0]};
end
assign kk=(count[0]!=count[1]);
parameter Clear_Lcd = 4'b0000, //清屏并光标复位
Set_Disp_Mode = 4'b0001, //设置显示模式:8位2行5x7点阵
Disp_On = 4'b0010, //显示器开、光标不显示、光标不允许闪烁
Shift_Down = 4'b0011, //文字不动,光标自动右移
Write_Addr = 4'b0100, //写入显示起始地址
Write_Data_First = 4'b0101, //写入第一行显示的数据
Write_Data_Second = 4'b0110, //写入第二行显示的数据
one_write_addr=4'b0111,
one_write_data=4'b1000,
Idel = 4'b0111; //空闲状态
assign RW = 1'b0; //RW=0时对LCD模块执行写操作
assign LCD_EN = LCD_EN_Sel ? clk_LCD : 1'b0;
///写数据缓冲区
always@(posedge pclk or posedge rst)
begin
if(rst)
begin
pready<=1'b0;
buffer<="Welcome To ZLG www.zlgmcu.com";
end
else
begin
pready<=1'b0;
if(sent_data&kk)
buffer<=(buffer<<8);
if(psel&(!penable))
begin
{buffer[paddr*8+7],buffer[paddr*8+6],buffer[paddr*8+5],buffer[paddr*8+4],buffer[paddr*8+3],buffer[paddr*8+2],buffer[paddr*8+1],buffer[paddr*8]}<=pwdata;//利用apb3总线写入缓存
pready<=1'b1;
end
end
end
always @(posedge pclk or posedge rst)
if(rst)
begin
en<=1'b0;
mod<=1'b1;
one_data<=8'b0000_0000;
one_addr<=8'b0000_0000;
end
else
begin
if(psel&(!penable))
begin
case(paddr)
5'b0000://apb3的00000单元用于控制字
begin
en<=pwdata[0];
mod<=pwdata[1];
end
5'b00001://apb3的00001单元用于只写一个单元时的数据
one_data<=pwdata;
5'b00010://apb3的00010单元用于只写一个单元时的地
one_addr<=pwdata;
default:
begin
en<=en;
mod<=mod;
one_data<=one_data;
one_addr<=one_addr;
end
endcase
end
end
//通过LCD_EN_Sel信号来控制LCD_EN的开启与关闭
always @(posedge clk_LCD or posedge rst)
begin
if(rst)
begin
state <= Clear_Lcd; //复位:清屏并光标复位
RS <= 1'b0; //复位:RS=0时为写指令;
DB8 <= 8'b0; //复位:使DB8总线输出全0
LCD_EN_Sel <= 1'b1; //复位:开启夜晶使能信号
disp_count <= 4'b0;
end
else
case(state) //初始化LCD模块
Clear_Lcd:
begin
state <= Set_Disp_Mode;
DB8 <= 8'b00000001; //清屏并光标复位
end
Set_Disp_Mode:
begin
state <= Disp_On;
DB8 <= 8'b00111000; //设置显示模式:8位2行5x8点阵
end
Disp_On:
begin
state <= Shift_Down;
DB8 <= 8'b00001100; //显示器开、光标不显示、光标不允许闪烁
end
Shift_Down:
begin
DB8 <= 8'b00000110; //文字不动,光标自动右移
if(mod)//如果mod为一,则全页刷新
state <= Write_Addr;
else//否则,只刷新一个显示单元
state<=one_write_addr;
end
Write_Addr:
begin
DB8 <= 8'b10000001; //写入第一行显示起始地址:第一行第二个位置
state <= Write_Data_First;
sent_data<=1'b1;
end
Write_Data_First: //写第一行数据
begin
if(disp_count==13)
sent_data<=1'b0;
if(disp_count == 14) //disp_count等于14时表示第一行数据已写完
begin
DB8 <= 8'b11000001; //送入写第二行的指令
RS <= 1'b0;
disp_count <= 4'b0;
state <= Write_Data_Second; //写完第一行进入写第二
sent_data<=1'b1;
end
else
begin
RS <= 1'b1; //RS=1表示写数据
disp_count <= disp_count + 1'b1;
state <= Write_Data_First;
DB8<=buffer[255:248];
end
end
Write_Data_Second: //写第二行数据
begin
if(disp_count == 14)
begin
LCD_EN_Sel <= 1'b0;
RS <= 1'b0;
disp_count <= 4'b0;
state <= Idel; //写完进入空闲状态
sent_data<=1'b0;
end
else
begin
RS <= 1'b1;
disp_count <= disp_count + 1'b1;
state <= Write_Data_Second;
DB8<=buffer[255:248];
end
end
one_write_addr:
begin
DB8 <= one_addr; //写入地址
state <= one_write_data;
end
one_write_data:
begin
RS <= 1'b1;
state<=Idel;
DB8<=one_data;
end
Idel:
begin
RS <= 1'b0;
if(en)
state<= Shift_Down;
end
default: state <= Clear_Lcd; //若state为其他值,则将state置为Clear_Lcd
endcase
end
endmodule
测试文件:
`timescale 1 ns / 1 ps
module testbench();
//////////////////////////////////////////////////////////
reg clk_LCD;
reg rst;
reg pclk;
reg pwrite;
reg psel;
reg penable;
reg[4:0] paddr;
reg[7:0] pwdata;
//////////////////////////////////////////////////////////
wire LCD_EN;
wire RS;
wire RW;
wire pready;
wire data_end;
wire[7:0] DB8;
////////////////////////////////////////////////////////
top_lcd_drive uu1(
.clk_LCD(clk_LCD),
.rst(rst),
.LCD_EN(LCD_EN),
.RS(RS),
.RW(RW),
.DB8(DB8),
.pclk(pclk),
.paddr(paddr),
.pwdata(pwdata),
.pwrite(pwrite),
.psel(psel),
.penable(penable),
.pready(pready),
.data_end(data_end)
);
initial
begin
clk_LCD=1'b0;
rst=1'b1;
pclk=1'b0;
pwrite=1'b0;
psel=1'b0;
penable=1'b0;
paddr=5'b00000;
pwdata="8"'b0000_0000;
# 300 rst="1"'b0;
# 4000
//利用单独写数据的模式显示
cpu_write(5'b00010,8'b10000010); //写地址
# 10 cpu_write(5'b00001,"o");//写数据
# 10 cpu_write(5'b00000,8'b0000_0001);//写控制字
//利用全页改变的模式显示
# 150 cpu_write(5'b00111,"o");//改写一个数据
# 10 cpu_write(5'b01000,"l");
# 10 cpu_write(5'b01001,"d");
# 10 cpu_write(5'b01010,"l");
# 10 cpu_write(5'b01011,"e");
# 10 cpu_write(5'b01100,"e");
# 10 cpu_write(5'b00000,8'b0000_0011);//写控制字
# 4000 $stop;
end
//clock
always
begin
# 50 clk_LCD=~clk_LCD;
end
always
begin
# 5 pclk=~pclk;
end
task cpu_write;
input [4:0] address;
input [7:0] data;
begin
// $display("CPU Write %04x = %04x",address,data);
@(negedge pclk);
pwrite = 1'b0;
@(negedge pclk);
penable = 1'b0;
psel = 1'b1;
paddr= address;
pwdata = data;
pwrite = 1'b1;
@(negedge pclk);
penable = 1'b1;
@(negedge pclk);
pwrite = 1'b0;
penable = 1'b0;
psel = 1'b0;
end
endtask
endmodule
文章评论(0条评论)
登录后参与讨论