FPGA驱动LCD1602(IIC) Verilog代码(二)------ LCD写命令/数据模块

一、概述

 

        时序及数据参考链接:51单片机 使用IIC转接板驱动LCD1602

        lcd1602引脚说明参考链接:STM32通过(软/硬IIC)控制LCD1602液晶显示屏(IIC转8位并口的PCF8574转接板的使用)原创

                                                         (图片来自上述链接)

        写数据和写命令的操作基本上都是一样的,只是最后以为不一样,所以就增加一个输入信号来控制是写数据还是写命令,最后将这个数据和需要写的数据进行或就可以了

        代码也没有什么特别的地方,只有特别繁杂的地方,就是每一次写一个数据,都需要等待这个数据写完成,这样可能状态有点多,但是代码看上去会更清晰一点

        下面贴出代码


二、Verilog代码

module lcd_write_cmd_data(
	input clk,						//时钟信号 1m
	input rst_n,					//按键复位信号
	input [7:0]data,				//需要写的一个字节
	input cmd_data,				//写的是数据还是命令 0:命令,1:数据
	input ena,						//模块使能
	inout sda,						//iic sda
	output scl,						//iic scl
	output done,					//一个字节写完成
	output sda_dir					//sda 方向
);

parameter DELAY = 25;			//写完一个字节等待时间,EN从1到0等待一段时间让lcd执行命令
reg [20:0] us_cnt;				//us计数器
reg us_cnt_clr;					//计数器清零信号

//状态声明
parameter WaitEn=0,WriteAddr=1,WaitWA=2,WriteHE=3,WaitWHE=4,WaitCMD1=5,WriteHNE=6,WaitWHNE=7,WriteLE=8,WaitWLE=9,WaitCMD2=10,WriteLNE=11,WaitWLNE=12,Done=13;
reg[3:0] state,next_state;		//当前状态和下一个状态
reg en_write;						//使能写

wire en_iicwrite = en_write;	//iic写使能赋值
wire iic_done;						//iic写完成
reg [7:0] iic_data;				//iic写的数据

//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 = WaitEn;//复位到初始状态
	else begin
		case(state)
		
			//等待模块使能
			WaitEn: next_state = ena ? WriteAddr : WaitEn;
			
			//写lcd地址
			WriteAddr: next_state = WaitWA;
			//等待写地址完成
			WaitWA: next_state = iic_done ? WriteHE : WaitWA;
			
			//写字节的高位 此时EN为1
			WriteHE: next_state = WaitWHE;
			//等待写高位为0
			WaitWHE: next_state = iic_done ? WaitCMD1 : WaitWHE;
			
			//延时等待一段时间
			WaitCMD1: next_state = (us_cnt == DELAY) ? WriteHNE : WaitCMD1;
			
			//写字节的高位 此时EN为0
			WriteHNE: next_state = WaitWHNE;
			//等待写完成
			WaitWHNE: next_state = iic_done ? WriteLE : WaitWHNE;
			
			//写字节的低位	 此时EN为1
			WriteLE: next_state =  WaitWLE;
			//等待写完成
			WaitWLE: next_state = iic_done ? WaitCMD2 : WaitWLE;
			
			//延时等待一段时间
			WaitCMD2: next_state = (us_cnt == DELAY) ? WriteLNE : WaitCMD2;
			
			//写字节的低位	此时EN为0
			WriteLNE: next_state = WaitWLNE;
			//延时等待一段时间
			WaitWLNE: next_state = iic_done ? Done : WaitWLNE;
			
			//完成写一次数据或命令
			Done: next_state = WaitEn;
		endcase
	end
end

