基于FPGA的串口接收lcd1602显示
简介
在DE2开发板上使用uart接收来自pc串口发送的字符,通过lcd1602液晶屏显示。
开发板:DE2
型号:EP2C35F672C6
开发工具:Quartus II 13.0 + Modelsim 10.5 SE
全局时钟:50M
串口接收模块
波特率:115200
数据位:8
校验:无
停止位:1
不使用状态机,只有接收模块,代码:
module uart_rx(// clk, rst_ninput clk,input rst_n,// uart rxinput rx,// valid data outputoutput 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) beginif(!rst_n) beginrx_1 <= 0;rx_2 <= 0;rx_3 <= 0;end else beginrx_1 <= rx;rx_2 <= rx_1;rx_3 <= rx_2; endend// rx下降沿检测,出现下降沿开始工作,接收完数据结束工作always @(posedge clk, negedge rst_n) beginif(!rst_n) beginwork_ena <= 0;end else beginif(~rx_2 & rx_3)work_ena <= 1;else if(cnt_bit_ena && cnt_bit_end)work_ena <= 0;endend// 波特计数器always @(posedge clk, negedge rst_n) beginif(!rst_n)cnt_baud <= 0;else if(cnt_baud_ena) beginif(cnt_baud_end)cnt_baud <= 0;elsecnt_baud <= cnt_baud + 1;end elsecnt_baud <= 0;endassign cnt_baud_ena = work_ena;assign cnt_baud_end = (cnt_baud == cnt_baud_max);// 比特计数器always @(posedge clk, negedge rst_n) beginif(!rst_n)cnt_bit <= 0;else if(cnt_bit_ena) beginif(cnt_bit_end)cnt_bit <= 0;elsecnt_bit <= cnt_bit + 1;endendassign cnt_bit_ena = (cnt_baud == cnt_baud_max / 2);assign cnt_bit_end = (cnt_bit == 8);// data移位寄存器always @(posedge clk, negedge rst_n) beginif(!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) beginif(!rst_n) beginvalid_reg <= 0;end else if(cnt_bit_ena && cnt_bit_end)valid_reg <= 1;elsevalid_reg <= 0;end// valid_reg 打一拍再输出,和data同步always @(posedge clk, negedge rst_n) beginif(!rst_n)valid <= 0;elsevalid <= valid_reg;end// 输出dataalways @(posedge clk, negedge rst_n) beginif(!rst_n)data <= 0;else if(valid_reg)data <= data_shift;endendmodule
tb测试文件和波形图就不放了
lcd1602驱动模块
根据datasheet,先进行初始化,再进入工作模式
每次写入使能大概16个时钟周期(50M时钟下20ns一时钟周期)
状态机三个初始化状态 + IDLE + WRITE共5个状态
三个初始化状态:
- 设定8位宽、两行显示、5*7点阵
- 开显示、开光标显示、开光标闪烁
- 清屏
每个初始化流程占大概1.53ms
没深入研究lcd1602,所以没写其他功能,比如清屏,换行等
所以只能复位清屏,第二行没法使用
代码:
module lcd1602_ctrl(// clk, rst_ninput clk,input rst_n,// valid data inputinput valid,input [7:0] data,// lcd1602 sideoutput 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.53msreg [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,其余状态是cmdassign LCD_RS = (state == WRITE);// 数据寄存器always @(posedge clk, negedge rst_n) beginif(!rst_n)data_reg <= 0;else if(valid)data_reg <= data;end// 初始化计数器always @(posedge clk, negedge rst_n) beginif(!rst_n)cnt_init <= 0;else if(cnt_init_ena) beginif(cnt_init_end)cnt_init <= 0;elsecnt_init <= cnt_init + 1;endendassign 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) beginif(!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) beginif(!rst_n)cnt_en <= 0;else if(cnt_en_ena) beginif(cnt_en_end)cnt_en <= 0;elsecnt_en <= cnt_en + 1;endendassign cnt_en_end = (cnt_en == 15);assign LCD_EN = cnt_en_ena;// 状态机always @(posedge clk, negedge rst_n) beginif(!rst_n)state <= MODE_SET0;elsestate <= next;endalways @(*) begincase(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;endcaseend// LCD_DATAalways @(*) begincase(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;endcaseendendmodule
tb测试文件和波形图就不放了
顶层模块只需例化这两个模块,将uart_rx模块输出端口valid和data接入到lcd1602模块的valid和data输入端口即可。
结果
代码经过modelsim仿真和上板验证。
实验结果:串口发送hello world!,FPGA上的lcd1602就显示hello world!
本次实验是串口传图实验的第一步,使用串口接收数据。
本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!
