Eric Johnson / Mbed 2 deprecated SSD1306-I2C

Dependencies:   mbed

Embed: (wiki syntax)

« Back to documentation index

Show/hide line numbers SSD1306-Library.cpp Source File

SSD1306-Library.cpp

00001 /*
00002  * SSD1306-Library.cpp
00003  *
00004  *  Created on: 19 Apr 2017
00005  *  Author: ebj
00006  *
00007  *  I2C version
00008  *      CS  GND
00009  *      DC  GND  for i2c addr 0x3C, VCC for addr 0x3D
00010  *      RES Vcc
00011  */
00012 
00013 #include "mbed.h"
00014 
00015 #include "SSD1306-Library.h"
00016 
00017 extern Serial pc;
00018 
00019 extern "C" {
00020 }
00021 
00022 #define NUM_ELEMENTS(x) ((sizeof x)/(sizeof x[0]))
00023 
00024 
00025 I2C i2c(I2C_SDA, I2C_SCL);
00026 DigitalOut rst(PTD1); //D13);           //reset pin on D13
00027 
00028 
00029 // the memory buffer for the LCD
00030 static uint8_t buffer[SSD1306_LCDHEIGHT * SSD1306_LCDWIDTH / 8];
00031 
00032 volatile uint8_t dma_pause = 0;
00033 
00034 SSD1306::SSD1306(int16_t w, int16_t h) : Adafruit_GFX(w, h) {
00035 }
00036 
00037 #define ssd1306_swap(a, b) { int16_t t = a; a = b; b = t; }
00038 
00039 void SSD1306::hw_setup() {
00040     i2c.frequency(400000);
00041 }
00042 
00043 
00044 // the most basic function, set a single pixel
00045 void SSD1306::drawPixel(int16_t x, int16_t y, uint16_t color) {
00046     if ((x < 0) || (x >= width()) || (y < 0) || (y >= height()))
00047         return;
00048 
00049     // check rotation, move pixel around if necessary
00050     switch (rotation) {
00051     case 1:
00052         ssd1306_swap(x, y);
00053         x = WIDTH - x - 1;
00054         break;
00055     case 2:
00056         x = WIDTH - x - 1;
00057         y = HEIGHT - y - 1;
00058         break;
00059     case 3:
00060         ssd1306_swap(x, y);
00061         y = HEIGHT - y - 1;
00062         break;
00063     }
00064 
00065     // x is which column
00066     uint8_t *p = &buffer[x + (y/8)*SSD1306_LCDWIDTH];
00067     uint8_t v = 1 << (y & 7);
00068 
00069     switch (color) {
00070     case WHITE:
00071         *p |= v;
00072         break;
00073     case BLACK:
00074         *p &= ~v;
00075         break;
00076     case INVERSE:
00077         *p ^= v;
00078         break;
00079     }
00080 
00081 }
00082 
00083 void SSD1306::_sendData(const uint8_t *blk, uint32_t len, bool isData) {
00084     const uint8_t *p = blk;
00085 
00086     //pc.printf("SendData...\r\n");
00087     // now send the data
00088     uint8_t control = 0x00 | (isData ? 0x40 : 0x00);
00089 
00090  
00091     if (isData) {
00092         i2c.start();
00093         //pc.printf("%0.2x ", *p);
00094         i2c.write(0x3C<<1);
00095         
00096         //control |= 0x80;
00097         i2c.write(control);
00098         
00099         for (int32_t i=0; i<len; i++, p++) {
00100             //pc.printf("%0.2x ", *p);
00101             int error = i2c.write(*p);
00102             //int error = 1;
00103             //wait(0.1);
00104             if (error != 1)
00105                 pc.printf("I2C error: %0.2d\r\n", error);
00106         }
00107         i2c.stop();
00108 
00109     } else {
00110         for (int32_t i=0; i<len; i++, p++) {
00111             i2c.start();
00112             //pc.printf("%0.2x ", *p);
00113             i2c.write(0x3C<<1);
00114             i2c.write(control);
00115             int error = i2c.write(*p);
00116             //int error = 1;
00117             //wait(0.1);
00118             i2c.stop();
00119             if (error != 1)
00120                 pc.printf("I2C error: %0.2d\r\n", error);
00121         }
00122     }
00123 }
00124 
00125 void SSD1306::sendCommands(const uint8_t *blk, uint32_t len) {
00126     _sendData(blk, len, false);
00127 }
00128 
00129 void SSD1306::sendData(const uint8_t *blk, uint32_t len) {
00130     _sendData(blk, len, true);
00131 }
00132 
00133 void SSD1306::begin(bool reset) {
00134     if (reset) {        //pulse the reset pin -- maybe replace with RC network
00135         rst = 1;
00136         wait_ms(1);
00137         rst = 0;
00138         wait_ms(10);
00139         rst = 1;    
00140     }
00141         
00142     const uint8_t cmds[] = {
00143         SSD1306_DISPLAYOFF,
00144         SSD1306_SETDISPLAYCLOCKDIV,
00145         0x80,                                   // the suggested ratio 0x80
00146         SSD1306_SETMULTIPLEX,
00147         SSD1306_LCDHEIGHT - 1,
00148         SSD1306_SETDISPLAYOFFSET,
00149         0x0,                                    // no offset
00150         SSD1306_SETSTARTLINE | 0x0,             // line #0
00151         SSD1306_CHARGEPUMP,
00152         0x14,
00153         SSD1306_MEMORYMODE,
00154         0x00,                                   // 0x0 act like ks0108
00155         SSD1306_SEGREMAP | 0x1,
00156         SSD1306_COMSCANDEC,
00157         SSD1306_SETCOMPINS,
00158         0x12,
00159         SSD1306_SETCONTRAST,
00160         0xCF,
00161         SSD1306_SETPRECHARGE,
00162         0xF1,
00163         SSD1306_SETVCOMDETECT,
00164         0x40,
00165         SSD1306_DISPLAYALLON_RESUME,
00166         SSD1306_NORMALDISPLAY,
00167         SSD1306_DEACTIVATE_SCROLL,
00168         SSD1306_DISPLAYON                     //--turn on oled panel
00169     };
00170 
00171         sendCommands(cmds, NUM_ELEMENTS(cmds));
00172 
00173 }
00174 
00175 
00176 void SSD1306::display(void) {
00177     const uint8_t cmds[] = {
00178         SSD1306_COLUMNADDR,
00179         0,                  // Column start address (0 = reset)
00180         SSD1306_LCDWIDTH - 1,       // Column end address (127 = reset)
00181         SSD1306_PAGEADDR,
00182         0,               // Page start address (0 = reset)
00183         7                // Page end address
00184         };
00185 
00186     sendCommands(cmds, NUM_ELEMENTS(cmds));
00187     //now send the screen image
00188     sendData(buffer, NUM_ELEMENTS(buffer));
00189 }
00190 
00191 void SSD1306::invertDisplay(uint8_t i) {
00192     const uint8_t normalCmd[] = { SSD1306_NORMALDISPLAY };
00193     const uint8_t invertCmd[] = { SSD1306_INVERTDISPLAY };
00194         sendCommands( i ? invertCmd : normalCmd, 1);
00195 }
00196 
00197 
00198 void SSD1306::_scroll(uint8_t mode, uint8_t start, uint8_t stop) {
00199     uint8_t cmds[] = { mode, 0, start, 0, stop, 0, 0xFF, SSD1306_ACTIVATE_SCROLL };
00200         sendCommands(cmds, NUM_ELEMENTS(cmds));
00201 }
00202 // startscrollright
00203 // Activate a right handed scroll for rows start through stop
00204 // Hint, the display is 16 rows tall. To scroll the whole display, run:
00205 // display.scrollright(0x00, 0x0F)
00206 void SSD1306::startscrollright(uint8_t start, uint8_t stop) {
00207     _scroll(SSD1306_RIGHT_HORIZONTAL_SCROLL, start, stop);
00208 }
00209 
00210 // startscrollleft
00211 // Activate a right handed scroll for rows start through stop
00212 // Hint, the display is 16 rows tall. To scroll the whole display, run:
00213 // display.scrollright(0x00, 0x0F)
00214 void SSD1306::startscrollleft(uint8_t start, uint8_t stop) {
00215     _scroll(SSD1306_LEFT_HORIZONTAL_SCROLL, start, stop);
00216 }
00217 
00218 // startscrolldiagright
00219 // Activate a diagonal scroll for rows start through stop
00220 // Hint, the display is 16 rows tall. To scroll the whole display, run:
00221 // display.scrollright(0x00, 0x0F)
00222 void SSD1306::startscrolldiagright(uint8_t start, uint8_t stop) {
00223     uint8_t cmds[] = { 
00224     SSD1306_SET_VERTICAL_SCROLL_AREA,
00225     0X00,
00226     SSD1306_LCDHEIGHT,
00227     SSD1306_VERTICAL_AND_RIGHT_HORIZONTAL_SCROLL,
00228     0X00,
00229     start,
00230     0X00,
00231     stop,
00232     0X01,
00233     SSD1306_ACTIVATE_SCROLL
00234     };
00235 
00236     sendCommands(cmds, NUM_ELEMENTS(cmds));
00237 }
00238 
00239 // startscrolldiagleft
00240 // Activate a diagonal scroll for rows start through stop
00241 // Hint, the display is 16 rows tall. To scroll the whole display, run:
00242 // display.scrollright(0x00, 0x0F)
00243 void SSD1306::startscrolldiagleft(uint8_t start, uint8_t stop) {
00244     uint8_t cmds[] = { 
00245     SSD1306_SET_VERTICAL_SCROLL_AREA,
00246     0X00,
00247     SSD1306_LCDHEIGHT,
00248     SSD1306_VERTICAL_AND_LEFT_HORIZONTAL_SCROLL,
00249     0X00,
00250     start,
00251     0X00,
00252     stop,
00253     0X01,
00254     SSD1306_ACTIVATE_SCROLL
00255     };
00256 
00257     sendCommands(cmds, NUM_ELEMENTS(cmds));
00258 }
00259 
00260 
00261 void SSD1306::stopscroll(void) {
00262     const uint8_t cmds[] = { SSD1306_DEACTIVATE_SCROLL };
00263         sendCommands(cmds, NUM_ELEMENTS(cmds));
00264 }
00265 
00266 // Dim the display
00267 // dim = true: display is dimmed
00268 // dim = false: display is normal
00269 void SSD1306::dim(bool dim) {
00270     // the range of contrast to too small to be really useful
00271     // it is useful to dim the display
00272     uint8_t cmds[] = {SSD1306_SETCONTRAST, dim ? 0 : 0xCF};
00273         sendCommands(cmds, NUM_ELEMENTS(cmds));
00274 }
00275 
00276 // clear everything
00277 void SSD1306::clearDisplay(void) {
00278     memset(buffer, 0, (SSD1306_LCDWIDTH * SSD1306_LCDHEIGHT / 8));
00279 }
00280 
00281 #if 1
00282 void SSD1306::drawFastHLine(int16_t x, int16_t y, int16_t w, uint16_t color) {
00283     bool bSwap = false;
00284     switch (rotation) {
00285     case 0:
00286         // 0 degree rotation, do nothing
00287         break;
00288     case 1:
00289         // 90 degree rotation, swap x & y for rotation, then invert x
00290         bSwap = true;
00291         ssd1306_swap(x, y);
00292         x = WIDTH - x - 1;
00293         break;
00294     case 2:
00295         // 180 degree rotation, invert x and y - then shift y around for height.
00296         x = WIDTH - x - 1;
00297         y = HEIGHT - y - 1;
00298         x -= (w - 1);
00299         break;
00300     case 3:
00301         // 270 degree rotation, swap x & y for rotation, then invert y  and adjust y for w (not to become h)
00302         bSwap = true;
00303         ssd1306_swap(x, y);
00304         y = HEIGHT - y - 1;
00305         y -= (w - 1);
00306         break;
00307     }
00308 
00309     if (bSwap) {
00310         drawFastVLineInternal(x, y, w, color);
00311     } else {
00312         drawFastHLineInternal(x, y, w, color);
00313     }
00314 }
00315 #endif
00316 
00317 
00318 void SSD1306::drawFastHLineInternal(int16_t x, int16_t y, int16_t w,
00319         uint16_t color) {
00320     // Do bounds/limit checks
00321     if (y < 0 || y >= HEIGHT) {
00322         return;
00323     }
00324 
00325     // make sure we don't try to draw below 0
00326     if (x < 0) {
00327         w += x;
00328         x = 0;
00329     }
00330 
00331     // make sure we don't go off the edge of the display
00332     if ((x + w) > WIDTH) {
00333         w = (WIDTH - x);
00334     }
00335 
00336     // if our width is now negative, punt
00337     if (w <= 0) {
00338         return;
00339     }
00340 
00341     // set up the pointer for  movement through the buffer
00342     register uint8_t *pBuf = buffer;
00343     // adjust the buffer pointer for the current row
00344     pBuf += ((y / 8) * SSD1306_LCDWIDTH);
00345     // and offset x columns in
00346     pBuf += x;
00347 
00348     register uint8_t mask = 1 << (y & 7);
00349 
00350     switch (color) {
00351     case WHITE:
00352         while (w--)
00353             *pBuf++ |= mask;
00354         break;
00355     case BLACK:
00356         mask = ~mask;
00357         while (w--)
00358             *pBuf++ &= mask;
00359         break;
00360     case INVERSE:
00361         while (w--)
00362             *pBuf++ ^= mask;
00363         break;
00364     }
00365 }
00366 
00367 void SSD1306::drawFastVLine(int16_t x, int16_t y, int16_t h, uint16_t color) {
00368     bool bSwap = false;
00369     switch (rotation) {
00370     case 0:
00371         break;
00372     case 1:
00373         // 90 degree rotation, swap x & y for rotation, then invert x and adjust x for h (now to become w)
00374         bSwap = true;
00375         ssd1306_swap(x, y)
00376         x = WIDTH - x - 1;
00377         x -= (h - 1);
00378         break;
00379     case 2:
00380         // 180 degree rotation, invert x and y - then shift y around for height.
00381         x = WIDTH - x - 1;
00382         y = HEIGHT - y - 1;
00383         y -= (h - 1);
00384         break;
00385     case 3:
00386         // 270 degree rotation, swap x & y for rotation, then invert y
00387         bSwap = true;
00388         ssd1306_swap(x, y)
00389         y = HEIGHT - y - 1;
00390         break;
00391     }
00392 
00393     if (bSwap) {
00394         drawFastHLineInternal(x, y, h, color);
00395     } else {
00396         drawFastVLineInternal(x, y, h, color);
00397     }
00398 }
00399 
00400 void SSD1306::drawFastVLineInternal(int16_t x, int16_t __y, int16_t __h, uint16_t color) {
00401 
00402     // do nothing if we're off the left or right side of the screen
00403     if (x < 0 || x >= WIDTH) {
00404         return;
00405     }
00406 
00407     // make sure we don't try to draw below 0
00408     if (__y < 0) {
00409         // __y is negative, this will subtract enough from __h to account for __y being 0
00410         __h += __y;
00411         __y = 0;
00412 
00413     }
00414 
00415     // make sure we don't go past the height of the display
00416     if ((__y + __h) > HEIGHT) {
00417         __h = (HEIGHT - __y);
00418     }
00419 
00420     // if our height is now negative, punt
00421     if (__h <= 0) {
00422         return;
00423     }
00424 
00425     // this display doesn't need ints for coordinates, use local byte registers for faster juggling
00426     register uint8_t y = __y;
00427     register uint8_t h = __h;
00428 
00429     // set up the pointer for fast movement through the buffer
00430     register uint8_t *pBuf = buffer;
00431     // adjust the buffer pointer for the current row
00432     pBuf += ((y / 8) * SSD1306_LCDWIDTH);
00433     // and offset x columns in
00434     pBuf += x;
00435 
00436     // do the first partial byte, if necessary - this requires some masking
00437     register uint8_t mod = (y & 7);
00438     if (mod) {
00439         // mask off the high n bits we want to set
00440         mod = 8 - mod;
00441 
00442         // note - lookup table results in a nearly 10% performance improvement in fill* functions
00443         // register uint8_t mask = ~(0xFF >> (mod));
00444         static uint8_t premask[8] = { 0x00, 0x80, 0xC0, 0xE0, 0xF0, 0xF8, 0xFC,
00445                 0xFE };
00446         register uint8_t mask = premask[mod];
00447 
00448         // adjust the mask if we're not going to reach the end of this byte
00449         if (h < mod) {
00450             mask &= (0XFF >> (mod - h));
00451         }
00452 
00453         switch (color) {
00454         case WHITE:
00455             *pBuf |= mask;
00456             break;
00457         case BLACK:
00458             *pBuf &= ~mask;
00459             break;
00460         case INVERSE:
00461             *pBuf ^= mask;
00462             break;
00463         }
00464 
00465         // fast exit if we're done here!
00466         if (h < mod) {
00467             return;
00468         }
00469 
00470         h -= mod;
00471 
00472         pBuf += SSD1306_LCDWIDTH;
00473     }
00474 
00475     // write solid bytes while we can - effectively doing 8 rows at a time
00476     if (h >= 8) {
00477         if (color == INVERSE) { // separate copy of the code so we don't impact performance of the black/white write version with an extra comparison per loop
00478             do {
00479                 *pBuf = ~(*pBuf);
00480 
00481                 // adjust the buffer forward 8 rows worth of data
00482                 pBuf += SSD1306_LCDWIDTH;
00483 
00484                 // adjust h & y (there's got to be a faster way for me to do this, but this should still help a fair bit for now)
00485                 h -= 8;
00486             } while (h >= 8);
00487         } else {
00488             // store a local value to work with
00489             register uint8_t val = (color == WHITE) ? 255 : 0;
00490 
00491             do {
00492                 // write our value in
00493                 *pBuf = val;
00494 
00495                 // adjust the buffer forward 8 rows worth of data
00496                 pBuf += SSD1306_LCDWIDTH;
00497 
00498                 // adjust h & y (there's got to be a faster way for me to do this, but this should still help a fair bit for now)
00499                 h -= 8;
00500             } while (h >= 8);
00501         }
00502     }
00503 
00504     // now do the final partial byte, if necessary
00505     if (h) {
00506         mod = h & 7;
00507         // this time we want to mask the low bits of the byte, vs the high bits we did above
00508         // register uint8_t mask = (1 << mod) - 1;
00509         // note - lookup table results in a nearly 10% performance improvement in fill* functions
00510         static uint8_t postmask[8] = { 0x00, 0x01, 0x03, 0x07, 0x0F, 0x1F, 0x3F,
00511                 0x7F };
00512         register uint8_t mask = postmask[mod];
00513         switch (color) {
00514         case WHITE:
00515             *pBuf |= mask;
00516             break;
00517         case BLACK:
00518             *pBuf &= ~mask;
00519             break;
00520         case INVERSE:
00521             *pBuf ^= mask;
00522             break;
00523         }
00524     }
00525 }