weiqi7777

串口控制LCD1602显示

0
阅读(4026)

花了一个下午加一个晚上的时间,写了一个串口控制LCD1602显示程序。用的是virtex5的板子,高端霸气上档次的板子。

功能其实也很简单,就是串口发送什么数据,就将数据显示在LCD1602上面,同时串口把接收的数据给回发回来。

结构图:

clip_image002

说明:

1、 串口部分: 接收和发送串口数据。串口接收到的数据,发送给FIFO。当外部的串口发送使能,就将从FIFO读取的数据发送出去。

2、 FIFO: 作为接收串口数据的暂存数据存储器。因为LCD1602操作是比较慢的,所以不能直接将串口接收到的数据发送给LCD1602进行显示,否则,就会存在数据丢失,所以使用FIFO,先将串口接收到的数据暂存下来,然后在发送给LCD1602.

3、 FIFO控制器: 因为LCD1602处理数据是比较慢的,所以在LCD1602上一个数据还没有处理完的时候,FIFO不能提供下一个数据。所以就需要一个FIFO控制器,当检测到LCD1602处理完后,FIFO读取数据,同时使能显示,将从FIFO读取的数据显示在LCD1602上。另外还要使能串口发送,将FIFO取出的数据发送出去。

4、 LCD1602: 控制LCD1602显示数据。

结构图清楚后,下面就开始设计了。

首先是串口的设计,串口的设计就比较简单了。我设计的串口是全双工串口。发送和接收各自有自己的波特率生成器。

下面是端口列表:


module uart_top
    #(
	     parameter  baud_tx = 256000,
	     parameter  baud_rx = 256000
	)
    (
          input  			 clk,
	  input				 rst_n,
	  
	  input	uart_rxd,     //input  serial rxd data
	  input	 tx_start,     //input    start send data module signal
	  input  [7:0]     tx_data,      //input   8-bits send data
	  
	  output		 tx_finish,    //output  send mode finish
	  output		 rx_finish,    //output  receive mode finish
	  output		 uart_txd,     //output   serial txd data
     output	[7:0]       receive_data	//output   receive 8-bits data  
    );



然后是FIFO,这个是直接使用xilinx的IP。例化了一个数据宽度是8位,深度是64的FIFO。

FIFO控制器模块。这个也是比较简单。


