]> git.donarmstrong.com Git - qmk_firmware.git/blob - keyboards/nyquist/ssd1306.c
Fork Let’s Split files
[qmk_firmware.git] / keyboards / nyquist / ssd1306.c
1 #ifdef SSD1306OLED
2
3 #include "ssd1306.h"
4 #include "config.h"
5 #include "i2c.h"
6 #include <string.h>
7 #include "print.h"
8 #include "lets_split.h"
9 #include "common/glcdfont.c"
10 #ifdef ADAFRUIT_BLE_ENABLE
11 #include "adafruit_ble.h"
12 #endif
13 #ifdef PROTOCOL_LUFA
14 #include "lufa.h"
15 #endif
16 #include "sendchar.h"
17 #include "pincontrol.h"
18
19 //assign the right code to your layers
20 #define _BASE 0
21 #define _LOWER 8
22 #define _RAISE 16
23 #define _FNLAYER 64
24 #define _NUMLAY 128
25 #define _NLOWER 136
26 #define _NFNLAYER 192
27 #define _MOUSECURSOR 256
28 #define _ADJUST 65560
29
30 // Set this to 1 to help diagnose early startup problems
31 // when testing power-on with ble.  Turn it off otherwise,
32 // as the latency of printing most of the debug info messes
33 // with the matrix scan, causing keys to drop.
34 #define DEBUG_TO_SCREEN 0
35
36 // Controls the SSD1306 128x32 OLED display via i2c
37
38 #define i2cAddress 0x3C
39
40 #define DisplayHeight 32
41 #define DisplayWidth 128
42
43 #define FontHeight 8
44 #define FontWidth 6
45
46 #define MatrixRows (DisplayHeight / FontHeight)
47 #define MatrixCols (DisplayWidth / FontWidth)
48
49 struct CharacterMatrix {
50   uint8_t display[MatrixRows][MatrixCols];
51   uint8_t *cursor;
52   bool dirty;
53 };
54
55 static struct CharacterMatrix display;
56 //static uint16_t last_battery_update;
57 //static uint32_t vbat;
58 //#define BatteryUpdateInterval 10000 /* milliseconds */
59 #define ScreenOffInterval 300000 /* milliseconds */
60 #if DEBUG_TO_SCREEN
61 static uint8_t displaying;
62 #endif
63 static uint16_t last_flush;
64
65 enum ssd1306_cmds {
66   DisplayOff = 0xAE,
67   DisplayOn = 0xAF,
68
69   SetContrast = 0x81,
70   DisplayAllOnResume = 0xA4,
71
72   DisplayAllOn = 0xA5,
73   NormalDisplay = 0xA6,
74   InvertDisplay = 0xA7,
75   SetDisplayOffset = 0xD3,
76   SetComPins = 0xda,
77   SetVComDetect = 0xdb,
78   SetDisplayClockDiv = 0xD5,
79   SetPreCharge = 0xd9,
80   SetMultiPlex = 0xa8,
81   SetLowColumn = 0x00,
82   SetHighColumn = 0x10,
83   SetStartLine = 0x40,
84
85   SetMemoryMode = 0x20,
86   ColumnAddr = 0x21,
87   PageAddr = 0x22,
88
89   ComScanInc = 0xc0,
90   ComScanDec = 0xc8,
91   SegRemap = 0xa0,
92   SetChargePump = 0x8d,
93   ExternalVcc = 0x01,
94   SwitchCapVcc = 0x02,
95
96   ActivateScroll = 0x2f,
97   DeActivateScroll = 0x2e,
98   SetVerticalScrollArea = 0xa3,
99   RightHorizontalScroll = 0x26,
100   LeftHorizontalScroll = 0x27,
101   VerticalAndRightHorizontalScroll = 0x29,
102   VerticalAndLeftHorizontalScroll = 0x2a,
103 };
104
105
106 // Write command sequence.
107 // Returns true on success.
108 static inline bool _send_cmd1(uint8_t cmd) {
109   bool res = false;
110
111   if (i2c_start_write(i2cAddress)) {
112     xprintf("failed to start write to %d\n", i2cAddress);
113     goto done;
114   }
115
116   if (i2c_master_write(0x0 /* command byte follows */)) {
117     print("failed to write control byte\n");
118
119     goto done;
120   }
121
122   if (i2c_master_write(cmd)) {
123     xprintf("failed to write command %d\n", cmd);
124     goto done;
125   }
126   res = true;
127 done:
128   i2c_master_stop();
129   return res;
130 }
131
132 // Write 2-byte command sequence.
133 // Returns true on success
134 static inline bool _send_cmd2(uint8_t cmd, uint8_t opr) {
135   if (!_send_cmd1(cmd)) {
136     return false;
137   }
138   return _send_cmd1(opr);
139 }
140
141 // Write 3-byte command sequence.
142 // Returns true on success
143 static inline bool _send_cmd3(uint8_t cmd, uint8_t opr1, uint8_t opr2) {
144   if (!_send_cmd1(cmd)) {
145     return false;
146   }
147   if (!_send_cmd1(opr1)) {
148     return false;
149   }
150   return _send_cmd1(opr2);
151 }
152
153 #define send_cmd1(c) if (!_send_cmd1(c)) {goto done;}
154 #define send_cmd2(c,o) if (!_send_cmd2(c,o)) {goto done;}
155 #define send_cmd3(c,o1,o2) if (!_send_cmd3(c,o1,o2)) {goto done;}
156
157 static void matrix_clear(struct CharacterMatrix *matrix);
158
159 static void clear_display(void) {
160   matrix_clear(&display);
161
162   // Clear all of the display bits (there can be random noise
163   // in the RAM on startup)
164   send_cmd3(PageAddr, 0, (DisplayHeight / 8) - 1);
165   send_cmd3(ColumnAddr, 0, DisplayWidth - 1);
166
167   if (i2c_start_write(i2cAddress)) {
168     goto done;
169   }
170   if (i2c_master_write(0x40)) {
171     // Data mode
172     goto done;
173   }
174   for (uint8_t row = 0; row < MatrixRows; ++row) {
175     for (uint8_t col = 0; col < DisplayWidth; ++col) {
176       i2c_master_write(0);
177     }
178   }
179
180   display.dirty = false;
181
182 done:
183   i2c_master_stop();
184 }
185
186 #if DEBUG_TO_SCREEN
187 #undef sendchar
188 static int8_t capture_sendchar(uint8_t c) {
189   sendchar(c);
190   iota_gfx_write_char(c);
191
192   if (!displaying) {
193     iota_gfx_flush();
194   }
195   return 0;
196 }
197 #endif
198
199 bool iota_gfx_init(void) {
200   bool success = false;
201
202   send_cmd1(DisplayOff);
203   send_cmd2(SetDisplayClockDiv, 0x80);
204   send_cmd2(SetMultiPlex, DisplayHeight - 1);
205
206   send_cmd2(SetDisplayOffset, 0);
207
208
209   send_cmd1(SetStartLine | 0x0);
210   send_cmd2(SetChargePump, 0x14 /* Enable */);
211   send_cmd2(SetMemoryMode, 0 /* horizontal addressing */);
212
213 /// Flips the display orientation 0 degrees
214   send_cmd1(SegRemap | 0x1);
215   send_cmd1(ComScanDec);
216 /*
217 // the following Flip the display orientation 180 degrees
218   send_cmd1(SegRemap);
219   send_cmd1(ComScanInc);
220 // end flip */
221   send_cmd2(SetComPins, 0x2);
222   send_cmd2(SetContrast, 0x8f);
223   send_cmd2(SetPreCharge, 0xf1);
224   send_cmd2(SetVComDetect, 0x40);
225   send_cmd1(DisplayAllOnResume);
226   send_cmd1(NormalDisplay);
227   send_cmd1(DeActivateScroll);
228   send_cmd1(DisplayOn);
229
230   send_cmd2(SetContrast, 0); // Dim
231
232   clear_display();
233
234   success = true;
235
236   iota_gfx_flush();
237
238 #if DEBUG_TO_SCREEN
239   print_set_sendchar(capture_sendchar);
240 #endif
241
242 done:
243   return success;
244 }
245
246 bool iota_gfx_off(void) {
247   bool success = false;
248
249   send_cmd1(DisplayOff);
250   success = true;
251
252 done:
253   return success;
254
255
256 bool iota_gfx_on(void) {
257   bool success = false;
258
259   send_cmd1(DisplayOn);
260   success = true;
261
262 done:
263   return success;
264 }
265
266 static void matrix_write_char_inner(struct CharacterMatrix *matrix, uint8_t c) {
267   *matrix->cursor = c;
268   ++matrix->cursor;
269
270   if (matrix->cursor - &matrix->display[0][0] == sizeof(matrix->display)) {
271     // We went off the end; scroll the display upwards by one line
272     memmove(&matrix->display[0], &matrix->display[1],
273             MatrixCols * (MatrixRows - 1));
274     matrix->cursor = &matrix->display[MatrixRows - 1][0];
275     memset(matrix->cursor, ' ', MatrixCols);
276   }
277 }
278
279 static void matrix_write_char(struct CharacterMatrix *matrix, uint8_t c) {
280   matrix->dirty = true;
281
282   if (c == '\n') {
283     // Clear to end of line from the cursor and then move to the
284     // start of the next line
285     uint8_t cursor_col = (matrix->cursor - &matrix->display[0][0]) % MatrixCols;
286
287     while (cursor_col++ < MatrixCols) {
288       matrix_write_char_inner(matrix, ' ');
289     }
290     return;
291   }
292
293   matrix_write_char_inner(matrix, c);
294 }
295
296 void iota_gfx_write_char(uint8_t c) {
297   matrix_write_char(&display, c);
298 }
299
300 static void matrix_write(struct CharacterMatrix *matrix, const char *data) {
301   const char *end = data + strlen(data);
302   while (data < end) {
303     matrix_write_char(matrix, *data);
304     ++data;
305   }
306 }
307
308 void iota_gfx_write(const char *data) {
309   matrix_write(&display, data);
310 }
311
312 static void matrix_write_P(struct CharacterMatrix *matrix, const char *data) {
313   while (true) {
314     uint8_t c = pgm_read_byte(data);
315     if (c == 0) {
316       return;
317     }
318     matrix_write_char(matrix, c);
319     ++data;
320   }
321 }
322
323 void iota_gfx_write_P(const char *data) {
324   matrix_write_P(&display, data);
325 }
326
327 static void matrix_clear(struct CharacterMatrix *matrix) {
328   memset(matrix->display, ' ', sizeof(matrix->display));
329   matrix->cursor = &matrix->display[0][0];
330   matrix->dirty = true;
331 }
332
333 void iota_gfx_clear_screen(void) {
334   matrix_clear(&display);
335 }
336
337 static void matrix_render(struct CharacterMatrix *matrix) {
338   last_flush = timer_read();
339   iota_gfx_on();
340 #if DEBUG_TO_SCREEN
341   ++displaying;
342 #endif
343
344   // Move to the home position
345   send_cmd3(PageAddr, 0, MatrixRows - 1);
346   send_cmd3(ColumnAddr, 0, (MatrixCols * FontWidth) - 1);
347
348   if (i2c_start_write(i2cAddress)) {
349     goto done;
350   }
351   if (i2c_master_write(0x40)) {
352     // Data mode
353     goto done;
354   }
355
356   for (uint8_t row = 0; row < MatrixRows; ++row) {
357     for (uint8_t col = 0; col < MatrixCols; ++col) {
358       const uint8_t *glyph = font + (matrix->display[row][col] * (FontWidth - 1));
359
360       for (uint8_t glyphCol = 0; glyphCol < FontWidth - 1; ++glyphCol) {
361         uint8_t colBits = pgm_read_byte(glyph + glyphCol);
362         i2c_master_write(colBits);
363       }
364
365       // 1 column of space between chars (it's not included in the glyph)
366       i2c_master_write(0);
367     }
368   }
369
370   matrix->dirty = false;
371
372 done:
373   i2c_master_stop();
374 #if DEBUG_TO_SCREEN
375   --displaying;
376 #endif
377 }
378
379 void iota_gfx_flush(void) {
380   matrix_render(&display);
381 }
382
383 static void matrix_update(struct CharacterMatrix *dest,
384                           const struct CharacterMatrix *source) {
385   if (memcmp(dest->display, source->display, sizeof(dest->display))) {
386     memcpy(dest->display, source->display, sizeof(dest->display));
387     dest->dirty = true;
388   }
389 }
390
391 static void render_status_info(void) {
392 #if DEBUG_TO_SCREEN
393   if (debug_enable) {
394     return;
395   }
396 #endif
397
398   struct CharacterMatrix matrix;
399
400   matrix_clear(&matrix);
401   matrix_write_P(&matrix, PSTR("USB: "));
402 #ifdef PROTOCOL_LUFA
403   switch (USB_DeviceState) {
404     case DEVICE_STATE_Unattached:
405       matrix_write_P(&matrix, PSTR("Unattached"));
406       break;
407     case DEVICE_STATE_Suspended:
408       matrix_write_P(&matrix, PSTR("Suspended"));
409       break;
410     case DEVICE_STATE_Configured:
411       matrix_write_P(&matrix, PSTR("Connected"));
412       break;
413     case DEVICE_STATE_Powered:
414       matrix_write_P(&matrix, PSTR("Powered"));
415       break;
416     case DEVICE_STATE_Default:
417       matrix_write_P(&matrix, PSTR("Default"));
418       break;
419     case DEVICE_STATE_Addressed:
420       matrix_write_P(&matrix, PSTR("Addressed"));
421       break;
422     default:
423       matrix_write_P(&matrix, PSTR("Invalid"));
424   }
425 #endif
426
427 // Define layers here, Have not worked out how to have text displayed for each layer. Copy down the number you see and add a case for it below
428
429   char buf[40];
430   snprintf(buf,sizeof(buf), "Undef-%ld", layer_state);
431   matrix_write_P(&matrix, PSTR("\n\nLayer: "));
432     switch (layer_state) {
433         case _BASE:
434            matrix_write_P(&matrix, PSTR("Default"));
435            break;
436         case _RAISE:
437            matrix_write_P(&matrix, PSTR("Raise"));
438            break;
439         case _LOWER:
440            matrix_write_P(&matrix, PSTR("Lower"));
441            break;
442         case _ADJUST:
443            matrix_write_P(&matrix, PSTR("ADJUST"));
444            break;
445         default:
446            matrix_write(&matrix, buf);
447  }
448   
449   // Host Keyboard LED Status
450   char led[40];
451     snprintf(led, sizeof(led), "\n%s  %s  %s",
452             (host_keyboard_leds() & (1<<USB_LED_NUM_LOCK)) ? "NUMLOCK" : "       ",
453             (host_keyboard_leds() & (1<<USB_LED_CAPS_LOCK)) ? "CAPS" : "    ",
454             (host_keyboard_leds() & (1<<USB_LED_SCROLL_LOCK)) ? "SCLK" : "    ");
455   matrix_write(&matrix, led);
456   matrix_update(&display, &matrix);
457 }
458
459 void iota_gfx_task(void) {
460   render_status_info();
461
462   if (display.dirty) {
463     iota_gfx_flush();
464   }
465
466   if (timer_elapsed(last_flush) > ScreenOffInterval) {
467     iota_gfx_off();
468   }
469 }
470 #endif