1 /*
2 * Author: Jon Trulson <jtrulson@ics.com>
3 * Copyright (c) 2015 Intel Corporation.
4 *
5 * Author: Tyler Gibson <tgibson@microsoft.com>
6 * Copyright (c) 2015 Microsoft Corporation.
7 *
8 * Permission is hereby granted, free of charge, to any person obtaining
9 * a copy of this software and associated documentation files (the
10 * "Software"), to deal in the Software without restriction, including
11 * without limitation the rights to use, copy, modify, merge, publish,
12 * distribute, sublicense, and/or sell copies of the Software, and to
13 * permit persons to whom the Software is furnished to do so, subject to
14 * the following conditions:
15 *
16 * The above copyright notice and this permission notice shall be
17 * included in all copies or substantial portions of the Software.
18 *
19 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
20 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
21 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
22 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
23 * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
24 * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
25 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
26 */
27 #include <unistd.h>
28 #include <iostream>
29
30 #include "eboled.h"
31
32 using namespace upm;
33 using namespace std;
34
35 static uint16_t screenBuffer[BUFFER_SIZE];
36
EBOLED(int spi,int CD,int reset)37 EBOLED::EBOLED(int spi, int CD, int reset) :
38 m_spi(spi), m_gpioCD(CD), m_gpioRST(reset)
39 {
40 m_name = "EBOLED";
41 m_textColor = COLOR_WHITE;
42 m_textWrap = 0;
43 m_textSize = 1;
44 m_cursorX = 0;
45 m_cursorY = 0;
46
47 m_gpioCD.dir(mraa::DIR_OUT);
48 m_gpioRST.dir(mraa::DIR_OUT);
49
50 //1000000 is standard.
51 m_spi.frequency(10000000);
52
53 // reset the device
54 m_gpioRST.write(1);
55 usleep(5000);
56 m_gpioRST.write(0);
57 usleep(10000);
58 m_gpioRST.write(1);
59
60 command(CMD_DISPLAYOFF);
61
62 command(CMD_SETDISPLAYCLOCKDIV);
63 command(0x80);
64
65 command(CMD_SETMULTIPLEX);
66 command(0x2f);
67
68 command(CMD_SETDISPLAYOFFSET);
69 command(0x0); // no offset
70
71 command(CMD_SETSTARTLINE | 0x0); // line #0
72
73 command(CMD_CHARGEPUMP); // enable charge pump
74 command(0x14);
75
76 command(CMD_NORMALDISPLAY);
77 command(CMD_DISPLAYALLONRESUME);
78
79 command(CMD_SEGREMAP | 0x1); // reverse mapping (SEG0==COL127)
80 command(CMD_COMSCANDEC);
81
82 command(CMD_SETCOMPINS); // custom COM PIN mapping
83 command(0x12);
84
85 command(CMD_SETCONTRAST);
86 command(0x8f);
87
88 command(CMD_SETPRECHARGE);
89 command(0xf1);
90
91 command(CMD_SETVCOMDESELECT);
92 command(0x40);
93
94 command(CMD_DISPLAYON);
95
96 usleep(4500);
97
98 setAddressingMode(HORIZONTAL);
99
100 //Set Page Address range, required for horizontal addressing mode.
101 command(CMD_SETPAGEADDRESS); // triple-byte cmd
102 command(0x00); //Initial page address
103 command(0x05); //Final page address
104
105 //Set Column Address range, required for horizontal addressing mode.
106 command(CMD_SETCOLUMNADDRESS); // triple-byte cmd
107 command(0x20); // this display has a horizontal offset of 20 columns
108 command(0x5f); // 64 columns wide - 0 based 63 offset
109 }
110
~EBOLED()111 EBOLED::~EBOLED()
112 {
113 clear();
114 }
115
refresh()116 mraa::Result EBOLED::refresh()
117 {
118 mraa::Result error = mraa::SUCCESS;;
119
120 m_gpioCD.write(1); // data mode
121 for(int i=0; i<BUFFER_SIZE; i++)
122 {
123 error = data(screenBuffer[i]);
124 if(error != mraa::SUCCESS)
125 return error;
126 }
127
128 return error;
129 }
130
write(std::string msg)131 mraa::Result EBOLED::write (std::string msg)
132 {
133 int len = msg.length();
134 uint8_t temp_cursorX = m_cursorX;
135 for (int idx = 0; idx < len; idx++)
136 {
137 if (msg[idx] == '\n')
138 {
139 m_cursorY += m_textSize * 9;
140 temp_cursorX = m_cursorX;
141 }
142 else if (msg[idx] == '\r')
143 {
144 // skip em
145 }
146 else
147 {
148 drawChar(temp_cursorX, m_cursorY, msg[idx], m_textColor, m_textSize);
149 temp_cursorX += m_textSize * 6;
150
151 //textColor used to avoid wrapping if COLOR_BLACK is set.
152 if (m_textWrap && (m_textColor > OLED_WIDTH - temp_cursorX - 6))
153 {
154 m_cursorY += m_textSize * 9;
155 temp_cursorX = m_cursorX;
156 }
157 }
158 }
159 return mraa::SUCCESS;;
160 }
161
setCursor(int row,int column)162 mraa::Result EBOLED::setCursor (int row, int column) {
163 m_cursorX = column;
164 m_cursorY = row;
165 return mraa::SUCCESS;;
166 }
167
setTextColor(uint8_t textColor)168 void EBOLED::setTextColor (uint8_t textColor) {
169 m_textColor = textColor;
170 }
171
setTextSize(uint8_t size)172 void EBOLED::setTextSize (uint8_t size) {
173 m_textSize = (size > 0) ? size : 1;
174 }
175
setTextWrap(uint8_t wrap)176 void EBOLED::setTextWrap (uint8_t wrap) {
177 m_textWrap = wrap;
178 }
179
drawChar(uint8_t x,uint8_t y,uint8_t data,uint8_t color,uint8_t size)180 void EBOLED::drawChar (uint8_t x, uint8_t y, uint8_t data, uint8_t color, uint8_t size) {
181 if( (x >= OLED_WIDTH) || // Clip right
182 (y >= OLED_HEIGHT) || // Clip bottom
183 ((x + 6 * size - 1) < 0) || // Clip left
184 ((y + 8 * size - 1) < 0)) // Clip top
185 return;
186
187 if (data < 0x20 || data > 0x7F) {
188 data = 0x20; // space
189 }
190
191 for (int8_t i=0; i<6; i++ ) {
192 uint8_t line;
193 if (i == 6)
194 line = 0x0;
195 else
196 {
197 //32 offset to align standard ASCII range to index
198 line = BasicFont[data - 32][i+1];
199 for (int8_t j = 0; j<8; j++)
200 {
201 if (line & 0x1)
202 {
203 if (size == 1) // default size
204 drawPixel(x+i, y+j, color);
205 else
206 drawRectangleFilled(x+(i*size), y+(j*size), size, size, color); // big size
207 }
208 line >>= 1;
209 }
210 }
211 }
212 }
213
clear()214 mraa::Result EBOLED::clear()
215 {
216 mraa::Result error = mraa::SUCCESS;;
217
218 m_gpioCD.write(1); // data mode
219 for(int i=0; i<BUFFER_SIZE; i++)
220 {
221 error = data(0x0000);
222 if(error != mraa::SUCCESS)
223 return error;
224 }
225
226 return mraa::SUCCESS;;
227 }
228
home()229 mraa::Result EBOLED::home()
230 {
231 return setCursor(0, 0);
232 }
233
drawPixel(int8_t x,int8_t y,uint8_t color)234 void EBOLED::drawPixel(int8_t x, int8_t y, uint8_t color)
235 {
236 if(x<0 || x>=OLED_WIDTH || y<0 || y>=OLED_HEIGHT)
237 return;
238
239 /* Screenbuffer is uint16 array, but pages are 8bit high so each buffer
240 * index is two columns. This means the index is based on x/2 and
241 * OLED_WIDTH/2 = VERT_COLUMNS.
242 *
243 * Then to set the appropriate bit, we need to shift based on the y
244 * offset in relation to the page and then adjust for high/low based
245 * on the x position.
246 */
247
248 switch(color)
249 {
250 case COLOR_XOR:
251 screenBuffer[(x/2) + ((y/8) * VERT_COLUMNS)] ^= (1<<(y%8+(x%2 * 8)));
252 return;
253 case COLOR_WHITE:
254 screenBuffer[(x/2) + ((y/8) * VERT_COLUMNS)] |= (1<<(y%8+(x%2 * 8)));
255 return;
256 case COLOR_BLACK:
257 screenBuffer[(x/2) + ((y/8) * VERT_COLUMNS)] &= ~(1<<(y%8+(x%2 * 8)));
258 return;
259 }
260 }
261
drawLine(int8_t x0,int8_t y0,int8_t x1,int8_t y1,uint8_t color)262 void EBOLED::drawLine(int8_t x0, int8_t y0, int8_t x1, int8_t y1, uint8_t color)
263 {
264 int16_t steep = abs(y1 - y0) > abs(x1 - x0);
265
266 if (steep) {
267 swap(x0, y0);
268 swap(x1, y1);
269 }
270
271 if (x0 > x1) {
272 swap(x0, x1);
273 swap(y0, y1);
274 }
275
276 int16_t dx, dy;
277 dx = x1 - x0;
278 dy = abs (y1 - y0);
279
280 int16_t err = dx / 2;
281 int16_t ystep;
282
283 if (y0 < y1) {
284 ystep = 1;
285 } else {
286 ystep = -1;
287 }
288
289 for (; x0 <= x1; x0++) {
290 if (steep) {
291 drawPixel(y0, x0, color);
292 } else {
293 drawPixel(x0, y0, color);
294 }
295 err -= dy;
296 if (err < 0) {
297 y0 += ystep;
298 err += dx;
299 }
300 }
301 }
302
drawLineHorizontal(int8_t x,int8_t y,uint8_t width,uint8_t color)303 void EBOLED::drawLineHorizontal(int8_t x, int8_t y, uint8_t width, uint8_t color)
304 {
305 drawLine(x, y, x+width-1, y, color);
306 }
307
drawLineVertical(int8_t x,int8_t y,uint8_t height,uint8_t color)308 void EBOLED::drawLineVertical(int8_t x, int8_t y, uint8_t height, uint8_t color)
309 {
310 drawLine(x, y, x, y+height-1, color);
311 }
312
drawRectangle(int8_t x,int8_t y,uint8_t width,uint8_t height,uint8_t color)313 void EBOLED::drawRectangle(int8_t x, int8_t y, uint8_t width, uint8_t height, uint8_t color)
314 {
315 drawLineHorizontal(x, y, width, color);
316 drawLineHorizontal(x, y+height-1, color);
317
318 uint8_t innerHeight = height - 2;
319 if(innerHeight > 0)
320 {
321 drawLineVertical(x, y+1, innerHeight, color);
322 drawLineVertical(x+width-1, y+1, innerHeight, color);
323 }
324 }
325
drawRoundedRectangle(int8_t x,int8_t y,int8_t width,int8_t height,int16_t radius,uint8_t color)326 void EBOLED::drawRoundedRectangle(int8_t x, int8_t y, int8_t width, int8_t height, int16_t radius, uint8_t color) {
327 // smarter version
328 drawLineHorizontal(x+radius , y , width-2*radius, color); // Top
329 drawLineHorizontal(x+radius , y+height-1, width-2*radius, color); // Bottom
330 drawLineVertical( x , y+radius , height-2*radius, color); // Left
331 drawLineVertical( x+width-1 , y+radius , height-2*radius, color); // Right
332 // draw four corners
333 drawRoundCorners(x+radius , y+radius , radius, 1, color);
334 drawRoundCorners(x+width-radius-1, y+radius , radius, 2, color);
335 drawRoundCorners(x+width-radius-1, y+height-radius-1, radius, 4, color);
336 drawRoundCorners(x+radius , y+height-radius-1, radius, 8, color);
337 }
338
drawRectangleFilled(int8_t x,int8_t y,uint8_t width,uint8_t height,uint8_t color)339 void EBOLED::drawRectangleFilled(int8_t x, int8_t y, uint8_t width, uint8_t height, uint8_t color)
340 {
341 for (uint8_t i=x; i<x+width; i++) {
342 drawLineVertical(i, y, height, color);
343 }
344 }
345
drawTriangle(int8_t x0,int8_t y0,int8_t x1,int8_t y1,int8_t x2,int8_t y2,uint8_t color)346 void EBOLED::drawTriangle(int8_t x0, int8_t y0, int8_t x1, int8_t y1, int8_t x2, int8_t y2, uint8_t color)
347 {
348 drawLine(x0, y0, x1, y1, color);
349 drawLine(x1, y1, x2, y2, color);
350 drawLine(x2, y2, x0, y0, color);
351 }
352
drawTriangleFilled(int8_t x0,int8_t y0,int8_t x1,int8_t y1,int8_t x2,int8_t y2,uint8_t color)353 void EBOLED::drawTriangleFilled ( int8_t x0, int8_t y0, int8_t x1, int8_t y1, int8_t x2, int8_t y2, uint8_t color) {
354
355 int16_t a, b, y, last;
356
357 // Sort coordinates by Y order (y2 >= y1 >= y0)
358 if (y0 > y1) {
359 swap(y0, y1); swap(x0, x1);
360 }
361 if (y1 > y2) {
362 swap(y2, y1); swap(x2, x1);
363 }
364 if (y0 > y1) {
365 swap(y0, y1); swap(x0, x1);
366 }
367
368 if(y0 == y2) { // Handle awkward all-on-same-line case as its own thing
369 a = b = x0;
370 if(x1 < a) a = x1;
371 else if(x1 > b) b = x1;
372 if(x2 < a) a = x2;
373 else if(x2 > b) b = x2;
374 drawLineHorizontal(a, y0, b-a+1, color);
375 return;
376 }
377
378 int16_t
379 dx01 = x1 - x0,
380 dy01 = y1 - y0,
381 dx02 = x2 - x0,
382 dy02 = y2 - y0,
383 dx12 = x2 - x1,
384 dy12 = y2 - y1;
385 int32_t
386 sa = 0,
387 sb = 0;
388
389 // For upper part of triangle, find scanline crossings for segments
390 // 0-1 and 0-2. If y1=y2 (flat-bottomed triangle), the scanline y1
391 // is included here (and second loop will be skipped, avoiding a /0
392 // error there), otherwise scanline y1 is skipped here and handled
393 // in the second loop...which also avoids a /0 error here if y0=y1
394 // (flat-topped triangle).
395 if(y1 == y2) last = y1; // Include y1 scanline
396 else last = y1-1; // Skip it
397
398 for(y=y0; y<=last; y++) {
399 a = x0 + sa / dy01;
400 b = x0 + sb / dy02;
401 sa += dx01;
402 sb += dx02;
403 /* longhand:
404 a = x0 + (x1 - x0) * (y - y0) / (y1 - y0);
405 b = x0 + (x2 - x0) * (y - y0) / (y2 - y0);
406 */
407 if(a > b) swap(a,b);
408 drawLineHorizontal(a, y, b-a+1, color);
409 }
410
411 // For lower part of triangle, find scanline crossings for segments
412 // 0-2 and 1-2. This loop is skipped if y1=y2.
413 sa = dx12 * (y - y1);
414 sb = dx02 * (y - y0);
415 for(; y<=y2; y++) {
416 a = x1 + sa / dy12;
417 b = x0 + sb / dy02;
418 sa += dx12;
419 sb += dx02;
420 /* longhand:
421 a = x1 + (x2 - x1) * (y - y1) / (y2 - y1);
422 b = x0 + (x2 - x0) * (y - y0) / (y2 - y0);
423 */
424 if(a > b) swap(a,b);
425 drawLineHorizontal(a, y, b-a+1, color);
426 }
427 }
428
drawCircle(int16_t x0,int16_t y0,int16_t radius,uint8_t color)429 void EBOLED::drawCircle(int16_t x0, int16_t y0, int16_t radius, uint8_t color)
430 {
431 int16_t f = 1 - radius;
432 int16_t ddF_x = 1;
433 int16_t ddF_y = -2 * radius;
434 int16_t x = 0;
435 int16_t y = radius;
436
437 drawPixel(x0 , y0+radius, color);
438 drawPixel(x0 , y0-radius, color);
439 drawPixel(x0+radius, y0 , color);
440 drawPixel(x0-radius, y0 , color);
441
442 while (x<y)
443 {
444 if (f >= 0)
445 {
446 y--;
447 ddF_y += 2;
448 f += ddF_y;
449 }
450 x++;
451
452 ddF_x += 2;
453 f += ddF_x;
454
455 drawPixel(x0 + x, y0 + y, color);
456 drawPixel(x0 - x, y0 + y, color);
457 drawPixel(x0 + x, y0 - y, color);
458 drawPixel(x0 - x, y0 - y, color);
459 drawPixel(x0 + y, y0 + x, color);
460 drawPixel(x0 - y, y0 + x, color);
461 drawPixel(x0 + y, y0 - x, color);
462 drawPixel(x0 - y, y0 - x, color);
463 }
464 }
465
drawRoundCorners(int8_t x0,int8_t y0,int16_t radius,uint8_t cornername,uint8_t color)466 void EBOLED::drawRoundCorners( int8_t x0, int8_t y0, int16_t radius, uint8_t cornername, uint8_t color) {
467 int16_t f = 1 - radius;
468 int16_t ddF_x = 1;
469 int16_t ddF_y = -2 * radius;
470 int16_t x = 0;
471 int16_t y = radius;
472
473 while (x<y) {
474 if (f >= 0) {
475 y--;
476 ddF_y += 2;
477 f += ddF_y;
478 }
479 x++;
480 ddF_x += 2;
481 f += ddF_x;
482 if (cornername & 0x4) {
483 drawPixel(x0 + x, y0 + y, color);
484 drawPixel(x0 + y, y0 + x, color);
485 }
486 if (cornername & 0x2) {
487 drawPixel(x0 + x, y0 - y, color);
488 drawPixel(x0 + y, y0 - x, color);
489 }
490 if (cornername & 0x8) {
491 drawPixel(x0 - y, y0 + x, color);
492 drawPixel(x0 - x, y0 + y, color);
493 }
494 if (cornername & 0x1) {
495 drawPixel(x0 - y, y0 - x, color);
496 drawPixel(x0 - x, y0 - y, color);
497 }
498 }
499 }
500
drawCircleFilled(int8_t x0,int8_t y0,int16_t radius,uint8_t color)501 void EBOLED::drawCircleFilled(int8_t x0, int8_t y0, int16_t radius, uint8_t color) {
502 drawLineVertical(x0, y0-radius, 2*radius+1, color);
503 drawRoundedCornersFilled(x0, y0, radius, 3, 0, color);
504 }
505
drawRoundedCornersFilled(int8_t x0,int8_t y0,int16_t radius,uint8_t cornername,int16_t delta,uint8_t color)506 void EBOLED::drawRoundedCornersFilled(int8_t x0, int8_t y0, int16_t radius, uint8_t cornername, int16_t delta, uint8_t color) {
507
508 int16_t f = 1 - radius;
509 int16_t ddF_x = 1;
510 int16_t ddF_y = -2 * radius;
511 int16_t x = 0;
512 int16_t y = radius;
513
514 while (x<y) {
515 if (f >= 0) {
516 y--;
517 ddF_y += 2;
518 f += ddF_y;
519 }
520 x++;
521 ddF_x += 2;
522 f += ddF_x;
523
524 if (cornername & 0x1) {
525 drawLineVertical(x0+x, y0-y, 2*y+1+delta, color);
526 drawLineVertical(x0+y, y0-x, 2*x+1+delta, color);
527 }
528 if (cornername & 0x2) {
529 drawLineVertical(x0-x, y0-y, 2*y+1+delta, color);
530 drawLineVertical(x0-y, y0-x, 2*x+1+delta, color);
531 }
532 }
533 }
534
fillScreen(uint8_t color)535 void EBOLED::fillScreen(uint8_t color)
536 {
537 drawRectangleFilled(0, 0, OLED_WIDTH-1, OLED_HEIGHT-1, color);
538 }
539
setAddressingMode(displayAddressingMode mode)540 mraa::Result EBOLED::setAddressingMode(displayAddressingMode mode)
541 {
542 mraa::Result rv;
543
544 rv = command(CMD_MEMORYADDRMODE);
545 rv = command(mode);
546
547 return rv;
548 }
549
command(uint8_t cmd)550 mraa::Result EBOLED::command(uint8_t cmd)
551 {
552 m_gpioCD.write(0); // command mode
553 m_spi.writeByte(cmd);
554 return mraa::SUCCESS;
555 }
556
data(uint16_t data)557 mraa::Result EBOLED::data(uint16_t data)
558 {
559 m_spi.write_word(data);
560 return mraa::SUCCESS;
561 }
562
clearScreenBuffer()563 void EBOLED::clearScreenBuffer()
564 {
565 for(int i=0; i<BUFFER_SIZE;i++)
566 screenBuffer[i] = 0x0000;
567 }
568