3654d0f0 |
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; |
3654d0f0 |
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 60000 /* 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 | send_cmd2(SetComPins, 0x2); |
155 | send_cmd2(SetContrast, 0x8f); |
156 | send_cmd2(SetPreCharge, 0xf1); |
157 | send_cmd2(SetVComDetect, 0x40); |
158 | send_cmd1(DisplayAllOnResume); |
159 | send_cmd1(NormalDisplay); |
160 | send_cmd1(DeActivateScroll); |
161 | send_cmd1(DisplayOn); |
162 | |
163 | send_cmd2(SetContrast, 0); // Dim |
164 | |
165 | clear_display(); |
166 | |
167 | success = true; |
168 | |
169 | iota_gfx_flush(); |
170 | |
171 | #if DEBUG_TO_SCREEN |
172 | print_set_sendchar(capture_sendchar); |
173 | #endif |
174 | |
175 | done: |
176 | return success; |
177 | } |
178 | |
179 | bool iota_gfx_off(void) { |
180 | bool success = false; |
181 | |
182 | send_cmd1(DisplayOff); |
183 | success = true; |
184 | |
185 | done: |
186 | return success; |
187 | } |
188 | |
189 | bool iota_gfx_on(void) { |
190 | bool success = false; |
191 | |
192 | send_cmd1(DisplayOn); |
193 | success = true; |
194 | |
195 | done: |
196 | return success; |
197 | } |
198 | |
199 | void matrix_write_char_inner(struct CharacterMatrix *matrix, uint8_t c) { |
200 | *matrix->cursor = c; |
201 | ++matrix->cursor; |
202 | |
203 | if (matrix->cursor - &matrix->display[0][0] == sizeof(matrix->display)) { |
204 | // We went off the end; scroll the display upwards by one line |
205 | memmove(&matrix->display[0], &matrix->display[1], |
206 | MatrixCols * (MatrixRows - 1)); |
207 | matrix->cursor = &matrix->display[MatrixRows - 1][0]; |
208 | memset(matrix->cursor, ' ', MatrixCols); |
209 | } |
210 | } |
211 | |
212 | void matrix_write_char(struct CharacterMatrix *matrix, uint8_t c) { |
213 | matrix->dirty = true; |
214 | |
215 | if (c == '\n') { |
216 | // Clear to end of line from the cursor and then move to the |
217 | // start of the next line |
218 | uint8_t cursor_col = (matrix->cursor - &matrix->display[0][0]) % MatrixCols; |
219 | |
220 | while (cursor_col++ < MatrixCols) { |
221 | matrix_write_char_inner(matrix, ' '); |
222 | } |
223 | return; |
224 | } |
225 | |
226 | matrix_write_char_inner(matrix, c); |
227 | } |
228 | |
229 | void iota_gfx_write_char(uint8_t c) { |
230 | matrix_write_char(&display, c); |
231 | } |
232 | |
233 | void matrix_write(struct CharacterMatrix *matrix, const char *data) { |
234 | const char *end = data + strlen(data); |
235 | while (data < end) { |
236 | matrix_write_char(matrix, *data); |
237 | ++data; |
238 | } |
239 | } |
240 | |
241 | void matrix_write_ln(struct CharacterMatrix *matrix, const char *data) { |
242 | char data_ln[strlen(data)+2]; |
243 | snprintf(data_ln, sizeof(data_ln), "%s\n", data); |
244 | matrix_write(matrix, data_ln); |
245 | } |
246 | |
247 | void iota_gfx_write(const char *data) { |
248 | matrix_write(&display, data); |
249 | } |
250 | |
251 | void matrix_write_P(struct CharacterMatrix *matrix, const char *data) { |
252 | while (true) { |
253 | uint8_t c = pgm_read_byte(data); |
254 | if (c == 0) { |
255 | return; |
256 | } |
257 | matrix_write_char(matrix, c); |
258 | ++data; |
259 | } |
260 | } |
261 | |
262 | void iota_gfx_write_P(const char *data) { |
263 | matrix_write_P(&display, data); |
264 | } |
265 | |
266 | void matrix_clear(struct CharacterMatrix *matrix) { |
267 | memset(matrix->display, ' ', sizeof(matrix->display)); |
268 | matrix->cursor = &matrix->display[0][0]; |
269 | matrix->dirty = true; |
270 | } |
271 | |
272 | void iota_gfx_clear_screen(void) { |
273 | matrix_clear(&display); |
274 | } |
275 | |
276 | void matrix_render(struct CharacterMatrix *matrix) { |
277 | last_flush = timer_read(); |
278 | iota_gfx_on(); |
279 | #if DEBUG_TO_SCREEN |
280 | ++displaying; |
281 | #endif |
282 | |
283 | // Move to the home position |
284 | send_cmd3(PageAddr, 0, MatrixRows - 1); |
285 | send_cmd3(ColumnAddr, 0, (MatrixCols * FontWidth) - 1); |
286 | |
287 | if (i2c_start_write(SSD1306_ADDRESS)) { |
288 | goto done; |
289 | } |
290 | if (i2c_master_write(0x40)) { |
291 | // Data mode |
292 | goto done; |
293 | } |
294 | |
295 | for (uint8_t row = 0; row < MatrixRows; ++row) { |
296 | for (uint8_t col = 0; col < MatrixCols; ++col) { |
297 | const uint8_t *glyph = font + (matrix->display[row][col] * FontWidth); |
298 | |
299 | for (uint8_t glyphCol = 0; glyphCol < FontWidth; ++glyphCol) { |
300 | uint8_t colBits = pgm_read_byte(glyph + glyphCol); |
301 | i2c_master_write(colBits); |
302 | } |
303 | |
304 | // 1 column of space between chars (it's not included in the glyph) |
305 | //i2c_master_write(0); |
306 | } |
307 | } |
308 | |
309 | matrix->dirty = false; |
310 | |
311 | done: |
312 | i2c_master_stop(); |
313 | #if DEBUG_TO_SCREEN |
314 | --displaying; |
315 | #endif |
316 | } |
317 | |
318 | void iota_gfx_flush(void) { |
319 | matrix_render(&display); |
320 | } |
321 | |
322 | __attribute__ ((weak)) |
323 | void iota_gfx_task_user(void) { |
324 | } |
325 | |
326 | void iota_gfx_task(void) { |
327 | iota_gfx_task_user(); |
328 | |
329 | if (display.dirty|| force_dirty) { |
330 | iota_gfx_flush(); |
331 | force_dirty = false; |
332 | } |
333 | |
334 | /* |
335 | if (timer_elapsed(last_flush) > ScreenOffInterval) { |
336 | iota_gfx_off(); |
337 | } |
338 | */ |
339 | } |
340 | |
341 | bool process_record_gfx(uint16_t keycode, keyrecord_t *record) { |
342 | force_dirty = true; |
343 | return true; |
344 | } |
345 | |
346 | #endif |