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