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
|