Important changes to repositories hosted on mbed.com
Mbed hosted mercurial repositories are deprecated and are due to be permanently deleted in July 2026.
To keep a copy of this software download the repository Zip archive or clone locally using Mercurial.
It is also possible to export all your personal repositories from the account settings page.
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 }
Generated on Wed Jul 13 2022 16:35:49 by
1.7.2