Meditation, The Art of Exploitation

Thinking? At last I have discovered it--thought; this alone is inseparable from me. I am, I exist--that is certain. But for how long? For as long as I am thinking. For it could be, that were I totally to cease from thinking, I should totally cease to exist....I am, then, in the strict sense only a thing that thinks.

Monday, April 07, 2008

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:


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