aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--keyboards/handwired/onekey/keymaps/oled/keymap.c452
-rw-r--r--keyboards/handwired/onekey/keymaps/oled/readme.md23
-rw-r--r--keyboards/handwired/onekey/keymaps/oled/rules.mk2
3 files changed, 477 insertions, 0 deletions
diff --git a/keyboards/handwired/onekey/keymaps/oled/keymap.c b/keyboards/handwired/onekey/keymaps/oled/keymap.c
new file mode 100644
index 000000000..b6e66ace7
--- /dev/null
+++ b/keyboards/handwired/onekey/keymaps/oled/keymap.c
@@ -0,0 +1,452 @@
1/* Copyright 2020 Sergey Vlasov <sigprof@gmail.com>
2 *
3 * This program is free software: you can redistribute it and/or modify
4 * it under the terms of the GNU General Public License as published by
5 * the Free Software Foundation, either version 2 of the License, or
6 * (at your option) any later version.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 */
16
17#include QMK_KEYBOARD_H
18
19enum tap_dances {
20 TD_OLED,
21};
22
23enum oled_test_modes {
24 // Modes between TEST_FIRST and TEST_LAST (inclusive) can be switched with a keypress.
25 TEST_FIRST,
26 TEST_LOGO = TEST_FIRST,
27 TEST_CHARACTERS,
28 TEST_SLOW_UPDATE,
29 TEST_ALL_ON,
30 TEST_FRAME,
31 TEST_ALL_OFF,
32 TEST_FILL_HORZ_0,
33 TEST_FILL_HORZ_1,
34 TEST_FILL_VERT_0,
35 TEST_FILL_VERT_1,
36 TEST_FILL_CHECKERBOARD_1,
37 TEST_FILL_CHECKERBOARD_2,
38 TEST_FILL_CHECKERBOARD_4,
39 TEST_LAST = TEST_FILL_CHECKERBOARD_4,
40
41 // Special modes which are not reachable normally.
42 TEST_DRAW_ALWAYS_ON,
43 TEST_DRAW_ALWAYS_OFF,
44};
45
46static enum oled_test_modes test_mode = TEST_FIRST;
47
48static oled_rotation_t rotation = OLED_ROTATION_0;
49
50static bool scrolling;
51static uint8_t scrolling_speed;
52static bool need_update = true;
53static bool draw_always;
54static bool update_speed_test;
55static uint32_t update_speed_start_timer;
56static uint16_t update_speed_count;
57static bool restart_test;
58
59static void stop_scrolling(void) {
60 if (scrolling) {
61 oled_scroll_off();
62 scrolling = false;
63 }
64}
65
66static void dance_oled_finished(qk_tap_dance_state_t *state, void *user_data) {
67 switch (state->count) {
68 case 1:
69 if (state->pressed) {
70 // single hold - step through rotations
71 switch (rotation) {
72 case OLED_ROTATION_0:
73 rotation = OLED_ROTATION_90;
74 break;
75 case OLED_ROTATION_90:
76 rotation = OLED_ROTATION_180;
77 break;
78 case OLED_ROTATION_180:
79 rotation = OLED_ROTATION_270;
80 break;
81 default:
82 rotation = OLED_ROTATION_0;
83 break;
84 }
85 stop_scrolling();
86 oled_init(rotation);
87 } else {
88 // single tap - step through test modes
89 if (test_mode < TEST_LAST) {
90 ++test_mode;
91 } else {
92 test_mode = TEST_FIRST;
93 }
94 stop_scrolling();
95 oled_clear();
96 }
97 restart_test = true;
98 need_update = true;
99 break;
100
101 case 2:
102 if (state->pressed) {
103 // tap + hold - change scrolling speed
104 scrolling_speed = (scrolling_speed + 1) % 8;
105 stop_scrolling();
106 oled_scroll_set_speed(scrolling_speed);
107 // Cannot reactivate scrolling here, because oled_scroll_off()
108 // marks the whole display as dirty, and oled_scroll_left()
109 // silently does nothing if either the display is dirty or
110 // scrolling is already active.
111 } else {
112 // double tap - toggle scrolling
113 if (!scrolling) {
114 scrolling = true;
115 oled_scroll_left();
116 } else {
117 scrolling = false;
118 oled_scroll_off();
119 }
120 }
121 need_update = true;
122 break;
123
124 case 3:
125 if (state->pressed) {
126 // double tap + hold - toggle `draw_always`
127 draw_always = !draw_always;
128 if (draw_always) {
129 test_mode = TEST_DRAW_ALWAYS_ON;
130 } else {
131 test_mode = TEST_DRAW_ALWAYS_OFF;
132 }
133 stop_scrolling();
134 oled_clear();
135 restart_test = true;
136 need_update = true;
137 } else {
138 // triple tap - toggle update speed test
139 update_speed_test = !update_speed_test;
140 if (update_speed_test) {
141 stop_scrolling();
142 update_speed_start_timer = timer_read32();
143 update_speed_count = 0;
144 }
145 }
146 break;
147 default:
148 break;
149 }
150}
151
152qk_tap_dance_action_t tap_dance_actions[] = {[TD_OLED] = ACTION_TAP_DANCE_FN(dance_oled_finished)};
153
154const uint16_t PROGMEM keymaps[][MATRIX_ROWS][MATRIX_COLS] = {LAYOUT_ortho_1x1(TD(TD_OLED))};
155
156// `bool oled_is_dirty(void)` does not exist at the moment
157extern OLED_BLOCK_TYPE oled_dirty;
158
159static inline uint8_t pixel_width(void) {
160 if (!(rotation & OLED_ROTATION_90)) {
161 return OLED_DISPLAY_WIDTH;
162 }
163 return OLED_DISPLAY_HEIGHT;
164}
165
166static inline uint8_t pixel_height(void) {
167 if (!(rotation & OLED_ROTATION_90)) {
168 return OLED_DISPLAY_HEIGHT;
169 }
170 return OLED_DISPLAY_WIDTH;
171}
172
173// Draw the QMK logo at the top left corner, clipping if it does not fit.
174static void test_logo(void) {
175 uint8_t lines = oled_max_lines();
176 if (lines > 3) {
177 lines = 3;
178 }
179 uint8_t chars = oled_max_chars();
180 if (chars > 21) {
181 chars = 21;
182 }
183 for (uint8_t row = 0; row < lines; ++row) {
184 oled_set_cursor(0, row);
185 for (uint8_t col = 0; col < chars; ++col) {
186 oled_write_char(0x80 + 0x20 * row + col, false);
187 }
188 }
189}
190
191static const PROGMEM char fill_ff[OLED_MATRIX_SIZE] = {[0 ... OLED_MATRIX_SIZE - 1] = 0xff};
192
193// Fill the whole screen with a pattern made from two bytes alternating after the specified number of repeats.
194static void test_fill(uint8_t byte0, uint8_t byte1, uint8_t repeats) {
195 uint8_t width = pixel_width();
196 uint8_t lines = oled_max_lines();
197 uint16_t index = 0;
198 for (uint8_t row = 0; row < lines; ++row) {
199 for (uint8_t col = 0; col < width; ++col) {
200 uint8_t byte = ((col / repeats) % 2) ? byte1 : byte0;
201 oled_write_raw_byte(byte, index++);
202 }
203 }
204}
205
206// Draw a frame at the edges of the OLED screen.
207static void test_frame(void) {
208 uint8_t width = pixel_width();
209 uint8_t height = pixel_height();
210 for (uint8_t x = 0; x < width; ++x) {
211 oled_write_pixel(x, 0, true);
212 oled_write_pixel(x, height - 1, true);
213 }
214 for (uint8_t y = 1; y < height - 1; ++y) {
215 oled_write_pixel(0, y, true);
216 oled_write_pixel(width - 1, y, true);
217 }
218}
219
220// Use all 94 visible ASCII characters for testing.
221#define TEST_CHAR_COUNT ('~' - '!' + 1)
222
223static char get_test_char(uint8_t char_index) { return char_index + '!'; }
224
225// Fill the whole screen with distinct characters (if the display is large enough to show more than 94 characters
226// at once, the sequence is repeated the second time with inverted characters).
227static void test_characters(void) {
228 uint8_t cols = oled_max_chars();
229 uint8_t rows = oled_max_lines();
230 bool invert = false;
231 uint8_t char_index = 0;
232 for (uint8_t row = 0; row < rows; ++row) {
233 for (uint8_t col = 0; col < cols; ++col) {
234 oled_write_char(get_test_char(char_index), invert);
235 if (++char_index >= TEST_CHAR_COUNT) {
236 char_index = 0;
237 invert = !invert;
238 }
239 }
240 }
241}
242
243// Test screen updating after drawing a single character or pixel.
244void test_slow_update(void) {
245 static uint8_t phase, x, y, char_index, first_char;
246 static uint16_t timer;
247 static uint16_t delay = 500;
248
249 if (restart_test) {
250 // Initialize all state variables before starting the test.
251 restart_test = false;
252 phase = 0;
253 x = 0;
254 y = 0;
255 char_index = 0;
256 first_char = 0;
257 delay = 500;
258 } else {
259 // Wait for the specified time between steps.
260 if (timer_elapsed(timer) < delay) {
261 return;
262 }
263 }
264
265 timer = timer_read();
266 switch (phase) {
267 case 0:
268 // Phase 0: fill the whole screen with mostly distinct characters, one character at a time. Here the
269 // inversion trick is not used, so that the frame which is drawn in subsequent phases would not be
270 // overlapped by the inverted character background.
271 oled_set_cursor(x, y);
272 oled_write_char(get_test_char(char_index), false);
273 if (++char_index >= TEST_CHAR_COUNT) {
274 char_index = 0;
275 }
276 if (++x >= oled_max_chars()) {
277 x = 0;
278 if (++y >= oled_max_lines()) {
279 // The whole screen was filled - start the next phase.
280 ++phase;
281 x = y = 0;
282 }
283 }
284 delay = 250;
285 break;
286
287 case 1:
288 // Phase 1: draw a line along the left edge of the screen, one pixel at a time.
289 oled_write_pixel(x, y, true);
290 if (y < pixel_height() - 1) {
291 ++y;
292 } else {
293 // The bottom left corner is reached - start the next phase.
294 ++phase;
295 ++x;
296 }
297 delay = 50;
298 break;
299
300 case 2:
301 // Phase 2: draw a line along the bottom edge of the screen, one pixel at a time.
302 oled_write_pixel(x, y, true);
303 if (x < pixel_width() - 1) {
304 ++x;
305 } else {
306 // The bottom right corner was reached - start the next phase.
307 ++phase;
308 --y;
309 }
310 delay = 50;
311 break;
312
313 case 3:
314 // Phase 3: draw a line along the right edge of the screen, one pixel at a time.
315 oled_write_pixel(x, y, true);
316 if (y > 0) {
317 --y;
318 } else {
319 // The top right corner was reached - start the next phase.
320 ++phase;
321 --x;
322 }
323 delay = 50;
324 break;
325
326 case 4:
327 // Phase 4: draw a line along the top edge of the screen, one pixel at a time.
328 oled_write_pixel(x, y, true);
329 if (x > 0) {
330 --x;
331 } else {
332 // The top left corner was reached - start the next phase.
333 ++phase;
334 }
335 delay = 50;
336 break;
337
338 default:
339 // Restart from phase 0, but change the first character of the sequence to make screen updates visible.
340 if (++first_char >= TEST_CHAR_COUNT) {
341 first_char = 0;
342 }
343 phase = 0;
344 x = 0;
345 y = 0;
346 char_index = first_char;
347 delay = 500;
348 break;
349 }
350}
351
352oled_rotation_t oled_init_user(oled_rotation_t rotation) {
353 oled_scroll_set_area(0, 0);
354 oled_scroll_set_speed(scrolling_speed);
355 return rotation;
356}
357
358void oled_task_user(void) {
359 if (update_speed_test) {
360 // Speed test mode - wait for screen update completion.
361 if (!oled_dirty) {
362 // Update statistics and send the measurement result to the console.
363 update_speed_count++;
364 if (update_speed_count % 256 == 0) {
365 uprintf("OLED: %u updates, %lu ms\n", update_speed_count, timer_elapsed32(update_speed_start_timer));
366 }
367
368 // Toggle between the "all on" and "all off" states and trigger the screen update again.
369 if (test_mode == TEST_ALL_ON) {
370 test_mode = TEST_ALL_OFF;
371 } else {
372 test_mode = TEST_ALL_ON;
373 }
374 need_update = true;
375 }
376 }
377
378 // The sample implementation of oled_task_user() in the documentation redraws the image after every call, relying on
379 // the fact that drawing functions check whether the output actually changes anything in the image, and set dirty
380 // bits only when something has actually changed. However, redrawing the image only when some of the underlying
381 // data has changed is more efficient. Make it possible to test both modes here.
382 if (!draw_always || update_speed_test) {
383 // Draw the image only when the `need_update` flag is set, except for the "slow update" test.
384 // This mode is also forced when the screen update speed test is performed.
385 if (!need_update) {
386 if (test_mode != TEST_SLOW_UPDATE) {
387 return;
388 }
389 }
390 need_update = false;
391 }
392
393 switch (test_mode) {
394 case TEST_LOGO:
395 test_logo();
396 break;
397 case TEST_CHARACTERS:
398 test_characters();
399 break;
400 case TEST_SLOW_UPDATE:
401 test_slow_update();
402 break;
403 case TEST_ALL_ON:
404 oled_write_raw_P(fill_ff, sizeof(fill_ff));
405 break;
406 case TEST_FRAME:
407 test_frame();
408 break;
409 case TEST_ALL_OFF:
410 // `oled_clear()` is faster, but cannot be used with `draw_always`, because it does not check the previous
411 // content of the buffer and always marks the whole buffer as dirty.
412 if (update_speed_test) {
413 oled_clear();
414 } else {
415 test_fill(0x00, 0x00, 1);
416 }
417 break;
418 case TEST_FILL_HORZ_0:
419 test_fill(0x55, 0x55, 1);
420 break;
421 case TEST_FILL_HORZ_1:
422 test_fill(0xaa, 0xaa, 1);
423 break;
424 case TEST_FILL_VERT_0:
425 test_fill(0xff, 0x00, 1);
426 break;
427 case TEST_FILL_VERT_1:
428 test_fill(0x00, 0xff, 1);
429 break;
430 case TEST_FILL_CHECKERBOARD_1:
431 test_fill(0x55, 0xaa, 1);
432 break;
433 case TEST_FILL_CHECKERBOARD_2:
434 test_fill(0x33, 0xcc, 2);
435 break;
436 case TEST_FILL_CHECKERBOARD_4:
437 test_fill(0x0f, 0xf0, 4);
438 break;
439
440 case TEST_DRAW_ALWAYS_ON:
441 oled_write_P(PSTR("Draw Always"), false);
442 break;
443 case TEST_DRAW_ALWAYS_OFF:
444 oled_write_P(PSTR("Draw Once"), false);
445 break;
446 }
447}
448
449void keyboard_post_init_user(void) {
450 // Console messages are used for update speed test results
451 debug_enable = true;
452}
diff --git a/keyboards/handwired/onekey/keymaps/oled/readme.md b/keyboards/handwired/onekey/keymaps/oled/readme.md
new file mode 100644
index 000000000..380b3eb52
--- /dev/null
+++ b/keyboards/handwired/onekey/keymaps/oled/readme.md
@@ -0,0 +1,23 @@
1# OLED tester
2
3Available commands using a single key:
4- Single tap: Switch to the next test pattern.
5- Single hold: Switch to the next orientation (note that 90° and 270° orientations may not work correctly with some displays).
6- Double tap: Toggle horizontal scrolling of the top row. Note that this scrolling is implemented by the controller and has major limitations: it works only with SSD1306-based displays, blocks all display updates, may not work correctly if the display width is less than 128 pixels, and does not handle 90°/270° rotation properly.
7- Tap and hold: Change scrolling speed (because of controller limitations, scrolling needs to be started again manually using a double tap).
8- Triple tap: Start or stop the update speed test. This test repeatedly fills the display with all-on and all-off pixels, measures the time required for updating the display, and prints the measured values to the HID console every 256th refresh.
9- Double tap and hold: Switch between the “draw once” (default) and “draw always” modes. The “draw always” mode means that `oled_task_user()` redraws the whole picture completely every time it is called; the example code in the OLED feature documentation is written in this style. Testing the “draw always” mode can uncover bugs in the implementation of drawing functions (they must not set the dirty mark if the buffer content is not actually changed).
10
11Available test patterns:
12- QMK logo (clipped to fit on the display).
13- Fill the whole screen with as much unique characters as possible (all 94 printable ASCII characters are used, and if the display has more character positions available, the same characters are printed again, but inverted).
14- “Slow update” test — instead of updating the whole screen at once, draw things piece by piece to uncover display update bugs. The drawing sequence used by this test:
15 - Fill the whole screen with printable ASCII characters (similar to the previous test, but characters are drawn one by one with 250 ms intervals between them, and inverted characters are not used to avoid obscuring the next phases).
16 - Draw a frame along the screen edges, starting from the top left corner and going down along the left edge, then along the bottom, right and top edges, with 50 ms delay after every pixel.
17 - Repeat the same sequence again, but with the character sequence shifted by 1 character (so that the updates would be visible).
18- All pixels on.
19- Pixels at the edges of the screen on.
20- All pixels off.
21- Horizontal on/off 1px lines (two variants - starting from on or off state).
22- Vertical on/off 1px lines (two variants - starting from on or off state).
23- Checkerboard pattern (three variants - 1×1, 2×2, 4×4 pixels).
diff --git a/keyboards/handwired/onekey/keymaps/oled/rules.mk b/keyboards/handwired/onekey/keymaps/oled/rules.mk
new file mode 100644
index 000000000..2ef0a8d04
--- /dev/null
+++ b/keyboards/handwired/onekey/keymaps/oled/rules.mk
@@ -0,0 +1,2 @@
1OLED_DRIVER_ENABLE = yes
2TAP_DANCE_ENABLE = yes