module fifo_lcd(
		input					clk,
		input					rst_n,
		
		input					iempty,          //fifo empty signal . 1 mean FIFO is empty
		input	[7:0]		   ififo_data,		  //fifo read data
		
		input					iLCD_finish,	  //LCD finish siganl .  1 mean LCD operate finish
		
		output reg			oFIFO_rd_en,		//FIFO read enable signal .  1 mean read data from FIFO
		output [8:0]	   oLCD_data,			//data	to LCD to display 
		output reg			oLCD_start			// LCD operate enable signal , 1 mean start
    );
	 
	 
	 assign oLCD_data = {1'b1,ififo_data};
	 
	 localparam  idle_state    = 1'd0;
	 localparam  trans_state   = 1'd1;
	 
	 reg   state;
	 always@(posedge clk or negedge rst_n) begin
		if(!rst_n)
			begin
				state <= idle_state;
				oLCD_start <= 1'b0;
				oFIFO_rd_en <= 1'b0;
			end
		else
			begin
				case(state) 
				idle_state: begin
					if(iempty == 0)
						begin
							oFIFO_rd_en <= 'b1;
							state <= trans_state;
						end
					else
						oFIFO_rd_en <= 'b0;
				end
				trans_state: begin
					oFIFO_rd_en <= 'b0;
					oLCD_start <= 1'b1;
					if(iLCD_finish == 1)
						begin
							oLCD_start <= 1'b0;
							state <= idle_state;
						end
				end
				endcase
			end
	 
	 end
endmodule


主要就是一个两个状态的状态机。Idle_state和trans_state。

在idle_state,如果检测到FIFO不是空的话,就使能读取FIFO的使能信号,从FIFO中读取数据,然后状态跳转。

在treans_state,将读取FIFO的使能信号给失能。这样,就暂时不向FIFO中读取数据。使能LCD的使能信号,让LCD模块工作。

最后这个,才是重点的LCD1602模块。

这个模块包括两个部分,一个是底层的控制模块,即控制LCD_EN,LCD_RS_LCD_RW,LCD_DATA这几个信号。另外一个是控制发送数据的状态机。

至于底层的控制模块,这里贴出源代码,是将网上别人写的稍微进行修改的:


module LCD_Controller (    //    Host Side
    input     [7:0]      iDATA,		
	 input					 iRS,		

    input                iCLK,      
	 input					 iRST_N,		
	
    input                iStart,    
	 output reg 			 oDone,  	
						
    //    LCD Interface
    output [3:0]         LCD_DATA, 
    output               LCD_RW,		
    output    reg        LCD_EN,		
    output               LCD_RS      
						);
 //    CLK
localparam    CLK_Divide    =    50;


//    Internal Register
reg        [5:0]    Cont;   
reg        [2:0]    ST;		

localparam   delay_number = 'd100000;
//localparam delay_number = 'd10;

reg [16:0]  time_count;

/////////////////////////////////////////////
//    Only write to LCD, bypass iRS to LCD_RS
assign    LCD_DATA    =   (ST != 4) ? iDATA[7:4] : iDATA[3:0]; 
assign    LCD_RW        =    1'b0;
assign    LCD_RS        =    iRS;
/////////////////////////////////////////////

always@(posedge iCLK or negedge iRST_N)
begin
    if(!iRST_N)
    begin
        oDone    <=    1'b0;
        LCD_EN    <=    1'b0;
        Cont    <=    0;
        ST        <=    0;
		  time_count <= 'd0;
    end
    else
    begin
		case(ST)
		0:  begin
			oDone <= 1'b0;
			if(iStart == 1)
				ST    <=    'd1;    //    Wait Setup
			else
				ST    <=    'd0;
		end
		1:    begin
			if(time_count > delay_number)
				begin
					ST        <=    'd2;
					time_count <= 'd0;
				end
			else
				time_count <= time_count + 1'b1;
		end
		2:    begin                 //send high 4 bits
				if(Cont < CLK_Divide)
					Cont    <=    Cont+1'b1;
				else
					ST      <=    'd3;
				if( Cont > 10   &&   Cont < CLK_Divide / 2 + 10)
					LCD_EN <= 1'b1;
				else
					LCD_EN <= 1'b0;
		end
		3:    begin
				Cont    <=    'd0;
				ST        <=    'd4;
		end
		4:		begin						//send low 4 bits
				if(Cont < CLK_Divide)
					Cont    <=    Cont+1'b1;
				else
					ST        <=    'd5;
				if( Cont > 10   &&   Cont < CLK_Divide / 2 + 10)
					LCD_EN <= 1'b1;
				else
					LCD_EN <= 1'b0;
		end
		5:		begin
				oDone    <=    1'b1;
				Cont    <=    'd0;
				ST        <=    'd6;
		end
		6:		begin
				ST		<= 0;
				oDone <= 1'b0;
		end
		endcase
    end
end

endmodule


这里的LCD是4位模式的,所以需要发送两次,第一次发送高4位,第二次发送低4位。另外,因为没有进行检测忙的操作,所以在状态的1状态,进行了一个时间比较长的延时。因为FPGA中对于inout信号操作要稍微麻烦一点,这里简单一点,就没有检测忙信号,直接使用延时。

这个模块,当iStart为1的时候,开始工作,当oDone为1的时候,表示一个数据发送完毕。

然后是上层的状态机设计:

首先是定义状态:

localparam init_state = 3'd0; //

localparam write_1line_state = 3'd1; //

localparam wait_1line_state = 3'd2; //

localparam write_2line_cmd_state = 3'd3; //

localparam write_2line_state = 3'd4; //

localparam wait_2line_state = 3'd5; //

localparam wait_data_state = 3'd6;

localparam clear_state = 3'd7;

总共定义了8个状态:

Init_state: 对LCD1602初始化。就是发送初始化的一些命令。

clip_image003

Write_1line_state: 写第一行数据的状态。在这个状态中,写的数据是写在第一行。

Wait_1line_state: 写第一行的等待状态。在第一行中写数据后,就会跳到这个状态,如果超过一定时间,没有接收到使能信号,就说明数据写完了。然后就跳转到wait_data_state。

write_2line_cmd_state: 因为LCD一行只能显示16个字符,如果超过16个字符,就要写到下一行,但是在写到第二行之前,需要发送命令0Xc0.将光标移到第二行。

write_2line_state和wait_2line_state两个状态和写第一行的两个状态功能是一样的。

wait_data_state: 当数据写完之后,会到这个状态。如果接收到新的数据,就说明要进行新的数据显示了。就跳转到clear_state。

clear_state: 在这个状态,首先发送命令0x01,对屏幕进行清屏,因为要进行新的显示了。然后再发送0x80.将光标移到第一行。然后跳转到Write_1line_state,开始进行写数据。

状态机理清楚后,程序写起来就比较简单了。


//      9'h120        @    9'h140        `    9'h160
// !    9'h121        A    9'h141        a    9'h161
// "    9'h122        B    9'h142        b    9'h162
// #    9'h123        C    9'h143        c    9'h163
// $    9'h124        D    9'h144        d    9'h164
// %    9'h125        E    9'h145        e    9'h165
// &    9'h126        F    9'h146        f    9'h166
// '    9'h127        G    9'h147        g    9'h167
// (    9'h128        H    9'h148        h    9'h168
// )    9'h129        I    9'h149        i    9'h169
// *    9'h12A        J    9'h14A        j    9'h16A
// +    9'h12B        K    9'h14B        k    9'h16B
// ,    9'h12C        L    9'h14C        l    9'h16C
// -    9'h12D        M    9'h14D        m    9'h16D
// .    9'h12E        N    9'h14E        n    9'h16E
// /    9'h12F        O    9'h14F        o    9'h16F
                            
// 0    9'h130        P    9'h150        p    9'h170
// 1    9'h131        Q    9'h151        q    9'h171
// 2    9'h132        R    9'h152        r    9'h172
// 3    9'h133        S    9'h153        s    9'h173
// 4    9'h134        T    9'h154        t    9'h174
// 5    9'h135        U    9'h155        u    9'h175
// 6    9'h136        V    9'h156        v    9'h176
// 7    9'h137        W    9'h157        w    9'h177
// 8    9'h138        X    9'h158        x    9'h178
// 9    9'h139        Y    9'h159        y    9'h179
// :    9'h13A        Z    9'h15A        z    9'h17A
// ;    9'h13B        [    9'h15B        {    9'h17B
// <    9'h13C        锟   9'h15C        |    9'h17C
// =    9'h13D        ]    9'h15D        }    9'h17D
// >    9'h13E        ^    9'h15E        鈫   9'h17E
// ?    9'h13F        _    9'h15F        鈫   9'h17F
module    LCD (    //    Host Side
   input               iCLK,   //鏃堕挓
	input 				  iRST_N, //澶嶄綅锛屼綆鐢靛钩鏈夋晥
	
	input				     istart,
	output 			     ofinish,	
	input  [8:0]		  idata,
	
   output  [3:0]   	  mLCD_DATA,  
	output 			     mLCD_RS,
	output  			     mLCD_RW,
	output				  mLCD_en
	);

	
	
//    Internal Wires/Registers
reg					mstart;
wire				   mfinish;
reg [8:0]			lcd_data;

//瀹氫箟4涓姸鎬  
localparam  init_state 				=     3'd0;		//
localparam 	write_1line_state		=	  3'd1;		//
localparam  wait_1line_state		=	  3'd2;		//
localparam  write_2line_cmd_state 	=	  3'd3;		//
localparam  write_2line_state		=  	  3'd4;		//
localparam  wait_2line_state		=	  3'd5;		//
localparam  wait_data_state			=	  3'd6;
localparam  clear_state				=	  3'd7;


localparam wait_number = 26'b11_1111_1111_1111_1110_1111_1111;
//localparam wait_number = 26'b111_1111_1111;


reg [2:0]  state;
reg [4:0]  lcd_counter;

reg [25:0] time_counter;

assign ofinish = (state == write_1line_state || state == write_2line_state ) ? mfinish : 1'b0;

always@(posedge iCLK or negedge iRST_N)  begin
    if(!iRST_N)
		begin
			state  <= init_state;
			mstart <= 1'b0;
			lcd_counter <= 'd0;
			time_counter <= 'd0;
		end
    else
		begin
			case(state)
			init_state:	  begin
				mstart <= 1'b1;
				if(mfinish == 1)
					begin
						if(lcd_counter >= 'd4)
							begin
								lcd_counter <= 'd0;
								state <= write_1line_state;
								mstart <= 1'b0;
							end
						else
							lcd_counter <= lcd_counter + 1'b1;
					end
			end
			write_1line_state:   begin
				mstart <= istart;
				time_counter <= 'd0;
				if( mfinish == 1 )
					begin
						if(lcd_counter > 14 )
							begin
								lcd_counter <= 'd0;
								state <= write_2line_cmd_state;
							end
						else
							begin
								lcd_counter <= lcd_counter + 1'b1;
								state <= wait_1line_state;
								mstart <= 1'b0;
							end
					end
			end
			wait_1line_state:   begin
				if (istart == 1)
					begin
						time_counter <= 'd0;
						state <= write_1line_state;
					end
				if(time_counter > wait_number)
					begin
						state <= wait_data_state;
					end
				else
					time_counter <= time_counter + 1'b1;
			end
			write_2line_cmd_state:	begin
				mstart <= 1'b1;
				if( mfinish ==1 )
					begin
						mstart <= 1'b0;
						state <= write_2line_state;
					end
			end
			write_2line_state:   begin
				mstart <= istart;
				time_counter <= 'd0;
				if( mfinish == 1 )
					begin
						if(lcd_counter > 14 )
							begin
								lcd_counter <= 'd0;
								state <= clear_state;
								lcd_counter <= 'd0;
							end
						else
							begin
								lcd_counter <= lcd_counter + 1'b1;
								state <= wait_2line_state;
								mstart <= 1'b0;
							end
					end
			end
			wait_2line_state:   begin			
				if (istart == 1)
					begin
						time_counter <= 'd0;
						state <= write_2line_state;
						mstart <= 1'b1;
					end
				if(time_counter > wait_number)
					begin
						state <= wait_data_state;
					end
				else
					time_counter <= time_counter + 1'b1;
			end
			wait_data_state: begin
				mstart <= 1'b0;
				if(istart == 1)
					state <= clear_state;
				lcd_counter <= 'd0;
			end
			clear_state: begin
				mstart <= 1'b1;
				if(mfinish == 1)
					begin
						if(lcd_counter >= 1)
							begin
								lcd_counter <= 'd0;
								state <= write_1line_state;
							end
						else
							lcd_counter <= lcd_counter + 1'b1;
					end
			end
			endcase
		end
end

always @ (*)
begin
    if(state == init_state) 
		case(lcd_counter)
		//    Initial
		0:    lcd_data    =    9'h028;//
		1:    lcd_data    =    9'h00C;//
		2:    lcd_data    =    9'h006;//
		3:    lcd_data    =    9'h001;//
		4:    lcd_data    =    9'h080;//
		5:		lcd_data		=    9'h16c;
		6:		lcd_data		=    9'h175;
		7:		lcd_data		=    9'h16A;
		8:		lcd_data		=	  9'h175;
		9:		lcd_data		=	  9'h16E;
		10:	lcd_data		=    9'h121;
		default: lcd_data = 9'h001;
		endcase
	else if(state == clear_state)
		case(lcd_counter)
		0:		lcd_data  =  9'h001;
		1:		lcd_data  =  9'h080;
		2:		lcd_data	 =  9'h080;
		default: lcd_data = 9'h001;
		endcase
	else if(state == write_2line_cmd_state)
		lcd_data = 9'h0c0;
	else
		lcd_data = idata;
end


LCD_Controller u1(    //    Host Side
       .iDATA  (lcd_data[7:0]),		//杈撳叆鍙戦€佺殑8浣嶆暟鎹
		 .iRS    (lcd_data[8]),		//杈撳叆鍛戒护鏁版嵁閫夋嫨淇″彿
         
       .iCLK   (iCLK),       //鏃堕挓
		 .iRST_N (iRST_N),		//澶嶄綅淇″彿	锛浣庣數骞虫湁鏁
		 
		 .iStart (mstart),     //LCD鎺у埗鍣ㄦ湁鏁堜俊鍙  1 LCD寮€鍚伐浣 0 LCD鍋滄宸ヤ綔
		 .oDone(mfinish),  	//LCD瀹屾垚淇″彿    1琛ㄧず瀹屾垚
	
         //    LCD Interface
         .LCD_DATA(mLCD_DATA[3:0]),  //LCD 4浣嶆暟鎹
         .LCD_RW  (mLCD_RW),		//LCD 璇诲啓淇″彿   0琛ㄧず鍐 1琛ㄧず璇
         .LCD_EN  (mLCD_en),		//LCD  璇诲啓浣胯兘淇″彿  涓嬮檷娌挎湁鏁
         .LCD_RS  (mLCD_RS)      //LCD  鍛戒护鏁版嵁閫夋嫨淇″彿    1閫夋嫨
						);

endmodule


然后在写一个顶层,将各个模块进行调用,然后连接信号即可。

最后贴上效果图:

clip_image005

开发板显示:

clip_image007

发送的数据和LCD上显示的数据一样。并且串口接收的数据和发送的数据一样。