//状态变量赋值
always @(posedge clk,negedge rst_n) begin
	if(!rst_n)begin
		iic_data <= 8'd0;				//iic数据复位
		en_write <= 1'b0;				//iic不使能写
		us_cnt_clr <= 1'b1;			//计数器复位
	end
	else begin
		case(state)
			WaitEn:begin
				iic_data <= 8'd0;				//iic数据复位
				en_write <= 1'b0;				//iic不使能写
				us_cnt_clr <= 1'b1;			//计数器复位
			end
		
			WriteAddr:begin
				iic_data <= 8'h4E;			//写lcd地址 0x4e
				en_write <= 1'b1;				//使能iic写
			end
			WaitWA:begin
				en_write <= 1'b0;				//使能信号拉低,等待写完成
			end
			
			WriteHE:begin
				en_write <= 1'b1;				//使能iic写
				//取得数据高4位,背光 1(开) 	EN(1)	RW 0 (写)	RS(cmd_data)0:命令,1:数据
				iic_data <= (data & 8'hF0) | 8'h0C | cmd_data;
			end
			WaitWHE:begin
				en_write <= 1'b0;				//使能信号拉低,等待写完成
			end
			
			WaitCMD1:begin
				us_cnt_clr <= 1'b0;			//计数器停止复位,开始计数
			end
			
			WriteHNE:begin
				us_cnt_clr <= 1'b1;
				en_write <= 1'b1;
				//取得数据高4位,背光 1(开) 	EN(清0)	RW 0 (写)	RS(cmd_data)0:命令,1:数据
				iic_data <= ((data & 8'hF0) | 8'h0C | cmd_data) & 8'hFB ;
			end
			WaitWHNE:begin
				en_write <= 1'b0;				//使能信号拉低,等待写完成
			end
			
			WriteLE:begin
				en_write <= 1'b1;
				//取得数据低4位,背光 1(开) 	EN(1)	RW 0 (写)	RS(cmd_data)0:命令,1:数据
				iic_data <= ((data & 8'h0F)<<4) | 8'h0C | cmd_data;
			end
			WaitWLE:begin
				en_write <= 1'b0;				//使能信号拉低,等待写完成
			end
			
			WaitCMD2:begin
				us_cnt_clr <= 1'b0;			//计数器停止复位,开始计数
			end
			
			WriteLNE:begin
				en_write <= 1'b1;
				us_cnt_clr <= 1'b1;
				//取得数据低4位,背光 1(开) 	EN(清0)	RW 0 (写)	RS(cmd_data)0:命令,1:数据
				iic_data <= (((data & 8'h0F)<<4) | 8'h0C | cmd_data) & 8'hFB ;
			end
			WaitWLNE:begin
				en_write <= 1'b0;				//使能信号拉低,等待写完成
			end
			
		endcase
	end
end

//状态流转
always @(posedge clk,negedge rst_n) begin
	if(!rst_n)
		state <= WaitEn;
	else
		state <= next_state;
end

//写一次数据或命令完成信号输出
assign done = (state == Done);

//例化iic写模块
myiic_writebyte myiic_writebyte_inst(
	.clk(clk),
	.rst_n(rst_n),
	.en_write(en_iicwrite),
	.data(iic_data),
	.sda(sda),
	.scl(scl),
	.sda_dir(sda_dir),
	.done(iic_done)
);

endmodule

三、模块仿真代码(testbench)

`timescale 1ns/1ns //仿真单位为1ns,精度为1ns

module lcd_write_cmd_data_tb();

	reg clk;
	reg rst_n;
	reg ena;
	reg [7:0]data;
	reg cmd_data;
	wire sda;
	wire scl;
	wire done;
	wire sda_dir;
	
	reg sda_in;
	
	assign sda = sda_dir ? 1'bz : sda_in;
	
	lcd_write_cmd_data lcd_write_cmd_data_inst(
		.clk(clk),
		.rst_n(rst_n),
		.ena(ena),
		.data(data),
		.cmd_data(cmd_data),
		.sda(sda),
		.scl(scl),
		.sda_dir(sda_dir),
		.done(done)
	);
	
	initial begin
		#0 	clk = 0;
				rst_n = 0;
				data = 8'b11010100;
				ena = 1;
				cmd_data = 0;
				sda_in = 1;
			
		#20	rst_n = 1;
		
		#2000 sda_in = 0;
	end
	
	always #5 clk = ~clk;

endmodule

四、仿真结果

           

  • 1
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
下面是一个简单的51单片机用IIC驱动LCD1602驱动代码示例: ``` #include <reg52.h> #define LCD1602_I2C_ADDR 0x27 // IIC通信地址 sbit SDA = P1^0; // SDA引脚 sbit SCL = P1^1; // SCL引脚 void delay_us(unsigned int us) // 微秒级延时函数 { while (us--) { _nop_(); _nop_(); _nop_(); _nop_(); } } void i2c_start(void) // 发送IIC起始信号 { SDA = 1; SCL = 1; delay_us(5); SDA = 0; delay_us(5); SCL = 0; } void i2c_stop(void) // 发送IIC停止信号 { SDA = 0; SCL = 1; delay_us(5); SDA = 1; delay_us(5); } void i2c_write_byte(unsigned char dat) // 向IIC总线发送一个字节数据 { unsigned char i; for (i = 0; i < 8; i++) { SDA = (dat & 0x80) >> 7; dat <<= 1; SCL = 1; delay_us(5); SCL = 0; delay_us(5); } } void lcd1602_write_cmd(unsigned char cmd) // 向LCD1602入指令 { i2c_start(); i2c_write_byte(LCD1602_I2C_ADDR << 1); i2c_write_byte(0x00); // 入指令 i2c_write_byte(cmd); i2c_stop(); } void lcd1602_write_data(unsigned char dat) // 向LCD1602数据 { i2c_start(); i2c_write_byte(LCD1602_I2C_ADDR << 1); i2c_write_byte(0x40); // 数据 i2c_write_byte(dat); i2c_stop(); } void lcd1602_init(void) // 初始化LCD1602 { lcd1602_write_cmd(0x38); // 8位数据总线,显示2行,5×7点阵字符 lcd1602_write_cmd(0x08); // 关闭显示 lcd1602_write_cmd(0x01); // 清屏 lcd1602_write_cmd(0x06); // 光标右移 lcd1602_write_cmd(0x0C); // 开启显示,光标不闪烁 } void lcd1602_display_string(unsigned char x, unsigned char y, unsigned char *str) // 在指定位置显示字符串 { unsigned char i; if (y == 0) { lcd1602_write_cmd(0x80 + x); // 第一行 } else { lcd1602_write_cmd(0xC0 + x); // 第行 } for (i = 0; str[i] != '\0'; i++) { lcd1602_write_data(str[i]); } } void main(void) { lcd1602_init(); // 初始化LCD1602 lcd1602_display_string(0, 0, "Hello, world!"); // 在第一行显示字符串 while (1); } ``` 该代码使用了51单片机的两个引脚进行IIC通信,通过向LCD1602入指令和数据来控制LCD1602的显示。其中,`lcd1602_init()`函数初始化LCD1602,`lcd1602_display_string()`函数在指定位置显示字符串,`i2c_start()`、`i2c_stop()`、`i2c_write_byte()`函数实现了IIC通信协议。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值