在DE2开发板上使用uart接收来自pc串口发送的字符,通过lcd1602液晶屏显示。
开发板:DE2
型号:EP2C35F672C6
开发工具:Quartus II 13.0 + Modelsim 10.5 SE
全局时钟:50M
波特率:115200
数据位:8
校验:无
停止位:1
不使用状态机,只有接收模块,代码:
module uart_rx(
// clk, rst_n
input clk,
input rst_n,
// uart rx
input rx,
// valid data output
output reg valid,
output reg [7:0] data
);
parameter CLK_FRE = 50_000_000;
parameter BAUD_RATE = 115200; // 默认115200波特率
parameter cnt_baud_max = CLK_FRE / BAUD_RATE - 1; // 115200波特率下是434时钟周期一波特
// 打两拍防止亚稳态,rx_3用于检测rx下降沿
reg rx_1;
reg rx_2;
reg rx_3;
// 工作使能
reg work_ena;
// 波特计数器 比特计数器
reg [8:0] cnt_baud;
reg [4:0] cnt_bit;
// 计数器使能、清零
wire cnt_baud_ena;
wire cnt_baud_end;
wire cnt_bit_ena;
wire cnt_bit_end;
// data移位寄存器
reg [7:0] data_shift;
// valid打一拍再输出,和data同步输出
reg valid_reg;
// 打两拍防止亚稳态
always @(posedge clk, negedge rst_n) begin
if(!rst_n) begin
rx_1 <= 0;
rx_2 <= 0;
rx_3 <= 0;
end else begin
rx_1 <= rx;
rx_2 <= rx_1;
rx_3 <= rx_2;
end
end
// rx下降沿检测,出现下降沿开始工作,接收完数据结束工作
always @(posedge clk, negedge rst_n) begin
if(!rst_n) begin
work_ena <= 0;
end else begin
if(~rx_2 & rx_3)
work_ena <= 1;
else if(cnt_bit_ena && cnt_bit_end)
work_ena <= 0;
end
end
// 波特计数器
always @(posedge clk, negedge rst_n) begin
if(!rst_n)
cnt_baud <= 0;
else if(cnt_baud_ena) begin
if(cnt_baud_end)
cnt_baud <= 0;
else
cnt_baud <= cnt_baud + 1;
end else
cnt_baud <= 0;
end
assign cnt_baud_ena = work_ena;
assign cnt_baud_end = (cnt_baud == cnt_baud_max);
// 比特计数器
always @(posedge clk, negedge rst_n) begin
if(!rst_n)
cnt_bit <= 0;
else if(cnt_bit_ena) begin
if(cnt_bit_end)
cnt_bit <= 0;
else
cnt_bit <= cnt_bit + 1;
end
end
assign cnt_bit_ena = (cnt_baud == cnt_baud_max / 2);
assign cnt_bit_end = (cnt_bit == 8);
// data移位寄存器
always @(posedge clk, negedge rst_n) begin
if(!rst_n)
data_shift <= 0;
else if(cnt_bit_ena)
data_shift <= {rx_2, data_shift[7:1]};
end
// 内部valid_reg有效信号
always @(posedge clk, negedge rst_n) begin
if(!rst_n) begin
valid_reg <= 0;
end
else if(cnt_bit_ena && cnt_bit_end)
valid_reg <= 1;
else
valid_reg <= 0;
end
// valid_reg 打一拍再输出,和data同步
always @(posedge clk, negedge rst_n) begin
if(!rst_n)
valid <= 0;
else
valid <= valid_reg;
end
// 输出data
always @(posedge clk, negedge rst_n) begin
if(!rst_n)
data <= 0;
else if(valid_reg)
data <= data_shift;
end
endmodule
tb测试文件和波形图就不放了
根据datasheet,先进行初始化,再进入工作模式
每次写入使能大概16个时钟周期(50M时钟下20ns一时钟周期)
状态机三个初始化状态 + IDLE + WRITE共5个状态
三个初始化状态:
每个初始化流程占大概1.53ms
没深入研究lcd1602,所以没写其他功能,比如清屏,换行等
所以只能复位清屏,第二行没法使用
代码:
module lcd1602_ctrl(
// clk, rst_n
input clk,
input rst_n,
// valid data input
input valid,
input [7:0] data,
// lcd1602 side
output LCD_ON,
output LCD_BLON,
output LCD_RW,
output LCD_EN,
output LCD_RS,
output reg [7:0] LCD_DATA
);
parameter MODE_SET0 = 0, MODE_SET1 = 1, CLEAR = 2, IDLE = 3, WRITE = 4;
reg [2:0] state, next;
// 数据寄存器
reg [7:0] data_reg;
// 初始化计数器,初始化三个状态各保持1.53ms
reg [16:0] cnt_init;
wire cnt_init_ena;
wire cnt_init_end;
// EN计数器(写入命令和数据需要EN拉高16个时钟周期)
reg [3:0] cnt_en;
reg cnt_en_ena;
wire cnt_en_end;
// 这两个信号不能忘了
assign LCD_ON = 1;
assign LCD_BLON = 1;
// 只写不读
assign LCD_RW = 0;
// WRITE状态下是data,其余状态是cmd
assign LCD_RS = (state == WRITE);
// 数据寄存器
always @(posedge clk, negedge rst_n) begin
if(!rst_n)
data_reg <= 0;
else if(valid)
data_reg <= data;
end
// 初始化计数器
always @(posedge clk, negedge rst_n) begin
if(!rst_n)
cnt_init <= 0;
else if(cnt_init_ena) begin
if(cnt_init_end)
cnt_init <= 0;
else
cnt_init <= cnt_init + 1;
end
end
assign cnt_init_ena = (state == MODE_SET0 || state == MODE_SET1 || state == CLEAR);
assign cnt_init_end = (cnt_init == 80_000);
// EN计数器使能
always @(posedge clk, negedge rst_n) begin
if(!rst_n)
cnt_en_ena <= 0;
else if(cnt_en_end)
cnt_en_ena <= 0;
else if(cnt_init == 100 || state == WRITE)
cnt_en_ena <= 1;
end
// EN 计数器
always @(posedge clk, negedge rst_n) begin
if(!rst_n)
cnt_en <= 0;
else if(cnt_en_ena) begin
if(cnt_en_end)
cnt_en <= 0;
else
cnt_en <= cnt_en + 1;
end
end
assign cnt_en_end = (cnt_en == 15);
assign LCD_EN = cnt_en_ena;
// 状态机
always @(posedge clk, negedge rst_n) begin
if(!rst_n)
state <= MODE_SET0;
else
state <= next;
end
always @(*) begin
case(state)
MODE_SET0: next = cnt_init_end ? MODE_SET1 : MODE_SET0;
MODE_SET1: next = cnt_init_end ? CLEAR : MODE_SET1;
CLEAR : next = cnt_init_end ? IDLE : CLEAR;
IDLE : next = valid ? WRITE : IDLE;
WRITE : next = cnt_en_end ? IDLE : WRITE;
default : next = MODE_SET0;
endcase
end
// LCD_DATA
always @(*) begin
case(state)
MODE_SET0: LCD_DATA = 8'h38; // 8位宽,两行显示,5*7点阵
MODE_SET1: LCD_DATA = 8'h0f; // 开显示,开光标显示、闪烁
CLEAR : LCD_DATA = 8'h01; // 清屏
IDLE : LCD_DATA = 8'h00;
WRITE : LCD_DATA = data_reg;
default : LCD_DATA = 8'h00;
endcase
end
endmodule
tb测试文件和波形图就不放了
顶层模块只需例化这两个模块,将uart_rx模块输出端口valid和data接入到lcd1602模块的valid和data输入端口即可。
代码经过modelsim仿真和上板验证。
实验结果:串口发送hello world!,FPGA上的lcd1602就显示hello world!
本次实验是串口传图实验的第一步,使用串口接收数据。