Hacked version of AdaFruit graphics library for SSD1306 usage on I2C

Dependencies:   mbed

Revision:
0:b0151666c710
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/SSD1306-Library.cpp	Thu Jun 15 15:01:14 2017 +0000
@@ -0,0 +1,525 @@
+/*
+ * SSD1306-Library.cpp
+ *
+ *  Created on: 19 Apr 2017
+ *  Author: ebj
+ *
+ *  I2C version
+ *  	CS	GND
+ *  	DC	GND  for i2c addr 0x3C, VCC for addr 0x3D
+ *  	RES	Vcc
+ */
+
+#include "mbed.h"
+
+#include "SSD1306-Library.h"
+
+extern Serial pc;
+
+extern "C" {
+}
+
+#define NUM_ELEMENTS(x)	((sizeof x)/(sizeof x[0]))
+
+
+I2C i2c(I2C_SDA, I2C_SCL);
+DigitalOut rst(PTD1); //D13);			//reset pin on D13
+
+
+// the memory buffer for the LCD
+static uint8_t buffer[SSD1306_LCDHEIGHT * SSD1306_LCDWIDTH / 8];
+
+volatile uint8_t dma_pause = 0;
+
+SSD1306::SSD1306(int16_t w, int16_t h) : Adafruit_GFX(w, h) {
+}
+
+#define ssd1306_swap(a, b) { int16_t t = a; a = b; b = t; }
+
+void SSD1306::hw_setup() {
+	i2c.frequency(400000);
+}
+
+
+// the most basic function, set a single pixel
+void SSD1306::drawPixel(int16_t x, int16_t y, uint16_t color) {
+	if ((x < 0) || (x >= width()) || (y < 0) || (y >= height()))
+		return;
+
+	// check rotation, move pixel around if necessary
+	switch (rotation) {
+	case 1:
+		ssd1306_swap(x, y);
+		x = WIDTH - x - 1;
+		break;
+	case 2:
+		x = WIDTH - x - 1;
+		y = HEIGHT - y - 1;
+		break;
+	case 3:
+		ssd1306_swap(x, y);
+		y = HEIGHT - y - 1;
+		break;
+	}
+
+	// x is which column
+	uint8_t *p = &buffer[x + (y/8)*SSD1306_LCDWIDTH];
+	uint8_t v = 1 << (y & 7);
+
+	switch (color) {
+	case WHITE:
+		*p |= v;
+		break;
+	case BLACK:
+		*p &= ~v;
+		break;
+	case INVERSE:
+		*p ^= v;
+		break;
+	}
+
+}
+
+void SSD1306::_sendData(const uint8_t *blk, uint32_t len, bool isData) {
+    const uint8_t *p = blk;
+
+	//pc.printf("SendData...\r\n");
+    // now send the data
+    uint8_t control = 0x00 | (isData ? 0x40 : 0x00);
+
+ 
+    if (isData) {
+		i2c.start();
+		//pc.printf("%0.2x ", *p);
+	    i2c.write(0x3C<<1);
+	    
+	    //control |= 0x80;
+	    i2c.write(control);
+		
+	    for (int32_t i=0; i<len; i++, p++) {
+		    //pc.printf("%0.2x ", *p);
+	        int error = i2c.write(*p);
+	        //int error = 1;
+	        //wait(0.1);
+	        if (error != 1)
+	            pc.printf("I2C error: %0.2d\r\n", error);
+	    }
+	    i2c.stop();
+
+    } else {
+	    for (int32_t i=0; i<len; i++, p++) {
+		    i2c.start();
+		    //pc.printf("%0.2x ", *p);
+	        i2c.write(0x3C<<1);
+	        i2c.write(control);
+	        int error = i2c.write(*p);
+	        //int error = 1;
+	        //wait(0.1);
+	        i2c.stop();
+	        if (error != 1)
+	            pc.printf("I2C error: %0.2d\r\n", error);
+	    }
+    }
+}
+
+void SSD1306::sendCommands(const uint8_t *blk, uint32_t len) {
+	_sendData(blk, len, false);
+}
+
+void SSD1306::sendData(const uint8_t *blk, uint32_t len) {
+	_sendData(blk, len, true);
+}
+
+void SSD1306::begin(bool reset) {
+    if (reset) {		//pulse the reset pin -- maybe replace with RC network
+    	rst = 1;
+    	wait_ms(1);
+    	rst = 0;
+    	wait_ms(10);
+    	rst = 1;	
+    }
+    	
+	const uint8_t cmds[] = {
+		SSD1306_DISPLAYOFF,
+		SSD1306_SETDISPLAYCLOCKDIV,
+		0x80,                                   // the suggested ratio 0x80
+		SSD1306_SETMULTIPLEX,
+		SSD1306_LCDHEIGHT - 1,
+		SSD1306_SETDISPLAYOFFSET,
+		0x0,                                    // no offset
+		SSD1306_SETSTARTLINE | 0x0,             // line #0
+		SSD1306_CHARGEPUMP,
+		0x14,
+		SSD1306_MEMORYMODE,
+		0x00,                                   // 0x0 act like ks0108
+		SSD1306_SEGREMAP | 0x1,
+		SSD1306_COMSCANDEC,
+		SSD1306_SETCOMPINS,
+		0x12,
+		SSD1306_SETCONTRAST,
+		0xCF,
+		SSD1306_SETPRECHARGE,
+		0xF1,
+		SSD1306_SETVCOMDETECT,
+		0x40,
+		SSD1306_DISPLAYALLON_RESUME,
+		SSD1306_NORMALDISPLAY,
+		SSD1306_DEACTIVATE_SCROLL,
+		SSD1306_DISPLAYON                     //--turn on oled panel
+	};
+
+        sendCommands(cmds, NUM_ELEMENTS(cmds));
+
+}
+
+
+void SSD1306::display(void) {
+	const uint8_t cmds[] = {
+		SSD1306_COLUMNADDR,
+		0,   				// Column start address (0 = reset)
+		SSD1306_LCDWIDTH - 1,		// Column end address (127 = reset)
+		SSD1306_PAGEADDR,
+		0,				 // Page start address (0 = reset)
+		7				 // Page end address
+		};
+
+    sendCommands(cmds, NUM_ELEMENTS(cmds));
+	//now send the screen image
+	sendData(buffer, NUM_ELEMENTS(buffer));
+}
+
+void SSD1306::invertDisplay(uint8_t i) {
+	const uint8_t normalCmd[] = { SSD1306_NORMALDISPLAY };
+	const uint8_t invertCmd[] = { SSD1306_INVERTDISPLAY };
+        sendCommands( i ? invertCmd : normalCmd, 1);
+}
+
+
+void SSD1306::_scroll(uint8_t mode, uint8_t start, uint8_t stop) {
+	uint8_t cmds[] = { mode, 0, start, 0, stop, 0, 0xFF, SSD1306_ACTIVATE_SCROLL };
+        sendCommands(cmds, NUM_ELEMENTS(cmds));
+}
+// startscrollright
+// Activate a right handed scroll for rows start through stop
+// Hint, the display is 16 rows tall. To scroll the whole display, run:
+// display.scrollright(0x00, 0x0F)
+void SSD1306::startscrollright(uint8_t start, uint8_t stop) {
+	_scroll(SSD1306_RIGHT_HORIZONTAL_SCROLL, start, stop);
+}
+
+// startscrollleft
+// Activate a right handed scroll for rows start through stop
+// Hint, the display is 16 rows tall. To scroll the whole display, run:
+// display.scrollright(0x00, 0x0F)
+void SSD1306::startscrollleft(uint8_t start, uint8_t stop) {
+	_scroll(SSD1306_LEFT_HORIZONTAL_SCROLL, start, stop);
+}
+
+// startscrolldiagright
+// Activate a diagonal scroll for rows start through stop
+// Hint, the display is 16 rows tall. To scroll the whole display, run:
+// display.scrollright(0x00, 0x0F)
+void SSD1306::startscrolldiagright(uint8_t start, uint8_t stop) {
+    uint8_t cmds[] = { 
+	SSD1306_SET_VERTICAL_SCROLL_AREA,
+	0X00,
+	SSD1306_LCDHEIGHT,
+	SSD1306_VERTICAL_AND_RIGHT_HORIZONTAL_SCROLL,
+	0X00,
+	start,
+	0X00,
+	stop,
+	0X01,
+	SSD1306_ACTIVATE_SCROLL
+    };
+
+    sendCommands(cmds, NUM_ELEMENTS(cmds));
+}
+
+// startscrolldiagleft
+// Activate a diagonal scroll for rows start through stop
+// Hint, the display is 16 rows tall. To scroll the whole display, run:
+// display.scrollright(0x00, 0x0F)
+void SSD1306::startscrolldiagleft(uint8_t start, uint8_t stop) {
+    uint8_t cmds[] = { 
+	SSD1306_SET_VERTICAL_SCROLL_AREA,
+	0X00,
+	SSD1306_LCDHEIGHT,
+	SSD1306_VERTICAL_AND_LEFT_HORIZONTAL_SCROLL,
+	0X00,
+	start,
+	0X00,
+	stop,
+	0X01,
+	SSD1306_ACTIVATE_SCROLL
+    };
+
+    sendCommands(cmds, NUM_ELEMENTS(cmds));
+}
+
+
+void SSD1306::stopscroll(void) {
+	const uint8_t cmds[] = { SSD1306_DEACTIVATE_SCROLL };
+        sendCommands(cmds, NUM_ELEMENTS(cmds));
+}
+
+// Dim the display
+// dim = true: display is dimmed
+// dim = false: display is normal
+void SSD1306::dim(bool dim) {
+	// the range of contrast to too small to be really useful
+	// it is useful to dim the display
+	uint8_t cmds[] = {SSD1306_SETCONTRAST, dim ? 0 : 0xCF};
+        sendCommands(cmds, NUM_ELEMENTS(cmds));
+}
+
+// clear everything
+void SSD1306::clearDisplay(void) {
+	memset(buffer, 0, (SSD1306_LCDWIDTH * SSD1306_LCDHEIGHT / 8));
+}
+
+#if 1
+void SSD1306::drawFastHLine(int16_t x, int16_t y, int16_t w, uint16_t color) {
+	bool bSwap = false;
+	switch (rotation) {
+	case 0:
+		// 0 degree rotation, do nothing
+		break;
+	case 1:
+		// 90 degree rotation, swap x & y for rotation, then invert x
+		bSwap = true;
+		ssd1306_swap(x, y);
+		x = WIDTH - x - 1;
+		break;
+	case 2:
+		// 180 degree rotation, invert x and y - then shift y around for height.
+		x = WIDTH - x - 1;
+		y = HEIGHT - y - 1;
+		x -= (w - 1);
+		break;
+	case 3:
+		// 270 degree rotation, swap x & y for rotation, then invert y  and adjust y for w (not to become h)
+		bSwap = true;
+		ssd1306_swap(x, y);
+		y = HEIGHT - y - 1;
+		y -= (w - 1);
+		break;
+	}
+
+	if (bSwap) {
+		drawFastVLineInternal(x, y, w, color);
+	} else {
+		drawFastHLineInternal(x, y, w, color);
+	}
+}
+#endif
+
+
+void SSD1306::drawFastHLineInternal(int16_t x, int16_t y, int16_t w,
+		uint16_t color) {
+	// Do bounds/limit checks
+	if (y < 0 || y >= HEIGHT) {
+		return;
+	}
+
+	// make sure we don't try to draw below 0
+	if (x < 0) {
+		w += x;
+		x = 0;
+	}
+
+	// make sure we don't go off the edge of the display
+	if ((x + w) > WIDTH) {
+		w = (WIDTH - x);
+	}
+
+	// if our width is now negative, punt
+	if (w <= 0) {
+		return;
+	}
+
+	// set up the pointer for  movement through the buffer
+	register uint8_t *pBuf = buffer;
+	// adjust the buffer pointer for the current row
+	pBuf += ((y / 8) * SSD1306_LCDWIDTH);
+	// and offset x columns in
+	pBuf += x;
+
+	register uint8_t mask = 1 << (y & 7);
+
+	switch (color) {
+	case WHITE:
+		while (w--)
+			*pBuf++ |= mask;
+		break;
+	case BLACK:
+		mask = ~mask;
+		while (w--)
+			*pBuf++ &= mask;
+		break;
+	case INVERSE:
+		while (w--)
+			*pBuf++ ^= mask;
+		break;
+	}
+}
+
+void SSD1306::drawFastVLine(int16_t x, int16_t y, int16_t h, uint16_t color) {
+	bool bSwap = false;
+	switch (rotation) {
+	case 0:
+		break;
+	case 1:
+		// 90 degree rotation, swap x & y for rotation, then invert x and adjust x for h (now to become w)
+		bSwap = true;
+		ssd1306_swap(x, y)
+		x = WIDTH - x - 1;
+		x -= (h - 1);
+		break;
+	case 2:
+		// 180 degree rotation, invert x and y - then shift y around for height.
+		x = WIDTH - x - 1;
+		y = HEIGHT - y - 1;
+		y -= (h - 1);
+		break;
+	case 3:
+		// 270 degree rotation, swap x & y for rotation, then invert y
+		bSwap = true;
+		ssd1306_swap(x, y)
+		y = HEIGHT - y - 1;
+		break;
+	}
+
+	if (bSwap) {
+		drawFastHLineInternal(x, y, h, color);
+	} else {
+		drawFastVLineInternal(x, y, h, color);
+	}
+}
+
+void SSD1306::drawFastVLineInternal(int16_t x, int16_t __y, int16_t __h, uint16_t color) {
+
+	// do nothing if we're off the left or right side of the screen
+	if (x < 0 || x >= WIDTH) {
+		return;
+	}
+
+	// make sure we don't try to draw below 0
+	if (__y < 0) {
+		// __y is negative, this will subtract enough from __h to account for __y being 0
+		__h += __y;
+		__y = 0;
+
+	}
+
+	// make sure we don't go past the height of the display
+	if ((__y + __h) > HEIGHT) {
+		__h = (HEIGHT - __y);
+	}
+
+	// if our height is now negative, punt
+	if (__h <= 0) {
+		return;
+	}
+
+	// this display doesn't need ints for coordinates, use local byte registers for faster juggling
+	register uint8_t y = __y;
+	register uint8_t h = __h;
+
+	// set up the pointer for fast movement through the buffer
+	register uint8_t *pBuf = buffer;
+	// adjust the buffer pointer for the current row
+	pBuf += ((y / 8) * SSD1306_LCDWIDTH);
+	// and offset x columns in
+	pBuf += x;
+
+	// do the first partial byte, if necessary - this requires some masking
+	register uint8_t mod = (y & 7);
+	if (mod) {
+		// mask off the high n bits we want to set
+		mod = 8 - mod;
+
+		// note - lookup table results in a nearly 10% performance improvement in fill* functions
+		// register uint8_t mask = ~(0xFF >> (mod));
+		static uint8_t premask[8] = { 0x00, 0x80, 0xC0, 0xE0, 0xF0, 0xF8, 0xFC,
+				0xFE };
+		register uint8_t mask = premask[mod];
+
+		// adjust the mask if we're not going to reach the end of this byte
+		if (h < mod) {
+			mask &= (0XFF >> (mod - h));
+		}
+
+		switch (color) {
+		case WHITE:
+			*pBuf |= mask;
+			break;
+		case BLACK:
+			*pBuf &= ~mask;
+			break;
+		case INVERSE:
+			*pBuf ^= mask;
+			break;
+		}
+
+		// fast exit if we're done here!
+		if (h < mod) {
+			return;
+		}
+
+		h -= mod;
+
+		pBuf += SSD1306_LCDWIDTH;
+	}
+
+	// write solid bytes while we can - effectively doing 8 rows at a time
+	if (h >= 8) {
+		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
+			do {
+				*pBuf = ~(*pBuf);
+
+				// adjust the buffer forward 8 rows worth of data
+				pBuf += SSD1306_LCDWIDTH;
+
+				// 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)
+				h -= 8;
+			} while (h >= 8);
+		} else {
+			// store a local value to work with
+			register uint8_t val = (color == WHITE) ? 255 : 0;
+
+			do {
+				// write our value in
+				*pBuf = val;
+
+				// adjust the buffer forward 8 rows worth of data
+				pBuf += SSD1306_LCDWIDTH;
+
+				// 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)
+				h -= 8;
+			} while (h >= 8);
+		}
+	}
+
+	// now do the final partial byte, if necessary
+	if (h) {
+		mod = h & 7;
+		// this time we want to mask the low bits of the byte, vs the high bits we did above
+		// register uint8_t mask = (1 << mod) - 1;
+		// note - lookup table results in a nearly 10% performance improvement in fill* functions
+		static uint8_t postmask[8] = { 0x00, 0x01, 0x03, 0x07, 0x0F, 0x1F, 0x3F,
+				0x7F };
+		register uint8_t mask = postmask[mod];
+		switch (color) {
+		case WHITE:
+			*pBuf |= mask;
+			break;
+		case BLACK:
+			*pBuf &= ~mask;
+			break;
+		case INVERSE:
+			*pBuf ^= mask;
+			break;
+		}
+	}
+}