| 1 |
4 |
Agner |
//////////////////////////////////////////////////////////////////////////////////
|
| 2 |
|
|
// Engineer: Agner Fog
|
| 3 |
|
|
//
|
| 4 |
|
|
// Create Date: 2020-06-22
|
| 5 |
|
|
// Last modified: 2020-06-29
|
| 6 |
|
|
// Module Name: decoder
|
| 7 |
|
|
// Project Name: ForwardCom soft core
|
| 8 |
|
|
// Target Devices: Artix 7
|
| 9 |
|
|
// Tool Versions: Vivado v. 2020.1
|
| 10 |
|
|
// License: CERN-OHL-W v. 2 or later
|
| 11 |
|
|
// Description: Driver for LCD displays
|
| 12 |
|
|
// Two LCD displays with each 4 lines x 20 characters
|
| 13 |
|
|
//////////////////////////////////////////////////////////////////////////////////
|
| 14 |
|
|
|
| 15 |
|
|
`include "defines.vh"
|
| 16 |
|
|
|
| 17 |
|
|
module lcd
|
| 18 |
|
|
#(parameter numrows = 8, // number of lines of combined displays (2 - 8)
|
| 19 |
|
|
parameter numcolumns = 20, // number of characters per line
|
| 20 |
|
|
parameter rows_per_display = 4) // number of rows per display unit
|
| 21 |
|
|
(input clock, // system clock 100 MHz
|
| 22 |
|
|
input reset, // reset and clear
|
| 23 |
|
|
input [4:0] x, // column number (0 = left)
|
| 24 |
|
|
input [2:0] y, // row number (0 = top)
|
| 25 |
|
|
input [7:0] text[0:numcolumns-1], // text for one line
|
| 26 |
|
|
input [4:0] text_length, // length of text
|
| 27 |
|
|
input start, // start writing
|
| 28 |
|
|
input eol, // pad with spaces until end of line
|
| 29 |
|
|
output reg lcd_rs, // LCD RS pin
|
| 30 |
|
|
output reg [1:0] lcd_e, // enable pins for two LCD displays
|
| 31 |
|
|
output reg [3:0] lcd_data, // LCD data, 4 bit bus
|
| 32 |
|
|
output reg ready // finished writing. ready for next line
|
| 33 |
|
|
);
|
| 34 |
|
|
|
| 35 |
|
|
localparam count_bits = 14; // number of bits in clock divider counter
|
| 36 |
|
|
localparam count_max = (2**count_bits)-1;// maximum count
|
| 37 |
|
|
|
| 38 |
|
|
logic [7:0] rowaddress; // command for setting row address
|
| 39 |
|
|
reg [count_bits-1:0] counter = 0; // clock divider
|
| 40 |
|
|
reg [7:0] delay; // delay counter
|
| 41 |
|
|
reg [3:0] state = 0; // state machine for initialization
|
| 42 |
|
|
// 0 - 9: initialization sequence
|
| 43 |
|
|
// 10 - 11: set x,y position
|
| 44 |
|
|
// 12 - 13: write characters
|
| 45 |
|
|
// 15: finished
|
| 46 |
|
|
reg [7:0] text_buffer [0:numcolumns-1]; // copy of input text
|
| 47 |
|
|
reg [4:0] column; // text column
|
| 48 |
|
|
reg [2:0] row; // text row
|
| 49 |
|
|
reg [4:0] text_count; // count down characters in text
|
| 50 |
|
|
reg eol_save; // copy of eol
|
| 51 |
|
|
|
| 52 |
|
|
/* initialization sequence:
|
| 53 |
|
|
The display can receive data in 4-bit or 8-bit mode. We are using 4-bit mode,
|
| 54 |
|
|
sending 4 bits at a time, with only the upper four bits connected.
|
| 55 |
|
|
First, we send 8'H3x three times to get into 8-bit mode. Then 8'H2x to get
|
| 56 |
|
|
into 4-bit mode. The remaining numbers are 8-bit pairs:
|
| 57 |
|
|
8'H28: multi-line mode (the first needs a long delay)
|
| 58 |
|
|
8'H01: reset (needs long delay)
|
| 59 |
|
|
8'H0C: display on, no cursor, no blink
|
| 60 |
|
|
//8'H06 forward direction
|
| 61 |
|
|
*/
|
| 62 |
|
|
reg [3:0] initialization_sequence [10] = {3, 3, 3, 2, 2, 8, 0, 1, 0, 12 };
|
| 63 |
|
|
|
| 64 |
|
|
always_comb begin
|
| 65 |
|
|
// command to set row address
|
| 66 |
|
|
if (rows_per_display == 4) begin
|
| 67 |
|
|
case (row[1:0]) // 4 lines per display
|
| 68 |
|
|
0: rowaddress = 8'H80 + column;
|
| 69 |
|
|
1: rowaddress = 8'HC0 + column;
|
| 70 |
|
|
2: rowaddress = 8'H80 + numcolumns + column;
|
| 71 |
|
|
3: rowaddress = 8'HC0 + numcolumns + column;
|
| 72 |
|
|
endcase
|
| 73 |
|
|
end else begin
|
| 74 |
|
|
case (row[0]) // 2 lines per display
|
| 75 |
|
|
0: rowaddress = 8'H80 + column;
|
| 76 |
|
|
1: rowaddress = 8'HC0 + column;
|
| 77 |
|
|
endcase
|
| 78 |
|
|
end
|
| 79 |
|
|
end
|
| 80 |
|
|
|
| 81 |
|
|
always_ff @(posedge clock) begin
|
| 82 |
|
|
lcd_e <= 0;
|
| 83 |
|
|
|
| 84 |
|
|
if (reset) begin
|
| 85 |
|
|
// reset
|
| 86 |
|
|
counter <= 0;
|
| 87 |
|
|
delay <= 8'H80;
|
| 88 |
|
|
state <= 0;
|
| 89 |
|
|
|
| 90 |
|
|
end else if (start && state == 15) begin
|
| 91 |
|
|
// write command received
|
| 92 |
|
|
text_buffer <= text;
|
| 93 |
|
|
column <= x;
|
| 94 |
|
|
row <= y;
|
| 95 |
|
|
text_count <= text_length;
|
| 96 |
|
|
eol_save <= eol;
|
| 97 |
|
|
counter <= 0;
|
| 98 |
|
|
state <= 10;
|
| 99 |
|
|
|
| 100 |
|
|
end else begin
|
| 101 |
|
|
counter <= counter + 1; // 2**count_bits / 100MHz = 160 µs
|
| 102 |
|
|
if (state < 10) begin
|
| 103 |
|
|
|
| 104 |
|
|
// initialization sequence
|
| 105 |
|
|
lcd_rs <= 0;
|
| 106 |
|
|
if (delay > 0) begin
|
| 107 |
|
|
if (counter == count_max) delay <= delay - 1;
|
| 108 |
|
|
end else begin
|
| 109 |
|
|
lcd_data <= initialization_sequence[state];
|
| 110 |
|
|
lcd_e[0] <= counter[count_bits-1:count_bits-2] == 2'b01; // generate pulse for display unit 0
|
| 111 |
|
|
lcd_e[1] <= counter[count_bits-1:count_bits-2] == 2'b01; // generate pulse for display unit 1
|
| 112 |
|
|
if (counter == count_max) begin
|
| 113 |
|
|
if (state == 9) state <= 15; // finished
|
| 114 |
|
|
else state <= state + 1; // next state
|
| 115 |
|
|
delay <= 8'H10;
|
| 116 |
|
|
end
|
| 117 |
|
|
end
|
| 118 |
|
|
|
| 119 |
|
|
end else if (state < 12) begin
|
| 120 |
|
|
|
| 121 |
|
|
// set (x,y) position
|
| 122 |
|
|
lcd_rs <= 0;
|
| 123 |
|
|
if (delay > 0) begin
|
| 124 |
|
|
if (counter == count_max) delay <= delay - 1;
|
| 125 |
|
|
end else begin
|
| 126 |
|
|
lcd_data <= ~state[0] ? rowaddress[7:4] : rowaddress[3:0];
|
| 127 |
|
|
if (row < rows_per_display)
|
| 128 |
|
|
lcd_e[0] <= counter[count_bits-1:count_bits-2] == 2'b01; // generate pulse
|
| 129 |
|
|
else lcd_e[1] <= counter[count_bits-1:count_bits-2] == 2'b01; // generate pulse
|
| 130 |
|
|
if (counter == count_max) begin
|
| 131 |
|
|
state <= state + 1;
|
| 132 |
|
|
delay <= 8'H10;
|
| 133 |
|
|
end
|
| 134 |
|
|
end
|
| 135 |
|
|
|
| 136 |
|
|
end else if (state < 15) begin
|
| 137 |
|
|
|
| 138 |
|
|
// write characters
|
| 139 |
|
|
lcd_rs <= 1;
|
| 140 |
|
|
if (delay > 0) begin
|
| 141 |
|
|
if (counter == count_max) delay <= delay - 1;
|
| 142 |
|
|
end else begin
|
| 143 |
|
|
if (text_count > 0) begin
|
| 144 |
|
|
// write character
|
| 145 |
|
|
lcd_data <= ~state[0] ? text_buffer[0][7:4] : text_buffer[0][3:0];
|
| 146 |
|
|
if (row < rows_per_display)
|
| 147 |
|
|
lcd_e[0] <= counter[count_bits-1:count_bits-2] == 2'b01; // generate pulse
|
| 148 |
|
|
else lcd_e[1] <= counter[count_bits-1:count_bits-2] == 2'b01; // generate pulse
|
| 149 |
|
|
end else if (eol_save) begin
|
| 150 |
|
|
lcd_data <= ~state[0] ? 2 : 0; // write space
|
| 151 |
|
|
if (row < rows_per_display)
|
| 152 |
|
|
lcd_e[0] <= counter[count_bits-1:count_bits-2] == 2'b01; // generate pulse
|
| 153 |
|
|
else lcd_e[1] <= counter[count_bits-1:count_bits-2] == 2'b01; // generate pulse
|
| 154 |
|
|
end
|
| 155 |
|
|
|
| 156 |
|
|
if (counter == count_max) begin
|
| 157 |
|
|
if (state[0]) begin
|
| 158 |
|
|
column <= column + 1; // count up column index
|
| 159 |
|
|
if (text_count != 0) text_count <= text_count - 1; // count down number of characters
|
| 160 |
|
|
for (int i = 0; i < numcolumns-1; i++) begin
|
| 161 |
|
|
text_buffer[i] <= text_buffer[i+1]; // shift down to get next character
|
| 162 |
|
|
end
|
| 163 |
|
|
end
|
| 164 |
|
|
if (state[0] && (column == numcolumns-1 || (text_count == 0 && !eol_save))) begin
|
| 165 |
|
|
state <= 15; // finished
|
| 166 |
|
|
end else begin
|
| 167 |
|
|
state[0] <= ~state[0];
|
| 168 |
|
|
end
|
| 169 |
|
|
// delay <= 8'H01;
|
| 170 |
|
|
end
|
| 171 |
|
|
end
|
| 172 |
|
|
end else begin
|
| 173 |
|
|
// state = 15. finished
|
| 174 |
|
|
lcd_e <= 0;
|
| 175 |
|
|
end
|
| 176 |
|
|
end
|
| 177 |
|
|
if (state == 15) ready <= 1;
|
| 178 |
|
|
else ready <= 0;
|
| 179 |
|
|
|
| 180 |
|
|
end
|
| 181 |
|
|
|
| 182 |
|
|
|
| 183 |
|
|
endmodule
|