aboutsummaryrefslogtreecommitdiff
path: root/drivers/lcd/st7565.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/lcd/st7565.c')
-rw-r--r--drivers/lcd/st7565.c496
1 files changed, 496 insertions, 0 deletions
diff --git a/drivers/lcd/st7565.c b/drivers/lcd/st7565.c
new file mode 100644
index 000000000..49b13c00f
--- /dev/null
+++ b/drivers/lcd/st7565.c
@@ -0,0 +1,496 @@
1/*
2Copyright 2021
3
4This program is free software: you can redistribute it and/or modify
5it under the terms of the GNU General Public License as published by
6the Free Software Foundation, either version 2 of the License, or
7(at your option) any later version.
8
9This program is distributed in the hope that it will be useful,
10but WITHOUT ANY WARRANTY; without even the implied warranty of
11MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12GNU General Public License for more details.
13
14You should have received a copy of the GNU General Public License
15along with this program. If not, see <http://www.gnu.org/licenses/>.
16*/
17
18#include "st7565.h"
19
20#include <string.h>
21
22#include "keyboard.h"
23#include "progmem.h"
24#include "timer.h"
25#include "wait.h"
26
27#include ST7565_FONT_H
28
29// Fundamental Commands
30#define CONTRAST 0x81
31#define DISPLAY_ALL_ON 0xA5
32#define DISPLAY_ALL_ON_RESUME 0xA4
33#define NORMAL_DISPLAY 0xA6
34#define INVERT_DISPLAY 0xA7
35#define DISPLAY_ON 0xAF
36#define DISPLAY_OFF 0xAE
37#define NOP 0xE3
38
39// Addressing Setting Commands
40#define PAM_SETCOLUMN_LSB 0x00
41#define PAM_SETCOLUMN_MSB 0x10
42#define PAM_PAGE_ADDR 0xB0 // 0xb0 -- 0xb7
43
44// Hardware Configuration Commands
45#define DISPLAY_START_LINE 0x40
46#define SEGMENT_REMAP 0xA0
47#define SEGMENT_REMAP_INV 0xA1
48#define COM_SCAN_INC 0xC0
49#define COM_SCAN_DEC 0xC8
50#define LCD_BIAS_7 0xA3
51#define LCD_BIAS_9 0xA2
52#define RESISTOR_RATIO 0x20
53#define POWER_CONTROL 0x28
54
55// Misc defines
56#ifndef ST7565_BLOCK_COUNT
57# define ST7565_BLOCK_COUNT (sizeof(ST7565_BLOCK_TYPE) * 8)
58#endif
59#ifndef ST7565_BLOCK_SIZE
60# define ST7565_BLOCK_SIZE (ST7565_MATRIX_SIZE / ST7565_BLOCK_COUNT)
61#endif
62
63#define ST7565_ALL_BLOCKS_MASK (((((ST7565_BLOCK_TYPE)1 << (ST7565_BLOCK_COUNT - 1)) - 1) << 1) | 1)
64
65#define HAS_FLAGS(bits, flags) ((bits & flags) == flags)
66
67// Display buffer's is the same as the display memory layout
68// this is so we don't end up with rounding errors with
69// parts of the display unusable or don't get cleared correctly
70// and also allows for drawing & inverting
71uint8_t st7565_buffer[ST7565_MATRIX_SIZE];
72uint8_t * st7565_cursor;
73ST7565_BLOCK_TYPE st7565_dirty = 0;
74bool st7565_initialized = false;
75bool st7565_active = false;
76bool st7565_inverted = false;
77display_rotation_t st7565_rotation = DISPLAY_ROTATION_0;
78#if ST7565_TIMEOUT > 0
79uint32_t st7565_timeout;
80#endif
81#if ST7565_UPDATE_INTERVAL > 0
82uint16_t st7565_update_timeout;
83#endif
84
85// Flips the rendering bits for a character at the current cursor position
86static void InvertCharacter(uint8_t *cursor) {
87 const uint8_t *end = cursor + ST7565_FONT_WIDTH;
88 while (cursor < end) {
89 *cursor = ~(*cursor);
90 cursor++;
91 }
92}
93
94bool st7565_init(display_rotation_t rotation) {
95 setPinOutput(ST7565_A0_PIN);
96 writePinHigh(ST7565_A0_PIN);
97 setPinOutput(ST7565_RST_PIN);
98 writePinHigh(ST7565_RST_PIN);
99
100 st7565_rotation = st7565_init_user(rotation);
101
102 spi_init();
103 spi_start(ST7565_SS_PIN, false, 0, ST7565_SPI_CLK_DIVISOR);
104
105 st7565_reset();
106
107 st7565_send_cmd(LCD_BIAS_7);
108 if (!HAS_FLAGS(st7565_rotation, DISPLAY_ROTATION_180)) {
109 st7565_send_cmd(SEGMENT_REMAP);
110 st7565_send_cmd(COM_SCAN_DEC);
111 } else {
112 st7565_send_cmd(SEGMENT_REMAP_INV);
113 st7565_send_cmd(COM_SCAN_INC);
114 }
115 st7565_send_cmd(DISPLAY_START_LINE | 0x00);
116 st7565_send_cmd(CONTRAST);
117 st7565_send_cmd(ST7565_CONTRAST);
118 st7565_send_cmd(RESISTOR_RATIO | 0x01);
119 st7565_send_cmd(POWER_CONTROL | 0x04);
120 wait_ms(50);
121 st7565_send_cmd(POWER_CONTROL | 0x06);
122 wait_ms(50);
123 st7565_send_cmd(POWER_CONTROL | 0x07);
124 wait_ms(10);
125 st7565_send_cmd(DISPLAY_ON);
126 st7565_send_cmd(DISPLAY_ALL_ON_RESUME);
127 st7565_send_cmd(NORMAL_DISPLAY);
128
129 spi_stop();
130
131#if ST7565_TIMEOUT > 0
132 st7565_timeout = timer_read32() + ST7565_TIMEOUT;
133#endif
134
135 st7565_clear();
136 st7565_initialized = true;
137 st7565_active = true;
138 return true;
139}
140
141__attribute__((weak)) display_rotation_t st7565_init_user(display_rotation_t rotation) { return rotation; }
142
143void st7565_clear(void) {
144 memset(st7565_buffer, 0, sizeof(st7565_buffer));
145 st7565_cursor = &st7565_buffer[0];
146 st7565_dirty = ST7565_ALL_BLOCKS_MASK;
147}
148
149uint8_t crot(uint8_t a, int8_t n) {
150 const uint8_t mask = 0x7;
151 n &= mask;
152 return a << n | a >> (-n & mask);
153}
154
155void st7565_render(void) {
156 if (!st7565_initialized) {
157 return;
158 }
159
160 // Do we have work to do?
161 st7565_dirty &= ST7565_ALL_BLOCKS_MASK;
162 if (!st7565_dirty) {
163 return;
164 }
165
166 // Find first dirty block
167 uint8_t update_start = 0;
168 while (!(st7565_dirty & ((ST7565_BLOCK_TYPE)1 << update_start))) {
169 ++update_start;
170 }
171
172 // Calculate commands to set memory addressing bounds.
173 uint8_t start_page = ST7565_BLOCK_SIZE * update_start / ST7565_DISPLAY_WIDTH;
174 uint8_t start_column = ST7565_BLOCK_SIZE * update_start % ST7565_DISPLAY_WIDTH;
175 // IC has 132 segment drivers, for panels with less width we need to offset the starting column
176 if (HAS_FLAGS(st7565_rotation, DISPLAY_ROTATION_180)) {
177 start_column += (132 - ST7565_DISPLAY_WIDTH);
178 }
179
180 spi_start(ST7565_SS_PIN, false, 0, ST7565_SPI_CLK_DIVISOR);
181
182 st7565_send_cmd(PAM_PAGE_ADDR | start_page);
183 st7565_send_cmd(PAM_SETCOLUMN_LSB | ((ST7565_COLUMN_OFFSET + start_column) & 0x0f));
184 st7565_send_cmd(PAM_SETCOLUMN_MSB | ((ST7565_COLUMN_OFFSET + start_column) >> 4 & 0x0f));
185
186 st7565_send_data(&st7565_buffer[ST7565_BLOCK_SIZE * update_start], ST7565_BLOCK_SIZE);
187
188 // Turn on display if it is off
189 st7565_on();
190
191 // Clear dirty flag
192 st7565_dirty &= ~((ST7565_BLOCK_TYPE)1 << update_start);
193}
194
195void st7565_set_cursor(uint8_t col, uint8_t line) {
196 uint16_t index = line * ST7565_DISPLAY_WIDTH + col * ST7565_FONT_WIDTH;
197
198 // Out of bounds?
199 if (index >= ST7565_MATRIX_SIZE) {
200 index = 0;
201 }
202
203 st7565_cursor = &st7565_buffer[index];
204}
205
206void st7565_advance_page(bool clearPageRemainder) {
207 uint16_t index = st7565_cursor - &st7565_buffer[0];
208 uint8_t remaining = ST7565_DISPLAY_WIDTH - (index % ST7565_DISPLAY_WIDTH);
209
210 if (clearPageRemainder) {
211 // Remaining Char count
212 remaining = remaining / ST7565_FONT_WIDTH;
213
214 // Write empty character until next line
215 while (remaining--) st7565_write_char(' ', false);
216 } else {
217 // Next page index out of bounds?
218 if (index + remaining >= ST7565_MATRIX_SIZE) {
219 index = 0;
220 remaining = 0;
221 }
222
223 st7565_cursor = &st7565_buffer[index + remaining];
224 }
225}
226
227void st7565_advance_char(void) {
228 uint16_t nextIndex = st7565_cursor - &st7565_buffer[0] + ST7565_FONT_WIDTH;
229 uint8_t remainingSpace = ST7565_DISPLAY_WIDTH - (nextIndex % ST7565_DISPLAY_WIDTH);
230
231 // Do we have enough space on the current line for the next character
232 if (remainingSpace < ST7565_FONT_WIDTH) {
233 nextIndex += remainingSpace;
234 }
235
236 // Did we go out of bounds
237 if (nextIndex >= ST7565_MATRIX_SIZE) {
238 nextIndex = 0;
239 }
240
241 // Update cursor position
242 st7565_cursor = &st7565_buffer[nextIndex];
243}
244
245// Main handler that writes character data to the display buffer
246void st7565_write_char(const char data, bool invert) {
247 // Advance to the next line if newline
248 if (data == '\n') {
249 // Old source wrote ' ' until end of line...
250 st7565_advance_page(true);
251 return;
252 }
253
254 if (data == '\r') {
255 st7565_advance_page(false);
256 return;
257 }
258
259 // copy the current render buffer to check for dirty after
260 static uint8_t st7565_temp_buffer[ST7565_FONT_WIDTH];
261 memcpy(&st7565_temp_buffer, st7565_cursor, ST7565_FONT_WIDTH);
262
263 _Static_assert(sizeof(font) >= ((ST7565_FONT_END + 1 - ST7565_FONT_START) * ST7565_FONT_WIDTH), "ST7565_FONT_END references outside array");
264
265 // set the reder buffer data
266 uint8_t cast_data = (uint8_t)data; // font based on unsigned type for index
267 if (cast_data < ST7565_FONT_START || cast_data > ST7565_FONT_END) {
268 memset(st7565_cursor, 0x00, ST7565_FONT_WIDTH);
269 } else {
270 const uint8_t *glyph = &font[(cast_data - ST7565_FONT_START) * ST7565_FONT_WIDTH];
271 memcpy_P(st7565_cursor, glyph, ST7565_FONT_WIDTH);
272 }
273
274 // Invert if needed
275 if (invert) {
276 InvertCharacter(st7565_cursor);
277 }
278
279 // Dirty check
280 if (memcmp(&st7565_temp_buffer, st7565_cursor, ST7565_FONT_WIDTH)) {
281 uint16_t index = st7565_cursor - &st7565_buffer[0];
282 st7565_dirty |= ((ST7565_BLOCK_TYPE)1 << (index / ST7565_BLOCK_SIZE));
283 // Edgecase check if the written data spans the 2 chunks
284 st7565_dirty |= ((ST7565_BLOCK_TYPE)1 << ((index + ST7565_FONT_WIDTH - 1) / ST7565_BLOCK_SIZE));
285 }
286
287 // Finally move to the next char
288 st7565_advance_char();
289}
290
291void st7565_write(const char *data, bool invert) {
292 const char *end = data + strlen(data);
293 while (data < end) {
294 st7565_write_char(*data, invert);
295 data++;
296 }
297}
298
299void st7565_write_ln(const char *data, bool invert) {
300 st7565_write(data, invert);
301 st7565_advance_page(true);
302}
303
304void st7565_pan(bool left) {
305 uint16_t i = 0;
306 for (uint16_t y = 0; y < ST7565_DISPLAY_HEIGHT / 8; y++) {
307 if (left) {
308 for (uint16_t x = 0; x < ST7565_DISPLAY_WIDTH - 1; x++) {
309 i = y * ST7565_DISPLAY_WIDTH + x;
310 st7565_buffer[i] = st7565_buffer[i + 1];
311 }
312 } else {
313 for (uint16_t x = ST7565_DISPLAY_WIDTH - 1; x > 0; x--) {
314 i = y * ST7565_DISPLAY_WIDTH + x;
315 st7565_buffer[i] = st7565_buffer[i - 1];
316 }
317 }
318 }
319 st7565_dirty = ST7565_ALL_BLOCKS_MASK;
320}
321
322display_buffer_reader_t st7565_read_raw(uint16_t start_index) {
323 if (start_index > ST7565_MATRIX_SIZE) start_index = ST7565_MATRIX_SIZE;
324 display_buffer_reader_t ret_reader;
325 ret_reader.current_element = &st7565_buffer[start_index];
326 ret_reader.remaining_element_count = ST7565_MATRIX_SIZE - start_index;
327 return ret_reader;
328}
329
330void st7565_write_raw_byte(const char data, uint16_t index) {
331 if (index > ST7565_MATRIX_SIZE) index = ST7565_MATRIX_SIZE;
332 if (st7565_buffer[index] == data) return;
333 st7565_buffer[index] = data;
334 st7565_dirty |= ((ST7565_BLOCK_TYPE)1 << (index / ST7565_BLOCK_SIZE));
335}
336
337void st7565_write_raw(const char *data, uint16_t size) {
338 uint16_t cursor_start_index = st7565_cursor - &st7565_buffer[0];
339 if ((size + cursor_start_index) > ST7565_MATRIX_SIZE) size = ST7565_MATRIX_SIZE - cursor_start_index;
340 for (uint16_t i = cursor_start_index; i < cursor_start_index + size; i++) {
341 uint8_t c = *data++;
342 if (st7565_buffer[i] == c) continue;
343 st7565_buffer[i] = c;
344 st7565_dirty |= ((ST7565_BLOCK_TYPE)1 << (i / ST7565_BLOCK_SIZE));
345 }
346}
347
348void st7565_write_pixel(uint8_t x, uint8_t y, bool on) {
349 if (x >= ST7565_DISPLAY_WIDTH) {
350 return;
351 }
352 uint16_t index = x + (y / 8) * ST7565_DISPLAY_WIDTH;
353 if (index >= ST7565_MATRIX_SIZE) {
354 return;
355 }
356 uint8_t data = st7565_buffer[index];
357 if (on) {
358 data |= (1 << (y % 8));
359 } else {
360 data &= ~(1 << (y % 8));
361 }
362 if (st7565_buffer[index] != data) {
363 st7565_buffer[index] = data;
364 st7565_dirty |= ((ST7565_BLOCK_TYPE)1 << (index / ST7565_BLOCK_SIZE));
365 }
366}
367
368#if defined(__AVR__)
369void st7565_write_P(const char *data, bool invert) {
370 uint8_t c = pgm_read_byte(data);
371 while (c != 0) {
372 st7565_write_char(c, invert);
373 c = pgm_read_byte(++data);
374 }
375}
376
377void st7565_write_ln_P(const char *data, bool invert) {
378 st7565_write_P(data, invert);
379 st7565_advance_page(true);
380}
381
382void st7565_write_raw_P(const char *data, uint16_t size) {
383 uint16_t cursor_start_index = st7565_cursor - &st7565_buffer[0];
384 if ((size + cursor_start_index) > ST7565_MATRIX_SIZE) size = ST7565_MATRIX_SIZE - cursor_start_index;
385 for (uint16_t i = cursor_start_index; i < cursor_start_index + size; i++) {
386 uint8_t c = pgm_read_byte(data++);
387 if (st7565_buffer[i] == c) continue;
388 st7565_buffer[i] = c;
389 st7565_dirty |= ((ST7565_BLOCK_TYPE)1 << (i / ST7565_BLOCK_SIZE));
390 }
391}
392#endif // defined(__AVR__)
393
394bool st7565_on(void) {
395 if (!st7565_initialized) {
396 return st7565_active;
397 }
398
399#if ST7565_TIMEOUT > 0
400 st7565_timeout = timer_read32() + ST7565_TIMEOUT;
401#endif
402
403 if (!st7565_active) {
404 spi_start(ST7565_SS_PIN, false, 0, ST7565_SPI_CLK_DIVISOR);
405 st7565_send_cmd(DISPLAY_ON);
406 spi_stop();
407 st7565_active = true;
408 st7565_on_user();
409 }
410 return st7565_active;
411}
412
413__attribute__((weak)) void st7565_on_user(void) {}
414
415bool st7565_off(void) {
416 if (!st7565_initialized) {
417 return !st7565_active;
418 }
419
420 if (st7565_active) {
421 spi_start(ST7565_SS_PIN, false, 0, ST7565_SPI_CLK_DIVISOR);
422 st7565_send_cmd(DISPLAY_OFF);
423 spi_stop();
424 st7565_active = false;
425 st7565_off_user();
426 }
427 return !st7565_active;
428}
429
430__attribute__((weak)) void st7565_off_user(void) {}
431
432bool st7565_is_on(void) { return st7565_active; }
433
434bool st7565_invert(bool invert) {
435 if (!st7565_initialized) {
436 return st7565_inverted;
437 }
438
439 if (invert != st7565_inverted) {
440 spi_start(ST7565_SS_PIN, false, 0, ST7565_SPI_CLK_DIVISOR);
441 st7565_send_cmd(invert ? INVERT_DISPLAY : NORMAL_DISPLAY);
442 spi_stop();
443 st7565_inverted = invert;
444 }
445 return st7565_inverted;
446}
447
448uint8_t st7565_max_chars(void) { return ST7565_DISPLAY_WIDTH / ST7565_FONT_WIDTH; }
449
450uint8_t st7565_max_lines(void) { return ST7565_DISPLAY_HEIGHT / ST7565_FONT_HEIGHT; }
451
452void st7565_task(void) {
453 if (!st7565_initialized) {
454 return;
455 }
456
457#if ST7565_UPDATE_INTERVAL > 0
458 if (timer_elapsed(st7565_update_timeout) >= ST7565_UPDATE_INTERVAL) {
459 st7565_update_timeout = timer_read();
460 st7565_set_cursor(0, 0);
461 st7565_task_user();
462 }
463#else
464 st7565_set_cursor(0, 0);
465 st7565_task_user();
466#endif
467
468 // Smart render system, no need to check for dirty
469 st7565_render();
470
471 // Display timeout check
472#if ST7565_TIMEOUT > 0
473 if (st7565_active && timer_expired32(timer_read32(), st7565_timeout)) {
474 st7565_off();
475 }
476#endif
477}
478
479__attribute__((weak)) void st7565_task_user(void) {}
480
481void st7565_reset(void) {
482 writePinLow(ST7565_RST_PIN);
483 wait_ms(20);
484 writePinHigh(ST7565_RST_PIN);
485 wait_ms(20);
486}
487
488spi_status_t st7565_send_cmd(uint8_t cmd) {
489 writePinLow(ST7565_A0_PIN);
490 return spi_write(cmd);
491}
492
493spi_status_t st7565_send_data(uint8_t *data, uint16_t length) {
494 writePinHigh(ST7565_A0_PIN);
495 return spi_transmit(data, length);
496}