FPGA: LCD display from RS232 interface
After more than a month's of studying of verilog and fpga design, I am proud to present the result of my first mini-project. Controlling Spartan3A LCD display with RS232 serial interface from a host computer. The setup is very simple:
I used the 2 serial interface modules from fpga4fun.com (async_receiver and async_transmitter). I implemented the main serial to lcd control module and the lcd display module. The lcd display module implementation is especially gratifying after it was completed. I learnt a great deal about finite state machine implementation in verilog and how sequential/combinatorial logic work together. Here is the state machine of the lcd controller (drawn using qfsm):
As you can see, state 2,3,4,5 (I didn't use one-hot encoding) all require that certain signal lines driven at a certain value for a certain amount of time (or number of clks). To achieve such kind of effect, one need to combine a FSM with a counter. The technical details of the requirements can be found in Spartan3A revion D's user's guide (ug334.pdf) in the LCD section.
The implementation of the lcd controller follows:
minicom (linux host) <------> RS232 (J27) <-------> LCD (DISP1)
I used the 2 serial interface modules from fpga4fun.com (async_receiver and async_transmitter). I implemented the main serial to lcd control module and the lcd display module. The lcd display module implementation is especially gratifying after it was completed. I learnt a great deal about finite state machine implementation in verilog and how sequential/combinatorial logic work together. Here is the state machine of the lcd controller (drawn using qfsm):
As you can see, state 2,3,4,5 (I didn't use one-hot encoding) all require that certain signal lines driven at a certain value for a certain amount of time (or number of clks). To achieve such kind of effect, one need to combine a FSM with a counter. The technical details of the requirements can be found in Spartan3A revion D's user's guide (ug334.pdf) in the LCD section.
The implementation of the lcd controller follows:
module lcd_controller (clk, rst_n, data_ready, rx_data, lcd_rs, lcd_rw, lcd_e, lcd_4, lcd_5, lcd_6, lcd_7);
parameter k = 18;
// in register_input mode, the input doesn't have to stay valid
// while the character is being transmitted
parameter register_input = 1;
parameter clr = 8'h0A;
input clk; // synthesis attribute PERIOD clk "50 MHz"
input rst_n;
input data_ready;
input [7:0] rx_data;
output lcd_rs;
output lcd_rw;
output lcd_e;
output lcd_7;
output lcd_6;
output lcd_5;
output lcd_4;
reg lcd_e, lcd_rs, lcd_rw, lcd_7, lcd_6, lcd_5, lcd_4;
reg [k+8:0] count;
reg [6:0] lcd_code;
reg [2:0] state;
reg [2:0] next_state;
wire lcd_ready = (state==1);
// store rx_data locally
reg [7:0] lcd_dataReg;
always @(posedge clk) if(data_ready & lcd_ready) lcd_dataReg <= rx_data;
wire [7:0] lcd_dataD = register_input ? lcd_dataReg : rx_data;
// continuous assignment by default of wire type, clr key clears display
wire clear = (rx_data == clr)? 1:0;
//assign {lcd_e,lcd_rs,lcd_rw,lcd_7,lcd_6,lcd_5,lcd_4} = lcd_code;
// sequential logic
always @ (posedge clk or negedge rst_n)
begin
if(~rst_n)
begin
state <= 0;
next_state <= 0;
count <= 0;
lcd_code[6:0] <= 0;
end
else
state <= next_state;
end
always @ (posedge clk)
begin
case (state)
3'b000: count <= count + 1;
3'b001: count <= 0;
3'b010: count <= (count[4]? 0 : count + 1);
3'b011: count <= (count[5]? 0 : count + 1);
3'b100: count <= (count[4]? 0 : count + 1);
3'b101: count <= (count[10]? 0 : count + 1);
3'b110: count <= count + 1;
endcase
{lcd_e,lcd_rs,lcd_rw,lcd_7,lcd_6,lcd_5,lcd_4} <= lcd_code;
if(state == 0 || state == 6) lcd_e <= ^count[k+1:k];
end // sequential logic
// combinatorial logic
always @ (state or count or data_ready or clear) begin
case(state)
3'b000:
begin
if(count[k+5:k+2] == 12)
next_state = 3'b1; // idle_data/1
else
next_state = 0;
end
3'b001:
begin
if(data_ready) begin
if(clear)
next_state = 3'b110; // clear/6
else
next_state = 3'b10; // disp_hn/2
end
else
next_state = 3'b1; // idle_data/1
end
3'b010:
begin
if(count[4])
next_state = 3'b11; // idle_high/3
else
next_state = 3'b10; // disp_hn/3
end
3'b011:
begin
if(count[5])
next_state = 3'b100; // disp_ln/4
else
next_state = 3'b11; // idle_high/3
end
3'b100:
begin
if(count[4])
next_state = 3'b101; // wait/5
else
next_state = 3'b100; // disp_ln/4
end
3'b101:
begin
if(count[10])
next_state = 3'b1; // idle_data/1
else
next_state = 3'b101; // wait/5
end
3'b110:
begin
if(count[k+3:k+2] == 2)
next_state = 3'h1; // idle_data/1
else
next_state = 3'h6; // clear/6
end
endcase
end // combinatorial logic
// output logic
always @(state or count or lcd_dataD) begin
lcd_code <= 7'h00;
case(state)
3'b000:
begin
case (count[k+5:k+2])
0: lcd_code <= 7'h43; // power-on initialization
1: lcd_code <= 7'h43;
2: lcd_code <= 7'h43;
3: lcd_code <= 7'h42;
4: lcd_code <= 7'h42; // function set
5: lcd_code <= 7'h48;
6: lcd_code <= 7'h40; // entry mode set
7: lcd_code <= 7'h46;
8: lcd_code <= 7'h40; // display on/off control
9: lcd_code <= 7'h4C;
10: lcd_code <= 7'h40; // display clear
11: lcd_code <= 7'h41;
endcase
end
3'b001:
lcd_code <= 7'h00;
3'b010:
lcd_code <= {3'b110, lcd_dataD[7:4]};
3'b011:
lcd_code <= 7'b0110000;
3'b100:
lcd_code <= {3'b110, lcd_dataD[3:0]};
3'b101:
lcd_code <= 7'b0110000;
3'b110:
begin
case(count[k+2])
0: lcd_code <= 7'h40; // display clear
1: lcd_code <= 7'h41;
endcase
end
endcase
end // output logic
endmodule
<< Home