dc570b0b |
1 | #ifdef SSD1306OLED |
2 | |
3 | #include "ssd1306.h" |
4 | #include "i2c.h" |
5 | #include <string.h> |
6 | #include "print.h" |
7 | #ifdef ADAFRUIT_BLE_ENABLE |
8 | #include "adafruit_ble.h" |
9 | #endif |
10 | #ifdef PROTOCOL_LUFA |
11 | #include "lufa.h" |
12 | #endif |
13 | #include "sendchar.h" |
14 | #include "timer.h" |
15 | |
9c781858 |
16 | extern const unsigned char font[] PROGMEM; |
dc570b0b |
17 | |
18 | // Set this to 1 to help diagnose early startup problems |
19 | // when testing power-on with ble. Turn it off otherwise, |
20 | // as the latency of printing most of the debug info messes |
21 | // with the matrix scan, causing keys to drop. |
22 | #define DEBUG_TO_SCREEN 0 |
23 | |
24 | //static uint16_t last_battery_update; |
25 | //static uint32_t vbat; |
26 | //#define BatteryUpdateInterval 10000 /* milliseconds */ |
27 | |
28 | // 'last_flush' is declared as uint16_t, |
29 | // so this must be less than 65535 |
30 | #define ScreenOffInterval 30000 /* milliseconds */ |
31 | #if DEBUG_TO_SCREEN |
32 | static uint8_t displaying; |
33 | #endif |
34 | static uint16_t last_flush; |
35 | |
36 | static bool force_dirty = true; |
37 | |
38 | // Write command sequence. |
39 | // Returns true on success. |
40 | static inline bool _send_cmd1(uint8_t cmd) { |
41 | bool res = false; |
42 | |
43 | if (i2c_start_write(SSD1306_ADDRESS)) { |
44 | xprintf("failed to start write to %d\n", SSD1306_ADDRESS); |
45 | goto done; |
46 | } |
47 | |
48 | if (i2c_master_write(0x0 /* command byte follows */)) { |
49 | print("failed to write control byte\n"); |
50 | |
51 | goto done; |
52 | } |
53 | |
54 | if (i2c_master_write(cmd)) { |
55 | xprintf("failed to write command %d\n", cmd); |
56 | goto done; |
57 | } |
58 | res = true; |
59 | done: |
60 | i2c_master_stop(); |
61 | return res; |
62 | } |
63 | |
64 | // Write 2-byte command sequence. |
65 | // Returns true on success |
66 | static inline bool _send_cmd2(uint8_t cmd, uint8_t opr) { |
67 | if (!_send_cmd1(cmd)) { |
68 | return false; |
69 | } |
70 | return _send_cmd1(opr); |
71 | } |
72 | |
73 | // Write 3-byte command sequence. |
74 | // Returns true on success |
75 | static inline bool _send_cmd3(uint8_t cmd, uint8_t opr1, uint8_t opr2) { |
76 | if (!_send_cmd1(cmd)) { |
77 | return false; |
78 | } |
79 | if (!_send_cmd1(opr1)) { |
80 | return false; |
81 | } |
82 | return _send_cmd1(opr2); |
83 | } |
84 | |
85 | #define send_cmd1(c) if (!_send_cmd1(c)) {goto done;} |
86 | #define send_cmd2(c,o) if (!_send_cmd2(c,o)) {goto done;} |
87 | #define send_cmd3(c,o1,o2) if (!_send_cmd3(c,o1,o2)) {goto done;} |
88 | |
89 | static void clear_display(void) { |
90 | matrix_clear(&display); |
91 | |
92 | // Clear all of the display bits (there can be random noise |
93 | // in the RAM on startup) |
94 | send_cmd3(PageAddr, 0, (DisplayHeight / 8) - 1); |
95 | send_cmd3(ColumnAddr, 0, DisplayWidth - 1); |
96 | |
97 | if (i2c_start_write(SSD1306_ADDRESS)) { |
98 | goto done; |
99 | } |
100 | if (i2c_master_write(0x40)) { |
101 | // Data mode |
102 | goto done; |
103 | } |
104 | for (uint8_t row = 0; row < MatrixRows; ++row) { |
105 | for (uint8_t col = 0; col < DisplayWidth; ++col) { |
106 | i2c_master_write(0); |
107 | } |
108 | } |
109 | |
110 | display.dirty = false; |
111 | |
112 | done: |
113 | i2c_master_stop(); |
114 | } |
115 | |
116 | #if DEBUG_TO_SCREEN |
117 | #undef sendchar |
118 | static int8_t capture_sendchar(uint8_t c) { |
119 | sendchar(c); |
120 | iota_gfx_write_char(c); |
121 | |
122 | if (!displaying) { |
123 | iota_gfx_flush(); |
124 | } |
125 | return 0; |
126 | } |
127 | #endif |
128 | |
129 | bool iota_gfx_init(bool rotate) { |
130 | bool success = false; |
131 | |
132 | i2c_master_init(); |
133 | send_cmd1(DisplayOff); |
134 | send_cmd2(SetDisplayClockDiv, 0x80); |
135 | send_cmd2(SetMultiPlex, DisplayHeight - 1); |
136 | |
137 | send_cmd2(SetDisplayOffset, 0); |
138 | |
139 | |
140 | send_cmd1(SetStartLine | 0x0); |
141 | send_cmd2(SetChargePump, 0x14 /* Enable */); |
142 | send_cmd2(SetMemoryMode, 0 /* horizontal addressing */); |
143 | |
144 | if(rotate){ |
145 | // the following Flip the display orientation 180 degrees |
146 | send_cmd1(SegRemap); |
147 | send_cmd1(ComScanInc); |
148 | }else{ |
149 | // Flips the display orientation 0 degrees |
150 | send_cmd1(SegRemap | 0x1); |
151 | send_cmd1(ComScanDec); |
152 | } |
153 | |
154 | #ifdef SSD1306_128X64 |
155 | send_cmd2(SetComPins, 0x12); |
156 | #else |
157 | send_cmd2(SetComPins, 0x2); |
158 | #endif |
159 | send_cmd2(SetContrast, 0x8f); |
160 | send_cmd2(SetPreCharge, 0xf1); |
161 | send_cmd2(SetVComDetect, 0x40); |
162 | send_cmd1(DisplayAllOnResume); |
163 | send_cmd1(NormalDisplay); |
164 | send_cmd1(DeActivateScroll); |
165 | send_cmd1(DisplayOn); |
166 | |
167 | send_cmd2(SetContrast, 0); // Dim |
168 | |
169 | clear_display(); |
170 | |
171 | success = true; |
172 | |
173 | iota_gfx_flush(); |
174 | |
175 | #if DEBUG_TO_SCREEN |
176 | print_set_sendchar(capture_sendchar); |
177 | #endif |
178 | |
179 | done: |
180 | return success; |
181 | } |
182 | |
183 | bool iota_gfx_off(void) { |
184 | bool success = false; |
185 | |
186 | send_cmd1(DisplayOff); |
187 | success = true; |
188 | |
189 | done: |
190 | return success; |
191 | } |
192 | |
193 | bool iota_gfx_on(void) { |
194 | bool success = false; |
195 | |
196 | send_cmd1(DisplayOn); |
197 | success = true; |
198 | |
199 | done: |
200 | return success; |
201 | } |
202 | |
203 | void matrix_write_char_inner(struct CharacterMatrix *matrix, uint8_t c) { |
204 | *matrix->cursor = c; |
205 | ++matrix->cursor; |
206 | |
207 | if (matrix->cursor - &matrix->display[0][0] == sizeof(matrix->display)) { |
208 | // We went off the end; scroll the display upwards by one line |
209 | memmove(&matrix->display[0], &matrix->display[1], |
210 | MatrixCols * (MatrixRows - 1)); |
211 | matrix->cursor = &matrix->display[MatrixRows - 1][0]; |
212 | memset(matrix->cursor, ' ', MatrixCols); |
213 | } |
214 | } |
215 | |
216 | void matrix_write_char(struct CharacterMatrix *matrix, uint8_t c) { |
217 | matrix->dirty = true; |
218 | |
219 | if (c == '\n') { |
220 | // Clear to end of line from the cursor and then move to the |
221 | // start of the next line |
222 | uint8_t cursor_col = (matrix->cursor - &matrix->display[0][0]) % MatrixCols; |
223 | |
224 | while (cursor_col++ < MatrixCols) { |
225 | matrix_write_char_inner(matrix, ' '); |
226 | } |
227 | return; |
228 | } |
229 | |
230 | matrix_write_char_inner(matrix, c); |
231 | } |
232 | |
233 | void iota_gfx_write_char(uint8_t c) { |
234 | matrix_write_char(&display, c); |
235 | } |
236 | |
237 | void matrix_write(struct CharacterMatrix *matrix, const char *data) { |
238 | const char *end = data + strlen(data); |
239 | while (data < end) { |
240 | matrix_write_char(matrix, *data); |
241 | ++data; |
242 | } |
243 | } |
244 | |
245 | void matrix_write_ln(struct CharacterMatrix *matrix, const char *data) { |
246 | char data_ln[strlen(data)+2]; |
247 | snprintf(data_ln, sizeof(data_ln), "%s\n", data); |
248 | matrix_write(matrix, data_ln); |
249 | } |
250 | |
251 | void iota_gfx_write(const char *data) { |
252 | matrix_write(&display, data); |
253 | } |
254 | |
255 | void matrix_write_P(struct CharacterMatrix *matrix, const char *data) { |
256 | while (true) { |
257 | uint8_t c = pgm_read_byte(data); |
258 | if (c == 0) { |
259 | return; |
260 | } |
261 | matrix_write_char(matrix, c); |
262 | ++data; |
263 | } |
264 | } |
265 | |
266 | void iota_gfx_write_P(const char *data) { |
267 | matrix_write_P(&display, data); |
268 | } |
269 | |
270 | void matrix_clear(struct CharacterMatrix *matrix) { |
271 | memset(matrix->display, ' ', sizeof(matrix->display)); |
272 | matrix->cursor = &matrix->display[0][0]; |
273 | matrix->dirty = true; |
274 | } |
275 | |
276 | void iota_gfx_clear_screen(void) { |
277 | matrix_clear(&display); |
278 | } |
279 | |
280 | void matrix_render(struct CharacterMatrix *matrix) { |
281 | last_flush = timer_read(); |
282 | iota_gfx_on(); |
283 | #if DEBUG_TO_SCREEN |
284 | ++displaying; |
285 | #endif |
286 | |
287 | // Move to the home position |
288 | send_cmd3(PageAddr, 0, MatrixRows - 1); |
289 | send_cmd3(ColumnAddr, 0, (MatrixCols * FontWidth) - 1); |
290 | |
291 | if (i2c_start_write(SSD1306_ADDRESS)) { |
292 | goto done; |
293 | } |
294 | if (i2c_master_write(0x40)) { |
295 | // Data mode |
296 | goto done; |
297 | } |
298 | |
299 | for (uint8_t row = 0; row < MatrixRows; ++row) { |
300 | for (uint8_t col = 0; col < MatrixCols; ++col) { |
301 | const uint8_t *glyph = font + (matrix->display[row][col] * FontWidth); |
302 | |
303 | for (uint8_t glyphCol = 0; glyphCol < FontWidth; ++glyphCol) { |
304 | uint8_t colBits = pgm_read_byte(glyph + glyphCol); |
305 | i2c_master_write(colBits); |
306 | } |
307 | |
308 | // 1 column of space between chars (it's not included in the glyph) |
309 | //i2c_master_write(0); |
310 | } |
311 | } |
312 | |
313 | matrix->dirty = false; |
314 | |
315 | done: |
316 | i2c_master_stop(); |
317 | #if DEBUG_TO_SCREEN |
318 | --displaying; |
319 | #endif |
320 | } |
321 | |
322 | void iota_gfx_flush(void) { |
323 | matrix_render(&display); |
324 | } |
325 | |
326 | __attribute__ ((weak)) |
327 | void iota_gfx_task_user(void) { |
328 | } |
329 | |
330 | void iota_gfx_task(void) { |
331 | iota_gfx_task_user(); |
332 | |
333 | if (display.dirty|| force_dirty) { |
334 | iota_gfx_flush(); |
335 | force_dirty = false; |
336 | } |
337 | |
338 | if (timer_elapsed(last_flush) > ScreenOffInterval) { |
339 | iota_gfx_off(); |
340 | } |
341 | } |
342 | |
343 | bool process_record_gfx(uint16_t keycode, keyrecord_t *record) { |
344 | force_dirty = true; |
345 | return true; |
346 | } |
347 | |
348 | #endif |