aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRyan <fauxpark@gmail.com>2021-06-10 17:16:09 +1000
committerGitHub <noreply@github.com>2021-06-10 17:16:09 +1000
commitb2fdd4874434ef6921a436fc82d9f24909c726f8 (patch)
tree0348596d5938326e95c09270d209556bbe60ba72
parentcfc7ee61c5cb9822a1195028681b928bbeac2fd3 (diff)
downloadqmk_firmware-b2fdd4874434ef6921a436fc82d9f24909c726f8.tar.gz
qmk_firmware-b2fdd4874434ef6921a436fc82d9f24909c726f8.zip
Add ST7565 LCD driver (#13089)
Co-authored-by: Joakim Tufvegren <jocke@barbanet.com>
-rw-r--r--common_features.mk8
-rw-r--r--docs/_summary.md1
-rw-r--r--docs/feature_st7565.md270
-rw-r--r--drivers/lcd/st7565.c479
-rw-r--r--drivers/lcd/st7565.h215
-rw-r--r--quantum/quantum.h4
-rw-r--r--tmk_core/common/keyboard.c18
7 files changed, 995 insertions, 0 deletions
diff --git a/common_features.mk b/common_features.mk
index 37ce928e2..b259af46c 100644
--- a/common_features.mk
+++ b/common_features.mk
@@ -587,6 +587,14 @@ ifeq ($(strip $(OLED_DRIVER_ENABLE)), yes)
587 SRC += oled_driver.c 587 SRC += oled_driver.c
588endif 588endif
589 589
590ifeq ($(strip $(ST7565_ENABLE)), yes)
591 OPT_DEFS += -DST7565_ENABLE
592 COMMON_VPATH += $(DRIVER_PATH)/oled # For glcdfont.h
593 COMMON_VPATH += $(DRIVER_PATH)/lcd
594 QUANTUM_LIB_SRC += spi_master.c
595 SRC += st7565.c
596endif
597
590include $(DRIVER_PATH)/qwiic/qwiic.mk 598include $(DRIVER_PATH)/qwiic/qwiic.mk
591 599
592ifeq ($(strip $(UCIS_ENABLE)), yes) 600ifeq ($(strip $(UCIS_ENABLE)), yes)
diff --git a/docs/_summary.md b/docs/_summary.md
index 9798ef512..4141e01e7 100644
--- a/docs/_summary.md
+++ b/docs/_summary.md
@@ -93,6 +93,7 @@
93 * Hardware Features 93 * Hardware Features
94 * Displays 94 * Displays
95 * [HD44780 LCD Controller](feature_hd44780.md) 95 * [HD44780 LCD Controller](feature_hd44780.md)
96 * [ST7565 LCD Driver](feature_st7565.md)
96 * [OLED Driver](feature_oled_driver.md) 97 * [OLED Driver](feature_oled_driver.md)
97 * Lighting 98 * Lighting
98 * [Backlight](feature_backlight.md) 99 * [Backlight](feature_backlight.md)
diff --git a/docs/feature_st7565.md b/docs/feature_st7565.md
new file mode 100644
index 000000000..7db0f9ac4
--- /dev/null
+++ b/docs/feature_st7565.md
@@ -0,0 +1,270 @@
1# ST7565 LCD Driver
2
3## Supported Hardware
4
5LCD modules using ST7565 driver IC, communicating over SPI.
6
7|Module |IC |Size |Notes |
8|------------------------------|-------|------|----------------------------------------------------------|
9|Newhaven Display NHD-C12832A1Z|ST7565R|128x32|Used by Ergodox Infinity; primary consumer of this feature|
10|Zolentech ZLE12864B |ST7565P|128x64|Requires contrast adjustment |
11
12## Usage
13
14To enable the feature, there are three steps. First, when compiling your keyboard, you'll need to add the following to your `rules.mk`:
15
16```make
17ST7565_ENABLE = yes
18```
19
20Then in your `keymap.c` file, implement the ST7565 task call. This example assumes your keymap has three layers named `_QWERTY`, `_FN` and `_ADJ`:
21
22```c
23#ifdef ST7565_ENABLE
24void st7565_task_user(void) {
25 // Host Keyboard Layer Status
26 st7565_write_P(PSTR("Layer: "), false);
27
28 switch (get_highest_layer(layer_state)) {
29 case _QWERTY:
30 st7565_write_P(PSTR("Default\n"), false);
31 break;
32 case _FN:
33 st7565_write_P(PSTR("FN\n"), false);
34 break;
35 case _ADJ:
36 st7565_write_P(PSTR("ADJ\n"), false);
37 break;
38 default:
39 // Or use the write_ln shortcut over adding '\n' to the end of your string
40 st7565_write_ln_P(PSTR("Undefined"), false);
41 }
42
43 // Host Keyboard LED Status
44 led_t led_state = host_keyboard_led_state();
45 st7565_write_P(led_state.num_lock ? PSTR("NUM ") : PSTR(" "), false);
46 st7565_write_P(led_state.caps_lock ? PSTR("CAP ") : PSTR(" "), false);
47 st7565_write_P(led_state.scroll_lock ? PSTR("SCR ") : PSTR(" "), false);
48}
49#endif
50```
51
52## Logo Example
53
54In the default font, certain ranges of characters are reserved for a QMK logo. To render this logo to the screen, use the following code example:
55
56```c
57static void render_logo(void) {
58 static const char PROGMEM qmk_logo[] = {
59 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8A, 0x8B, 0x8C, 0x8D, 0x8E, 0x8F, 0x90, 0x91, 0x92, 0x93, 0x94,
60 0xA0, 0xA1, 0xA2, 0xA3, 0xA4, 0xA5, 0xA6, 0xA7, 0xA8, 0xA9, 0xAA, 0xAB, 0xAC, 0xAD, 0xAE, 0xAF, 0xB0, 0xB1, 0xB2, 0xB3, 0xB4,
61 0xC0, 0xC1, 0xC2, 0xC3, 0xC4, 0xC5, 0xC6, 0xC7, 0xC8, 0xC9, 0xCA, 0xCB, 0xCC, 0xCD, 0xCE, 0xCF, 0xD0, 0xD1, 0xD2, 0xD3, 0xD4, 0x00
62 };
63
64 st7565_write_P(qmk_logo, false);
65}
66```
67
68## Buffer Read Example
69For some purposes, you may need to read the current state of the display buffer. The `st7565_read_raw` function can be used to safely read bytes from the buffer.
70
71In this example, calling `fade_display` in the `st7565_task_user` function will slowly fade away whatever is on the screen by turning random pixels off over time.
72```c
73//Setup some mask which can be or'd with bytes to turn off pixels
74const uint8_t single_bit_masks[8] = {127, 191, 223, 239, 247, 251, 253, 254};
75
76static void fade_display(void) {
77 //Define the reader structure
78 display_buffer_reader_t reader;
79 uint8_t buff_char;
80 if (random() % 30 == 0) {
81 srand(timer_read());
82 // Fetch a pointer for the buffer byte at index 0. The return structure
83 // will have the pointer and the number of bytes remaining from this
84 // index position if we want to perform a sequential read by
85 // incrementing the buffer pointer
86 reader = st7565_read_raw(0);
87 //Loop over the remaining buffer and erase pixels as we go
88 for (uint16_t i = 0; i < reader.remaining_element_count; i++) {
89 //Get the actual byte in the buffer by dereferencing the pointer
90 buff_char = *reader.current_element;
91 if (buff_char != 0) {
92 st7565_write_raw_byte(buff_char & single_bit_masks[rand() % 8], i);
93 }
94 //increment the pointer to fetch a new byte during the next loop
95 reader.current_element++;
96 }
97 }
98}
99```
100
101## Other Examples
102
103In split keyboards, it is very common to have two displays that each render different content and are oriented or flipped differently. You can do this by switching which content to render by using the return value from `is_keyboard_master()` or `is_keyboard_left()` found in `split_util.h`, e.g:
104
105```c
106#ifdef ST7565_ENABLE
107display_rotation_t st7565_init_user(display_rotation_t rotation) {
108 if (!is_keyboard_master()) {
109 return DISPLAY_ROTATION_180; // flips the display 180 degrees if offhand
110 }
111
112 return rotation;
113}
114
115void st7565_task_user(void) {
116 if (is_keyboard_master()) {
117 render_status(); // Renders the current keyboard state (layer, lock, caps, scroll, etc)
118 } else {
119 render_logo(); // Renders a static logo
120 }
121}
122#endif
123```
124
125## Basic Configuration
126
127|Define |Default |Description |
128|------------------------|--------------|-----------------------------------------------------------------------------------------------------|
129|`ST7565_A0_PIN` |*Not defined* |(Required) The GPIO connected to the display's A0 (data/command) pin |
130|`ST7565_RST_PIN` |*Not defined* |(Required) The GPIO connected to the display's reset pin |
131|`ST7565_SS_PIN` |*Not defined* |(Required) The GPIO connected to the display's slave select pin |
132|`ST7565_SPI_CLK_DIVISOR`|`4` |The SPI clock divisor to use |
133|`ST7565_FONT_H` |`"glcdfont.c"`|The font code file to use for custom fonts |
134|`ST7565_FONT_START` |`0` |The starting character index for custom fonts |
135|`ST7565_FONT_END` |`223` |The ending character index for custom fonts |
136|`ST7565_FONT_WIDTH` |`6` |The font width |
137|`ST7565_FONT_HEIGHT` |`8` |The font height (untested) |
138|`ST7565_TIMEOUT` |`60000` |Turns off the screen after 60000ms of keyboard inactivity. Helps reduce burn-in. Set to 0 to disable.|
139|`ST7565_COLUMN_OFFSET` |`0` |Shift output to the right this many pixels. |
140|`ST7565_CONTRAST` |`32` |The default contrast level of the display, from 0 to 255. |
141|`ST7565_UPDATE_INTERVAL`|`0` |Set the time interval for updating the display in ms. This will improve the matrix scan rate. |
142
143## Custom sized displays
144
145The default display size for this feature is 128x32 and all necessary defines are precalculated with that in mind.
146
147|Define |Default |Description |
148|-----------------------|----------|-----------------------------------------------------------------------------------------------------------|
149|`ST7565_DISPLAY_WIDTH` |`128` |The width of the display. |
150|`ST7565_DISPLAY_HEIGHT`|`32` |The height of the display. |
151|`ST7565_MATRIX_SIZE` |`512` |The local buffer size to allocate.<br>`(ST7565_DISPLAY_HEIGHT / 8 * ST7565_DISPLAY_WIDTH)`. |
152|`ST7565_BLOCK_TYPE` |`uint16_t`|The unsigned integer type to use for dirty rendering. |
153|`ST7565_BLOCK_COUNT` |`16` |The number of blocks the display is divided into for dirty rendering.<br>`(sizeof(ST7565_BLOCK_TYPE) * 8)`.|
154|`ST7565_BLOCK_SIZE` |`32` |The size of each block for dirty rendering<br>`(ST7565_MATRIX_SIZE / ST7565_BLOCK_COUNT)`. |
155
156## API
157
158```c
159// Rotation enum values are flags
160typedef enum {
161 DISPLAY_ROTATION_0,
162 DISPLAY_ROTATION_180
163} display_rotation_t;
164
165// Initialize the display, rotating the rendered output based on the define passed in.
166// Returns true if the was initialized successfully
167bool st7565_init(display_rotation_t rotation);
168
169// Called at the start of st7565_init, weak function overridable by the user
170// rotation - the value passed into st7565_init
171// Return new display_rotation_t if you want to override default rotation
172display_rotation_t st7565_init_user(display_rotation_t rotation);
173
174// Clears the display buffer, resets cursor position to 0, and sets the buffer to dirty for rendering
175void st7565_clear(void);
176
177// Renders the dirty chunks of the buffer to display
178void st7565_render(void);
179
180// Moves cursor to character position indicated by column and line, wraps if out of bounds
181// Max column denoted by 'st7565_max_chars()' and max lines by 'st7565_max_lines()' functions
182void st7565_set_cursor(uint8_t col, uint8_t line);
183
184// Advances the cursor to the next page, writing ' ' if true
185// Wraps to the begining when out of bounds
186void st7565_advance_page(bool clearPageRemainder);
187
188// Moves the cursor forward 1 character length
189// Advance page if there is not enough room for the next character
190// Wraps to the begining when out of bounds
191void st7565_advance_char(void);
192
193// Writes a single character to the buffer at current cursor position
194// Advances the cursor while writing, inverts the pixels if true
195// Main handler that writes character data to the display buffer
196void st7565_write_char(const char data, bool invert);
197
198// Writes a string to the buffer at current cursor position
199// Advances the cursor while writing, inverts the pixels if true
200void st7565_write(const char *data, bool invert);
201
202// Writes a string to the buffer at current cursor position
203// Advances the cursor while writing, inverts the pixels if true
204// Advances the cursor to the next page, wiring ' ' to the remainder of the current page
205void st7565_write_ln(const char *data, bool invert);
206
207// Pans the buffer to the right (or left by passing true) by moving contents of the buffer
208// Useful for moving the screen in preparation for new drawing
209void st7565_pan(bool left);
210
211// Returns a pointer to the requested start index in the buffer plus remaining
212// buffer length as struct
213display_buffer_reader_t st7565_read_raw(uint16_t start_index);
214
215// Writes a string to the buffer at current cursor position
216void st7565_write_raw(const char *data, uint16_t size);
217
218// Writes a single byte into the buffer at the specified index
219void st7565_write_raw_byte(const char data, uint16_t index);
220
221// Sets a specific pixel on or off
222// Coordinates start at top-left and go right and down for positive x and y
223void st7565_write_pixel(uint8_t x, uint8_t y, bool on);
224
225// Writes a PROGMEM string to the buffer at current cursor position
226// Advances the cursor while writing, inverts the pixels if true
227// Remapped to call 'void st7565_write(const char *data, bool invert);' on ARM
228void st7565_write_P(const char *data, bool invert);
229
230// Writes a PROGMEM string to the buffer at current cursor position
231// Advances the cursor while writing, inverts the pixels if true
232// Advances the cursor to the next page, wiring ' ' to the remainder of the current page
233// Remapped to call 'void st7565_write_ln(const char *data, bool invert);' on ARM
234void st7565_write_ln_P(const char *data, bool invert);
235
236// Writes a PROGMEM string to the buffer at current cursor position
237void st7565_write_raw_P(const char *data, uint16_t size);
238
239// Can be used to manually turn on the screen if it is off
240// Returns true if the screen was on or turns on
241bool st7565_on(void);
242
243// Called when st7565_on() turns on the screen, weak function overridable by the user
244// Not called if the screen is already on
245void st7565_on_user(void);
246
247// Can be used to manually turn off the screen if it is on
248// Returns true if the screen was off or turns off
249bool st7565_off(void);
250
251// Called when st7565_off() turns off the screen, weak function overridable by the user
252// Not called if the screen is already off
253void st7565_off_user(void);
254
255// Returns true if the screen is currently on, false if it is
256// not
257bool st7565_is_on(void);
258
259// Basically it's st7565_render, but with timeout management and st7565_task_user calling!
260void st7565_task(void);
261
262// Called at the start of st7565_task, weak function overridable by the user
263void st7565_task_user(void);
264
265// Returns the maximum number of characters that will fit on a line
266uint8_t st7565_max_chars(void);
267
268// Returns the maximum number of lines that will fit on the display
269uint8_t st7565_max_lines(void);
270```
diff --git a/drivers/lcd/st7565.c b/drivers/lcd/st7565.c
new file mode 100644
index 000000000..4b4891ce7
--- /dev/null
+++ b/drivers/lcd/st7565.c
@@ -0,0 +1,479 @@
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 DISPLAY_ON 0xAF
35#define DISPLAY_OFF 0xAE
36#define NOP 0xE3
37
38// Addressing Setting Commands
39#define PAM_SETCOLUMN_LSB 0x00
40#define PAM_SETCOLUMN_MSB 0x10
41#define PAM_PAGE_ADDR 0xB0 // 0xb0 -- 0xb7
42
43// Hardware Configuration Commands
44#define DISPLAY_START_LINE 0x40
45#define SEGMENT_REMAP 0xA0
46#define SEGMENT_REMAP_INV 0xA1
47#define COM_SCAN_INC 0xC0
48#define COM_SCAN_DEC 0xC8
49#define LCD_BIAS_7 0xA3
50#define LCD_BIAS_9 0xA2
51#define RESISTOR_RATIO 0x20
52#define POWER_CONTROL 0x28
53
54// Misc defines
55#ifndef ST7565_BLOCK_COUNT
56# define ST7565_BLOCK_COUNT (sizeof(ST7565_BLOCK_TYPE) * 8)
57#endif
58#ifndef ST7565_BLOCK_SIZE
59# define ST7565_BLOCK_SIZE (ST7565_MATRIX_SIZE / ST7565_BLOCK_COUNT)
60#endif
61
62#define ST7565_ALL_BLOCKS_MASK (((((ST7565_BLOCK_TYPE)1 << (ST7565_BLOCK_COUNT - 1)) - 1) << 1) | 1)
63
64#define HAS_FLAGS(bits, flags) ((bits & flags) == flags)
65
66// Display buffer's is the same as the display memory layout
67// this is so we don't end up with rounding errors with
68// parts of the display unusable or don't get cleared correctly
69// and also allows for drawing & inverting
70uint8_t st7565_buffer[ST7565_MATRIX_SIZE];
71uint8_t * st7565_cursor;
72ST7565_BLOCK_TYPE st7565_dirty = 0;
73bool st7565_initialized = false;
74bool st7565_active = false;
75display_rotation_t st7565_rotation = DISPLAY_ROTATION_0;
76#if ST7565_TIMEOUT > 0
77uint32_t st7565_timeout;
78#endif
79#if ST7565_UPDATE_INTERVAL > 0
80uint16_t st7565_update_timeout;
81#endif
82
83// Flips the rendering bits for a character at the current cursor position
84static void InvertCharacter(uint8_t *cursor) {
85 const uint8_t *end = cursor + ST7565_FONT_WIDTH;
86 while (cursor < end) {
87 *cursor = ~(*cursor);
88 cursor++;
89 }
90}
91
92bool st7565_init(display_rotation_t rotation) {
93 setPinOutput(ST7565_A0_PIN);
94 writePinHigh(ST7565_A0_PIN);
95 setPinOutput(ST7565_RST_PIN);
96 writePinHigh(ST7565_RST_PIN);
97
98 st7565_rotation = st7565_init_user(rotation);
99
100 spi_init();
101 spi_start(ST7565_SS_PIN, false, 0, ST7565_SPI_CLK_DIVISOR);
102
103 st7565_reset();
104
105 st7565_send_cmd(LCD_BIAS_7);
106 if (!HAS_FLAGS(st7565_rotation, DISPLAY_ROTATION_180)) {
107 st7565_send_cmd(SEGMENT_REMAP);
108 st7565_send_cmd(COM_SCAN_DEC);
109 } else {
110 st7565_send_cmd(SEGMENT_REMAP_INV);
111 st7565_send_cmd(COM_SCAN_INC);
112 }
113 st7565_send_cmd(DISPLAY_START_LINE | 0x00);
114 st7565_send_cmd(CONTRAST);
115 st7565_send_cmd(ST7565_CONTRAST);
116 st7565_send_cmd(RESISTOR_RATIO | 0x01);
117 st7565_send_cmd(POWER_CONTROL | 0x04);
118 wait_ms(50);
119 st7565_send_cmd(POWER_CONTROL | 0x06);
120 wait_ms(50);
121 st7565_send_cmd(POWER_CONTROL | 0x07);
122 wait_ms(10);
123 st7565_send_cmd(DISPLAY_ON);
124 st7565_send_cmd(DISPLAY_ALL_ON_RESUME);
125 st7565_send_cmd(NORMAL_DISPLAY);
126
127 spi_stop();
128
129#if ST7565_TIMEOUT > 0
130 st7565_timeout = timer_read32() + ST7565_TIMEOUT;
131#endif
132
133 st7565_clear();
134 st7565_initialized = true;
135 st7565_active = true;
136 return true;
137}
138
139__attribute__((weak)) display_rotation_t st7565_init_user(display_rotation_t rotation) { return rotation; }
140
141void st7565_clear(void) {
142 memset(st7565_buffer, 0, sizeof(st7565_buffer));
143 st7565_cursor = &st7565_buffer[0];
144 st7565_dirty = ST7565_ALL_BLOCKS_MASK;
145}
146
147uint8_t crot(uint8_t a, int8_t n) {
148 const uint8_t mask = 0x7;
149 n &= mask;
150 return a << n | a >> (-n & mask);
151}
152
153void st7565_render(void) {
154 if (!st7565_initialized) {
155 return;
156 }
157
158 // Do we have work to do?
159 st7565_dirty &= ST7565_ALL_BLOCKS_MASK;
160 if (!st7565_dirty) {
161 return;
162 }
163
164 // Find first dirty block
165 uint8_t update_start = 0;
166 while (!(st7565_dirty & ((ST7565_BLOCK_TYPE)1 << update_start))) {
167 ++update_start;
168 }
169
170 // Calculate commands to set memory addressing bounds.
171 uint8_t start_page = ST7565_BLOCK_SIZE * update_start / ST7565_DISPLAY_WIDTH;
172 uint8_t start_column = ST7565_BLOCK_SIZE * update_start % ST7565_DISPLAY_WIDTH;
173 // IC has 132 segment drivers, for panels with less width we need to offset the starting column
174 if (HAS_FLAGS(st7565_rotation, DISPLAY_ROTATION_180)) {
175 start_column += (132 - ST7565_DISPLAY_WIDTH);
176 }
177
178 spi_start(ST7565_SS_PIN, false, 0, ST7565_SPI_CLK_DIVISOR);
179
180 st7565_send_cmd(PAM_PAGE_ADDR | start_page);
181 st7565_send_cmd(PAM_SETCOLUMN_LSB | ((ST7565_COLUMN_OFFSET + start_column) & 0x0f));
182 st7565_send_cmd(PAM_SETCOLUMN_MSB | ((ST7565_COLUMN_OFFSET + start_column) >> 4 & 0x0f));
183
184 st7565_send_data(&st7565_buffer[ST7565_BLOCK_SIZE * update_start], ST7565_BLOCK_SIZE);
185
186 // Turn on display if it is off
187 st7565_on();
188
189 // Clear dirty flag
190 st7565_dirty &= ~((ST7565_BLOCK_TYPE)1 << update_start);
191}
192
193void st7565_set_cursor(uint8_t col, uint8_t line) {
194 uint16_t index = line * ST7565_DISPLAY_WIDTH + col * ST7565_FONT_WIDTH;
195
196 // Out of bounds?
197 if (index >= ST7565_MATRIX_SIZE) {
198 index = 0;
199 }
200
201 st7565_cursor = &st7565_buffer[index];
202}
203
204void st7565_advance_page(bool clearPageRemainder) {
205 uint16_t index = st7565_cursor - &st7565_buffer[0];
206 uint8_t remaining = ST7565_DISPLAY_WIDTH - (index % ST7565_DISPLAY_WIDTH);
207
208 if (clearPageRemainder) {
209 // Remaining Char count
210 remaining = remaining / ST7565_FONT_WIDTH;
211
212 // Write empty character until next line
213 while (remaining--) st7565_write_char(' ', false);
214 } else {
215 // Next page index out of bounds?
216 if (index + remaining >= ST7565_MATRIX_SIZE) {
217 index = 0;
218 remaining = 0;
219 }
220
221 st7565_cursor = &st7565_buffer[index + remaining];
222 }
223}
224
225void st7565_advance_char(void) {
226 uint16_t nextIndex = st7565_cursor - &st7565_buffer[0] + ST7565_FONT_WIDTH;
227 uint8_t remainingSpace = ST7565_DISPLAY_WIDTH - (nextIndex % ST7565_DISPLAY_WIDTH);
228
229 // Do we have enough space on the current line for the next character
230 if (remainingSpace < ST7565_FONT_WIDTH) {
231 nextIndex += remainingSpace;
232 }
233
234 // Did we go out of bounds
235 if (nextIndex >= ST7565_MATRIX_SIZE) {
236 nextIndex = 0;
237 }
238
239 // Update cursor position
240 st7565_cursor = &st7565_buffer[nextIndex];
241}
242
243// Main handler that writes character data to the display buffer
244void st7565_write_char(const char data, bool invert) {
245 // Advance to the next line if newline
246 if (data == '\n') {
247 // Old source wrote ' ' until end of line...
248 st7565_advance_page(true);
249 return;
250 }
251
252 if (data == '\r') {
253 st7565_advance_page(false);
254 return;
255 }
256
257 // copy the current render buffer to check for dirty after
258 static uint8_t st7565_temp_buffer[ST7565_FONT_WIDTH];
259 memcpy(&st7565_temp_buffer, st7565_cursor, ST7565_FONT_WIDTH);
260
261 _Static_assert(sizeof(font) >= ((ST7565_FONT_END + 1 - ST7565_FONT_START) * ST7565_FONT_WIDTH), "ST7565_FONT_END references outside array");
262
263 // set the reder buffer data
264 uint8_t cast_data = (uint8_t)data; // font based on unsigned type for index
265 if (cast_data < ST7565_FONT_START || cast_data > ST7565_FONT_END) {
266 memset(st7565_cursor, 0x00, ST7565_FONT_WIDTH);
267 } else {
268 const uint8_t *glyph = &font[(cast_data - ST7565_FONT_START) * ST7565_FONT_WIDTH];
269 memcpy_P(st7565_cursor, glyph, ST7565_FONT_WIDTH);
270 }
271
272 // Invert if needed
273 if (invert) {
274 InvertCharacter(st7565_cursor);
275 }
276
277 // Dirty check
278 if (memcmp(&st7565_temp_buffer, st7565_cursor, ST7565_FONT_WIDTH)) {
279 uint16_t index = st7565_cursor - &st7565_buffer[0];
280 st7565_dirty |= ((ST7565_BLOCK_TYPE)1 << (index / ST7565_BLOCK_SIZE));
281 // Edgecase check if the written data spans the 2 chunks
282 st7565_dirty |= ((ST7565_BLOCK_TYPE)1 << ((index + ST7565_FONT_WIDTH - 1) / ST7565_BLOCK_SIZE));
283 }
284
285 // Finally move to the next char
286 st7565_advance_char();
287}
288
289void st7565_write(const char *data, bool invert) {
290 const char *end = data + strlen(data);
291 while (data < end) {
292 st7565_write_char(*data, invert);
293 data++;
294 }
295}
296
297void st7565_write_ln(const char *data, bool invert) {
298 st7565_write(data, invert);
299 st7565_advance_page(true);
300}
301
302void st7565_pan(bool left) {
303 uint16_t i = 0;
304 for (uint16_t y = 0; y < ST7565_DISPLAY_HEIGHT / 8; y++) {
305 if (left) {
306 for (uint16_t x = 0; x < ST7565_DISPLAY_WIDTH - 1; x++) {
307 i = y * ST7565_DISPLAY_WIDTH + x;
308 st7565_buffer[i] = st7565_buffer[i + 1];
309 }
310 } else {
311 for (uint16_t x = ST7565_DISPLAY_WIDTH - 1; x > 0; x--) {
312 i = y * ST7565_DISPLAY_WIDTH + x;
313 st7565_buffer[i] = st7565_buffer[i - 1];
314 }
315 }
316 }
317 st7565_dirty = ST7565_ALL_BLOCKS_MASK;
318}
319
320display_buffer_reader_t st7565_read_raw(uint16_t start_index) {
321 if (start_index > ST7565_MATRIX_SIZE) start_index = ST7565_MATRIX_SIZE;
322 display_buffer_reader_t ret_reader;
323 ret_reader.current_element = &st7565_buffer[start_index];
324 ret_reader.remaining_element_count = ST7565_MATRIX_SIZE - start_index;
325 return ret_reader;
326}
327
328void st7565_write_raw_byte(const char data, uint16_t index) {
329 if (index > ST7565_MATRIX_SIZE) index = ST7565_MATRIX_SIZE;
330 if (st7565_buffer[index] == data) return;
331 st7565_buffer[index] = data;
332 st7565_dirty |= ((ST7565_BLOCK_TYPE)1 << (index / ST7565_BLOCK_SIZE));
333}
334
335void st7565_write_raw(const char *data, uint16_t size) {
336 uint16_t cursor_start_index = st7565_cursor - &st7565_buffer[0];
337 if ((size + cursor_start_index) > ST7565_MATRIX_SIZE) size = ST7565_MATRIX_SIZE - cursor_start_index;
338 for (uint16_t i = cursor_start_index; i < cursor_start_index + size; i++) {
339 if (st7565_buffer[i] == data[i]) continue;
340 st7565_buffer[i] = data[i];
341 st7565_dirty |= ((ST7565_BLOCK_TYPE)1 << (i / ST7565_BLOCK_SIZE));
342 }
343}
344
345void st7565_write_pixel(uint8_t x, uint8_t y, bool on) {
346 if (x >= ST7565_DISPLAY_WIDTH) {
347 return;
348 }
349 uint16_t index = x + (y / 8) * ST7565_DISPLAY_WIDTH;
350 if (index >= ST7565_MATRIX_SIZE) {
351 return;
352 }
353 uint8_t data = st7565_buffer[index];
354 if (on) {
355 data |= (1 << (y % 8));
356 } else {
357 data &= ~(1 << (y % 8));
358 }
359 if (st7565_buffer[index] != data) {
360 st7565_buffer[index] = data;
361 st7565_dirty |= ((ST7565_BLOCK_TYPE)1 << (index / ST7565_BLOCK_SIZE));
362 }
363}
364
365#if defined(__AVR__)
366void st7565_write_P(const char *data, bool invert) {
367 uint8_t c = pgm_read_byte(data);
368 while (c != 0) {
369 st7565_write_char(c, invert);
370 c = pgm_read_byte(++data);
371 }
372}
373
374void st7565_write_ln_P(const char *data, bool invert) {
375 st7565_write_P(data, invert);
376 st7565_advance_page(true);
377}
378
379void st7565_write_raw_P(const char *data, uint16_t size) {
380 uint16_t cursor_start_index = st7565_cursor - &st7565_buffer[0];
381 if ((size + cursor_start_index) > ST7565_MATRIX_SIZE) size = ST7565_MATRIX_SIZE - cursor_start_index;
382 for (uint16_t i = cursor_start_index; i < cursor_start_index + size; i++) {
383 uint8_t c = pgm_read_byte(data++);
384 if (st7565_buffer[i] == c) continue;
385 st7565_buffer[i] = c;
386 st7565_dirty |= ((ST7565_BLOCK_TYPE)1 << (i / ST7565_BLOCK_SIZE));
387 }
388}
389#endif // defined(__AVR__)
390
391bool st7565_on(void) {
392 if (!st7565_initialized) {
393 return st7565_active;
394 }
395
396#if ST7565_TIMEOUT > 0
397 st7565_timeout = timer_read32() + ST7565_TIMEOUT;
398#endif
399
400 if (!st7565_active) {
401 spi_start(ST7565_SS_PIN, false, 0, ST7565_SPI_CLK_DIVISOR);
402 st7565_send_cmd(DISPLAY_ON);
403 spi_stop();
404 st7565_active = true;
405 st7565_on_user();
406 }
407 return st7565_active;
408}
409
410__attribute__((weak)) void st7565_on_user(void) {}
411
412bool st7565_off(void) {
413 if (!st7565_initialized) {
414 return !st7565_active;
415 }
416
417 if (st7565_active) {
418 spi_start(ST7565_SS_PIN, false, 0, ST7565_SPI_CLK_DIVISOR);
419 st7565_send_cmd(DISPLAY_OFF);
420 spi_stop();
421 st7565_active = false;
422 st7565_off_user();
423 }
424 return !st7565_active;
425}
426
427__attribute__((weak)) void st7565_off_user(void) {}
428
429bool st7565_is_on(void) { return st7565_active; }
430
431uint8_t st7565_max_chars(void) { return ST7565_DISPLAY_WIDTH / ST7565_FONT_WIDTH; }
432
433uint8_t st7565_max_lines(void) { return ST7565_DISPLAY_HEIGHT / ST7565_FONT_HEIGHT; }
434
435void st7565_task(void) {
436 if (!st7565_initialized) {
437 return;
438 }
439
440#if ST7565_UPDATE_INTERVAL > 0
441 if (timer_elapsed(st7565_update_timeout) >= ST7565_UPDATE_INTERVAL) {
442 st7565_update_timeout = timer_read();
443 st7565_set_cursor(0, 0);
444 st7565_task_user();
445 }
446#else
447 st7565_set_cursor(0, 0);
448 st7565_task_user();
449#endif
450
451 // Smart render system, no need to check for dirty
452 st7565_render();
453
454 // Display timeout check
455#if ST7565_TIMEOUT > 0
456 if (st7565_active && timer_expired32(timer_read32(), st7565_timeout)) {
457 st7565_off();
458 }
459#endif
460}
461
462__attribute__((weak)) void st7565_task_user(void) {}
463
464void st7565_reset(void) {
465 writePinLow(ST7565_RST_PIN);
466 wait_ms(20);
467 writePinHigh(ST7565_RST_PIN);
468 wait_ms(20);
469}
470
471spi_status_t st7565_send_cmd(uint8_t cmd) {
472 writePinLow(ST7565_A0_PIN);
473 return spi_write(cmd);
474}
475
476spi_status_t st7565_send_data(uint8_t *data, uint16_t length) {
477 writePinHigh(ST7565_A0_PIN);
478 return spi_transmit(data, length);
479}
diff --git a/drivers/lcd/st7565.h b/drivers/lcd/st7565.h
new file mode 100644
index 000000000..53cfc9a81
--- /dev/null
+++ b/drivers/lcd/st7565.h
@@ -0,0 +1,215 @@
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#pragma once
19
20#include <stdint.h>
21#include <stdbool.h>
22
23#include "spi_master.h"
24
25#ifndef ST7565_DISPLAY_WIDTH
26# define ST7565_DISPLAY_WIDTH 128
27#endif
28#ifndef ST7565_DISPLAY_HEIGHT
29# define ST7565_DISPLAY_HEIGHT 32
30#endif
31#ifndef ST7565_MATRIX_SIZE
32# define ST7565_MATRIX_SIZE (ST7565_DISPLAY_HEIGHT / 8 * ST7565_DISPLAY_WIDTH) // 1024 (compile time mathed)
33#endif
34#ifndef ST7565_BLOCK_TYPE
35# define ST7565_BLOCK_TYPE uint16_t
36#endif
37#ifndef ST7565_BLOCK_COUNT
38# define ST7565_BLOCK_COUNT (sizeof(ST7565_BLOCK_TYPE) * 8) // 32 (compile time mathed)
39#endif
40#ifndef ST7565_BLOCK_SIZE
41# define ST7565_BLOCK_SIZE (ST7565_MATRIX_SIZE / ST7565_BLOCK_COUNT) // 32 (compile time mathed)
42#endif
43
44// the column address corresponding to the first column in the display hardware
45#if !defined(ST7565_COLUMN_OFFSET)
46# define ST7565_COLUMN_OFFSET 0
47#endif
48
49// spi clock divisor
50#if !defined(ST7565_SPI_CLK_DIVISOR)
51# define ST7565_SPI_CLK_DIVISOR 4
52#endif
53
54// Custom font file to use
55#if !defined(ST7565_FONT_H)
56# define ST7565_FONT_H "glcdfont.c"
57#endif
58// unsigned char value of the first character in the font file
59#if !defined(ST7565_FONT_START)
60# define ST7565_FONT_START 0
61#endif
62// unsigned char value of the last character in the font file
63#if !defined(ST7565_FONT_END)
64# define ST7565_FONT_END 223
65#endif
66// Font render width
67#if !defined(ST7565_FONT_WIDTH)
68# define ST7565_FONT_WIDTH 6
69#endif
70// Font render height
71#if !defined(ST7565_FONT_HEIGHT)
72# define ST7565_FONT_HEIGHT 8
73#endif
74// Default contrast level
75#if !defined(ST7565_CONTRAST)
76# define ST7565_CONTRAST 32
77#endif
78
79#if !defined(ST7565_TIMEOUT)
80# if defined(ST7565_DISABLE_TIMEOUT)
81# define ST7565_TIMEOUT 0
82# else
83# define ST7565_TIMEOUT 60000
84# endif
85#endif
86
87#if !defined(ST7565_UPDATE_INTERVAL) && defined(SPLIT_KEYBOARD)
88# define ST7565_UPDATE_INTERVAL 50
89#endif
90
91typedef struct __attribute__((__packed__)) {
92 uint8_t *current_element;
93 uint16_t remaining_element_count;
94} display_buffer_reader_t;
95
96// Rotation enum values are flags
97typedef enum { DISPLAY_ROTATION_0, DISPLAY_ROTATION_180 } display_rotation_t;
98
99// Initialize the display, rotating the rendered output based on the define passed in.
100// Returns true if the display was initialized successfully
101bool st7565_init(display_rotation_t rotation);
102
103// Called at the start of st7565_init, weak function overridable by the user
104// rotation - the value passed into st7565_init
105// Return new display_rotation_t if you want to override default rotation
106display_rotation_t st7565_init_user(display_rotation_t rotation);
107
108// Clears the display buffer, resets cursor position to 0, and sets the buffer to dirty for rendering
109void st7565_clear(void);
110
111// Renders the dirty chunks of the buffer to display
112void st7565_render(void);
113
114// Moves cursor to character position indicated by column and line, wraps if out of bounds
115// Max column denoted by 'st7565_max_chars()' and max lines by 'st7565_max_lines()' functions
116void st7565_set_cursor(uint8_t col, uint8_t line);
117
118// Advances the cursor to the next page, writing ' ' if true
119// Wraps to the begining when out of bounds
120void st7565_advance_page(bool clearPageRemainder);
121
122// Moves the cursor forward 1 character length
123// Advance page if there is not enough room for the next character
124// Wraps to the begining when out of bounds
125void st7565_advance_char(void);
126
127// Writes a single character to the buffer at current cursor position
128// Advances the cursor while writing, inverts the pixels if true
129// Main handler that writes character data to the display buffer
130void st7565_write_char(const char data, bool invert);
131
132// Writes a string to the buffer at current cursor position
133// Advances the cursor while writing, inverts the pixels if true
134void st7565_write(const char *data, bool invert);
135
136// Writes a string to the buffer at current cursor position
137// Advances the cursor while writing, inverts the pixels if true
138// Advances the cursor to the next page, wiring ' ' to the remainder of the current page
139void st7565_write_ln(const char *data, bool invert);
140
141// Pans the buffer to the right (or left by passing true) by moving contents of the buffer
142// Useful for moving the screen in preparation for new drawing
143void st7565_pan(bool left);
144
145// Returns a pointer to the requested start index in the buffer plus remaining
146// buffer length as struct
147display_buffer_reader_t st7565_read_raw(uint16_t start_index);
148
149// Writes a string to the buffer at current cursor position
150void st7565_write_raw(const char *data, uint16_t size);
151
152// Writes a single byte into the buffer at the specified index
153void st7565_write_raw_byte(const char data, uint16_t index);
154
155// Sets a specific pixel on or off
156// Coordinates start at top-left and go right and down for positive x and y
157void st7565_write_pixel(uint8_t x, uint8_t y, bool on);
158
159#if defined(__AVR__)
160// Writes a PROGMEM string to the buffer at current cursor position
161// Advances the cursor while writing, inverts the pixels if true
162// Remapped to call 'void st7565_write(const char *data, bool invert);' on ARM
163void st7565_write_P(const char *data, bool invert);
164
165// Writes a PROGMEM string to the buffer at current cursor position
166// Advances the cursor while writing, inverts the pixels if true
167// Advances the cursor to the next page, wiring ' ' to the remainder of the current page
168// Remapped to call 'void st7565_write_ln(const char *data, bool invert);' on ARM
169void st7565_write_ln_P(const char *data, bool invert);
170
171// Writes a PROGMEM string to the buffer at current cursor position
172void st7565_write_raw_P(const char *data, uint16_t size);
173#else
174# define st7565_write_P(data, invert) st7565_write(data, invert)
175# define st7565_write_ln_P(data, invert) st7565_write_ln(data, invert)
176# define st7565_write_raw_P(data, size) st7565_write_raw(data, size)
177#endif // defined(__AVR__)
178
179// Can be used to manually turn on the screen if it is off
180// Returns true if the screen was on or turns on
181bool st7565_on(void);
182
183// Called when st7565_on() turns on the screen, weak function overridable by the user
184// Not called if the screen is already on
185void st7565_on_user(void);
186
187// Can be used to manually turn off the screen if it is on
188// Returns true if the screen was off or turns off
189bool st7565_off(void);
190
191// Called when st7565_off() turns off the screen, weak function overridable by the user
192// Not called if the screen is already off
193void st7565_off_user(void);
194
195// Returns true if the screen is currently on, false if it is
196// not
197bool st7565_is_on(void);
198
199// Basically it's st7565_render, but with timeout management and st7565_task_user calling!
200void st7565_task(void);
201
202// Called at the start of st7565_task, weak function overridable by the user
203void st7565_task_user(void);
204
205// Returns the maximum number of characters that will fit on a line
206uint8_t st7565_max_chars(void);
207
208// Returns the maximum number of lines that will fit on the display
209uint8_t st7565_max_lines(void);
210
211void st7565_reset(void);
212
213spi_status_t st7565_send_cmd(uint8_t cmd);
214
215spi_status_t st7565_send_data(uint8_t *data, uint16_t length);
diff --git a/quantum/quantum.h b/quantum/quantum.h
index e4a7c5723..66ba96fde 100644
--- a/quantum/quantum.h
+++ b/quantum/quantum.h
@@ -176,6 +176,10 @@ extern layer_state_t layer_state;
176# include "oled_driver.h" 176# include "oled_driver.h"
177#endif 177#endif
178 178
179#ifdef ST7565_ENABLE
180# include "st7565.h"
181#endif
182
179#ifdef DIP_SWITCH_ENABLE 183#ifdef DIP_SWITCH_ENABLE
180# include "dip_switch.h" 184# include "dip_switch.h"
181#endif 185#endif
diff --git a/tmk_core/common/keyboard.c b/tmk_core/common/keyboard.c
index 3d6092e71..249da85bd 100644
--- a/tmk_core/common/keyboard.c
+++ b/tmk_core/common/keyboard.c
@@ -85,6 +85,9 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
85#ifdef OLED_DRIVER_ENABLE 85#ifdef OLED_DRIVER_ENABLE
86# include "oled_driver.h" 86# include "oled_driver.h"
87#endif 87#endif
88#ifdef ST7565_ENABLE
89# include "st7565.h"
90#endif
88#ifdef VELOCIKEY_ENABLE 91#ifdef VELOCIKEY_ENABLE
89# include "velocikey.h" 92# include "velocikey.h"
90#endif 93#endif
@@ -306,6 +309,9 @@ void keyboard_init(void) {
306#ifdef OLED_DRIVER_ENABLE 309#ifdef OLED_DRIVER_ENABLE
307 oled_init(OLED_ROTATION_0); 310 oled_init(OLED_ROTATION_0);
308#endif 311#endif
312#ifdef ST7565_ENABLE
313 st7565_init(DISPLAY_ROTATION_0);
314#endif
309#ifdef PS2_MOUSE_ENABLE 315#ifdef PS2_MOUSE_ENABLE
310 ps2_mouse_init(); 316 ps2_mouse_init();
311#endif 317#endif
@@ -470,6 +476,18 @@ MATRIX_LOOP_END:
470# endif 476# endif
471#endif 477#endif
472 478
479#ifdef ST7565_ENABLE
480 st7565_task();
481# ifndef ST7565_DISABLE_TIMEOUT
482 // Wake up display if user is using those fabulous keys or spinning those encoders!
483# ifdef ENCODER_ENABLE
484 if (matrix_changed || encoders_changed) st7565_on();
485# else
486 if (matrix_changed) st7565_on();
487# endif
488# endif
489#endif
490
473#ifdef MOUSEKEY_ENABLE 491#ifdef MOUSEKEY_ENABLE
474 // mousekey repeat & acceleration 492 // mousekey repeat & acceleration
475 mousekey_task(); 493 mousekey_task();