#driver for Sainsmart 1.8" TFT display ST7735 #Translated by Guy Carver from the ST7735 sample code. #NOTE: This current code will set the pixel at 0,0 but the scrolling will not scroll it. Don't know if it's software causing it or not. import pyb _I2C_ADDRESS = const(0x3C) # 011110+SA0+RW - 0x3C or 0x3D _SETCONTRAST = const(0x81) _DISPLAYALLON_RESUME = const(0xA4) _DISPLAYALLON = const(0xA5) _NORMALDISPLAY = const(0xA6) _INVERTDISPLAY = const(0xA7) _DISPLAYOFF = const(0xAE) _DISPLAYON = const(0xAF) _SETDISPLAYOFFSET = const(0xD3) _SETCOMPINS = const(0xDA) _SETVCOMDETECT = const(0xDB) _SETDISPLAYCLOCKDIV = const(0xD5) _SETPRECHARGE = const(0xD9) _SETMULTIPLEX = const(0xA8) _SETLOWCOLUMN = const(0x00) _SETHIGHCOLUMN = const(0x10) _SETSTARTLINE = const(0x40) _MEMORYMODE = const(0x20) _COMSCANINC = const(0xC0) _COMSCANDEC = const(0xC8) _SEGREMAP = const(0xA0) _CHARGEPUMP = const(0x8D) _EXTRNALVCC = const(0x1) _SWITCHAPVCC = const(0x2) _ACTIVATE_SCROLL = const(0x2F) _DEACTIVATE_SCROLL = const(0x2E) _SET_VERTICAL_SCROLL_AREA = const(0xA3) _RIGHT_HORIZONTAL_SCROLL = const(0x26) _LEFT_HORIZONTAL_SCROLL = const(0x27) _VERTICAL_AND_RIGHT_HORIZONTAL_SCROLL = const(0x29) _VERTICAL_AND_LEFT_HORIZONTAL_SCROLL = const(0x2A) #Buffer layout in bits. 128 columns by 64 rows. #Each byte represents 8 pixels in a row. # Column # R 0 8 10 ... 3F8 # O 1 9 11 ... 3F9 # W 2 A 12 ... 3FA # 3 B 13 ... 3FB # 4 C 14 ... 3FC # 5 D 15 ... 3FD # 6 E 16 ... 3FE # 7 F 17 ... 3FF # 400 408 # 401 409 # 402 40A # 403 40B # 404 40C # 405 40D # 406 40E # 407 40F class OLED(object) : """diyMall OLED 9.6 128x64 pixel display driver.""" def __init__( self, aLoc ) : """aLoc I2C pin location is either 1 for 'X' or 2 for 'Y'.""" self._size = (128, 64) self._rotation = 0 self._inverted = False self._on = False self.i2c = pyb.I2C(aLoc, pyb.I2C.MASTER, baudrate = 200000) self.bytes = self.size[0] * self.size[1] // 8 self.buffer = bytearray(self.bytes + 1) self.buffer[0] = 0x40 #data write start command at very start of buffer. self.data = bytearray(2) self.data[0] = 0 self.command = _DISPLAYOFF self.command = _SETDISPLAYCLOCKDIV self.command = 0x80 #suggested ratio. self.command = _SETMULTIPLEX self.command = 0x3F self.command = _SETDISPLAYOFFSET self.command = 0x0 self.command = _SETSTARTLINE #| 0x0 self.command = _CHARGEPUMP self.command = 0x14 #No external power. self.command = _MEMORYMODE self.command = 0x00 #Act like ks0108 self.command = _SEGREMAP + 0x01 self.command = _COMSCANDEC self.command = _SETCOMPINS self.command = 0x12 self.command = _SETCONTRAST self.command = 0xCF self.command = _SETPRECHARGE self.command = 0xF1 self.command = _SETVCOMDETECT self.command = 0x40 self.command = _DISPLAYALLON_RESUME self.command = _NORMALDISPLAY self.command = 0XB0 self.command = 0x10 self.command = 0x01 #Set original position to 0,0. self.on = True self.display() @property def size( self ) : return self._size @property def rotation( self ) : return self._rotation @rotation.setter def rotation( self, aValue ) : self._rotation = aValue & 3 def write( self, aValue ) : self.i2c.send(aValue, _I2C_ADDRESS) @property def command( self ) : return 0 @command.setter def command( self, aValue ) : self.data[1] = aValue self.write(self.data) @property def on( self ) : return self._on @on.setter def on( self, aTF ) : if aTF != self._on : self._on = aTF '''Turn display on or off.''' self.command = _DISPLAYON if aTF else _DISPLAYOFF @property def invert( self ) : return self._inverted @invert.setter def invert( self, aTF ) : if aTF != self._inverted : self._inverted = aTF self.command = _INVERTDISPLAY if aTF else _NORMALDISPLAY @micropython.native def fill( self, aValue ) : for x in range(1, self.bytes + 1): self.buffer[x] = aValue; def clear( self ) : self.fill(0) @micropython.native def pixel( self, aPos, aOn ) : '''Draw a pixel at the given position''' x, y = aPos w, h = self.size if 0 <= x < w and 0 <= y < h: if self._rotation == 1: aPos = (w - y - 1, x) elif self._rotation == 2: aPos = (w - x - 1, h - y - 1) elif self._rotation == 3: aPos = (y, h - x - 1) bit = 1 << (aPos[1] % 8) index = (aPos[0] + (aPos[1] // 8) * w) + 1 if aOn : self.buffer[index] |= bit else : self.buffer[index] &= not bit @micropython.native def line( self, aStart, aEnd, aOn ) : '''Draws a line from aStart to aEnd in the given color. Vertical or horizontal lines are forwarded to vline and hline.''' px, py = aStart ex, ey = aEnd dx = ex - px dy = ey - py inx = 1 if dx > 0 else -1 iny = 1 if dy > 0 else -1 dx = abs(dx) dy = abs(dy) if (dx >= dy): dy <<= 1 e = dy - dx dx <<= 1 while (px != ex): self.pixel((px, py), aOn) if (e >= 0): py += iny e -= dx e += dy px += inx else: dx <<= 1 e = dx - dy dy <<= 1 while (py != ey): self.pixel((px, py), aOn) if (e >= 0): px += inx e -= dy e += dx py += iny # @micropython.native def fillrect( self, aStart, aSize, aOn ) : '''Draw a filled rectangle. aStart is the smallest coordinate corner and aSize is a tuple indicating width, height.''' x, y = aStart w, h = aSize ex = x + w for i in range(y, y + h): self.line((x, i), (ex, i), aOn) @micropython.native def text( self, aPos, aString, aColor, aFont, aSize = 1 ) : '''Draw a text at the given position. If the string reaches the end of the display it is wrapped to aPos[0] on the next line. aSize may be an integer which will size the font uniformly on w,h or a or any type that may be indexed with [0] or [1].''' if aFont == None: return #Make a size either from single value or 2 elements. if (type(aSize) == int) or (type(aSize) == float): wh = (aSize, aSize) else: wh = aSize px, py = aPos width = wh[0] * aFont["Width"] + 1 for c in aString: self.char((px, py), c, aColor, aFont, wh) px += width #We check > rather than >= to let the right (blank) edge of the # character print off the right of the screen. if px + width > self._size[0]: py += aFont["Height"] * wh[1] + 1 px = aPos[0] @micropython.native def char( self, aPos, aChar, aOn, aFont, aSizes ) : '''Draw a character at the given position using the given font and color. aSizes is a tuple with x, y as integer scales indicating the # of pixels to draw for each pixel in the character.''' if aFont == None: return startchar = aFont['Start'] endchar = aFont['End'] ci = ord(aChar) if (startchar <= ci <= endchar): fontw = aFont['Width'] fonth = aFont['Height'] ci = (ci - startchar) * fontw charA = aFont["Data"][ci:ci + fontw] px = aPos[0] if aSizes[0] <= 1 and aSizes[1] <= 1 : for c in charA : py = aPos[1] for r in range(fonth) : if c & 0x01 : self.pixel((px, py), aOn) py += 1 c >>= 1 px += 1 else: for c in charA : py = aPos[1] for r in range(fonth) : if c & 0x01 : self.fillrect((px, py), aSizes, aOn) py += aSizes[1] c >>= 1 px += aSizes[0] def doscrollLR( self, start, stop, aDir ) : self.command = aDir self.command = 0x00 self.command = start self.command = 0x00 self.command = stop self.command = 0x01 self.command = 0xFF self.command = _ACTIVATE_SCROLL def startscrollright( self, start, stop ) : self.doscrollLR(start, stop, _RIGHT_HORIZONTAL_SCROLL) def startscrollleft( self, start, stop ) : self.doscrollLR(start, stop, _LEFT_HORIZONTAL_SCROLL) def doscrollDiag( self, start, stop, aDir ) : self.command = _SET_VERTICAL_SCROLL_AREA self.command = 0x00 self.command = self.size()[1] self.command = aDir self.command = 0x00 self.command = start self.command = 0x00 self.command = stop self.command = 0x01 self.command = _ACTIVATE_SCROLL def startscrolldiagright( self, start, stop ) : self.doscrollDiag(start, stop, _VERTICAL_AND_RIGHT_HORIZONTAL_SCROLL) def startscrolldiagleft( self, start, stop ) : self.doscrollDiag(start, stop, _VERTICAL_AND_LEFT_HORIZONTAL_SCROLL) def stopscroll( self ) : self.command = _DEACTIVATE_SCROLL def display( self ) : self.command = _SETLOWCOLUMN #| 0x00 self.command = _SETHIGHCOLUMN #| 0x00 self.command = _SETSTARTLINE #| 0x00 #buffer starts with 0x40 in 1st byte which is the command to start the buffer write. self.write(self.buffer)