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