├── README.md ├── SSD1306.py ├── bmp.py ├── font5x7.fnt ├── icon.bmp ├── lcd_gfx.py └── main.py /README.md: -------------------------------------------------------------------------------- 1 | ESP8266 Micropython driver for SSD1306 OLED 128x64 display. 2 | 3 | **font5x7.fnt** is a 5 x 7 font file. It holds the font in pure binary and so uses only 480 bytes 4 | When the display needs a character it reads the five bytes it needs from the file rather than loading the whole font into memory 5 | 6 | **bmp.py** converts a 24-bit BITMAP file to a displayed image. Although this is a colour format, the image must be black and white. 7 | 8 | **lcd_gfx.py** is not my work. It came from here https://forum.micropython.org/viewtopic.php?t=195 9 | 10 | **icon.bmp** is the bitmap that is displayed in main.py. Be aware there is cuerrently no positioning, size check or resizing. 11 | 12 | invoke the display with: 13 | 14 | from SSD1306 import SSD1306 15 | 16 | if you use different pins then you will need to specify these in the inital call 17 | 18 | d = PCD8544() 19 | 20 | d.poweron() 21 | 22 | d.init_display() # displays the Project Pages logo 23 | 24 | d.clear() # clears the display buffer 25 | 26 | d.display() # writes the buffer to the actual display 27 | 28 | d._row is the character row 29 | 30 | d._col is the character column 31 | 32 | d.p_char('x') 33 | * puts the character into the display buffer 34 | * advances _row and _col accordingly. They will wrap back to the top of the screen 35 | * requires d.display() to show it 36 | 37 | d.p_string('hello world') 38 | * prints the string to the display buffer 39 | * advances _row and _col accoridngly. They will wrap back to the top of the screen 40 | * requires d.display() to show it 41 | 42 | d.pixel(x,y,fill) 43 | * sets a pixel in the display buffer 44 | * this is for use by the lcd_gfx.py 45 | * this allows you to draw lines, rectangles, triangles and circles. Filled or not 46 | 47 | I have added a **main.py** that demonstrates the string printing and drawing capabilities 48 | 49 | 50 | -------------------------------------------------------------------------------- /SSD1306.py: -------------------------------------------------------------------------------- 1 | from time import sleep_ms 2 | from machine import Pin, I2C 3 | 4 | # Constants 5 | DISPLAYOFF = 0xAE 6 | SETCONTRAST = 0x81 7 | DISPLAYALLON_RESUME = 0xA4 8 | DISPLAYALLON = 0xA5 9 | NORMALDISPLAY = 0xA6 10 | INVERTDISPLAY = 0xA7 11 | DISPLAYON = 0xAF 12 | SETDISPLAYOFFSET = 0xD3 13 | SETCOMPINS = 0xDA 14 | SETVCOMDETECT = 0xDB 15 | SETDISPLAYCLOCKDIV = 0xD5 16 | SETPRECHARGE = 0xD9 17 | SETMULTIPLEX = 0xA8 18 | SETLOWCOLUMN = 0x00 19 | SETHIGHCOLUMN = 0x10 20 | SETSTARTLINE = 0x40 21 | MEMORYMODE = 0x20 22 | COLUMNADDR = 0x21 23 | PAGEADDR = 0x22 24 | COMSCANINC = 0xC0 25 | COMSCANDEC = 0xC8 26 | SEGREMAP = 0xA0 27 | CHARGEPUMP = 0x8D 28 | EXTERNALVCC = 0x10 #0x1 29 | SWITCHCAPVCC = 0x20 #0x2 30 | SETPAGEADDR = 0xB0 31 | SETCOLADDR_LOW = 0x00 32 | SETCOLADDR_HIGH = 0x10 33 | ACTIVATE_SCROLL = 0x2F 34 | DEACTIVATE_SCROLL = 0x2E 35 | SET_VERTICAL_SCROLL_AREA = 0xA3 36 | RIGHT_HORIZONTAL_SCROLL = 0x26 37 | LEFT_HORIZONTAL_SCROLL = 0x27 38 | VERTICAL_AND_RIGHT_HORIZONTAL_SCROLL = 0x29 39 | VERTICAL_AND_LEFT_HORIZONTAL_SCROLL = 0x2A 40 | 41 | # I2C devices are accessed through a Device ID. This is a 7-bit 42 | # value but is sometimes expressed left-shifted by 1 as an 8-bit value. 43 | # A pin on SSD1306 allows it to respond to ID 0x3C or 0x3D. The board 44 | # I bought from ebay used a 0-ohm resistor to select between "0x78" 45 | # (0x3c << 1) or "0x7a" (0x3d << 1). The default was set to "0x78" 46 | DEVID = 0x3c 47 | 48 | # I2C communication here is either 49 | # or <> <> <> <>... 50 | # These two values encode the Co (Continuation) bit as b7 and the 51 | # D/C# (Data/Command Selection) bit as b6. 52 | CTL_CMD = 0x80 53 | CTL_DAT = 0x40 54 | 55 | class SSD1306(object): 56 | 57 | def __init__(self, height=64, external_vcc=True, i2c_devid=DEVID): 58 | self.external_vcc = external_vcc 59 | self.height = 32 if height == 32 else 64 60 | self.pages = int(self.height / 8) 61 | self.columns = 128 62 | self._row = 0 63 | self._col = 0 64 | self._x = 0 65 | self._y = 0 66 | 67 | self.devid = i2c_devid 68 | self.offset = 1 69 | self.cbuffer = bytearray(2) 70 | self.cbuffer[0] = CTL_CMD 71 | 72 | self.i2c = I2C(scl=Pin(2), sda=Pin(16), freq=400000) 73 | self.buffer = bytearray(self.offset + self.pages * self.columns) 74 | 75 | def clear(self): 76 | self.buffer = bytearray(self.offset + self.pages * self.columns) 77 | if self.offset == 1: 78 | self.buffer[0] = CTL_DAT 79 | 80 | def write_command(self, command_byte): 81 | self.cbuffer[1] = command_byte 82 | self.i2c.writeto(self.devid, self.cbuffer) 83 | 84 | def invert_display(self, invert): 85 | self.write_command(INVERTDISPLAY if invert else NORMALDISPLAY) 86 | 87 | def display(self): 88 | self.write_command(COLUMNADDR) 89 | self.write_command(0) 90 | self.write_command(self.columns - 1) 91 | self.write_command(PAGEADDR) 92 | self.write_command(0) 93 | self.write_command(self.pages - 1) 94 | self.i2c.writeto(self.devid, self.buffer) 95 | 96 | def set_pixel(self, x, y, state): 97 | index = x + (int(y / 8) * self.columns) 98 | if state: 99 | self.buffer[self.offset + index] |= (1 << (y & 7)) 100 | else: 101 | self.buffer[self.offset + index] &= ~(1 << (y & 7)) 102 | 103 | def init_display(self): 104 | chargepump = 0x10 if self.external_vcc else 0x14 105 | precharge = 0x22 if self.external_vcc else 0xf1 106 | multiplex = 0x1f if self.height == 32 else 0x3f 107 | compins = 0x02 if self.height == 32 else 0x12 108 | contrast = 0x9f # 0x8f if self.height == 32 else (0x9f if self.external_vcc else 0x9f) 109 | data = [DISPLAYOFF, 110 | SETDISPLAYCLOCKDIV, 0xF0, 111 | SETMULTIPLEX, 0x3f, 112 | SETDISPLAYOFFSET, 0x00, 113 | SETSTARTLINE | 0x00, 114 | CHARGEPUMP, 0x14, 115 | MEMORYMODE, 0x00, 116 | SEGREMAP | 0x00, 117 | COMSCANINC, 118 | SETCOMPINS, 0x12, 119 | SETCONTRAST, 0xCF, 120 | SETPRECHARGE, 0xF1, 121 | DISPLAYALLON_RESUME, 122 | NORMALDISPLAY, 123 | 0x2e, # stop scroll 124 | DISPLAYON] 125 | for item in data: 126 | self.write_command(item) 127 | # self.clear() 128 | self.display() 129 | 130 | def poweron(self): 131 | if self.offset == 1: 132 | sleep_ms(10) 133 | else: 134 | self.res.high() 135 | sleep_ms(1) 136 | self.res.low() 137 | sleep_ms(10) 138 | self.res.high() 139 | sleep_ms(10) 140 | 141 | def poweroff(self): 142 | self.write_command(DISPLAYOFF) 143 | 144 | def contrast(self, contrast): 145 | self.write_command(SETCONTRAST) 146 | self.write_command(contrast) 147 | 148 | def set_start_end_cols(self, start_col=0, end_col=None): 149 | if end_col is None: 150 | end_col = self.columns - 1 151 | if start_col < 0 or start_col > self.columns - 1: 152 | raise ValueError('Start column must be between 0 and %d.' % (self.columns - 1,)) 153 | if end_col < start_col or end_col > self.columns -1: 154 | raise ValueError('End column must be between the start column (%d) and %d.' % (start_col, self.columns - 1)) 155 | self.write_command(COLUMNADDR) 156 | self.write_command(start_col) # Start column 157 | self.write_command(end_col) # End column 158 | 159 | def set_start_end_pages(self, start_page=0, end_page=None): 160 | if end_page is None: 161 | end_page = self.pages - 1 162 | if start_page < 0 or start_page > self.pages - 1: 163 | raise ValueError('Start page must be between 0 and %d.' % (self.pages - 1,)) 164 | if end_page < start_page or end_page > self.pages - 1: 165 | raise ValueError('End page must be between the start page (%d) and %d.' % (start_page, self.pages - 1)) 166 | self.write_command(PAGEADDR) 167 | self.write_command(start_page) # Page start address. (0 = reset) 168 | self.write_command(end_page) # Page end address. 169 | 170 | def p_char(self, ch): 171 | fp = (ord(ch)-0x20) * 5 172 | char_buf = bytearray([0,0,0,0,0]) 173 | f = open('font5x7.fnt','rb') 174 | f.seek(fp) 175 | char_buf = f.read(5) 176 | bp = self.columns*self._row + 6*self._col + 1 177 | for x in range (0,5): 178 | self.buffer[bp+x] = char_buf[x] 179 | self.buffer[bp+5] = 0 # put in inter char space 180 | self._col += 1 181 | if (self._col>int(self.columns/6 - 1)): 182 | self._col = 0 183 | self._row += 1 184 | if (self._row>int(self.height/8 - 1)): 185 | self._row = 0 186 | 187 | def p_string(self, str): 188 | for ch in (str): 189 | self.p_char(ch) 190 | 191 | def pixel(self,x,y,fill): 192 | r = int(y/8) 193 | i = r * self.columns + x + self.offset 194 | b = y % 8 195 | self.buffer[i] = self.buffer[i] | ( 1 << b ) 196 | 197 | 198 | 199 | 200 | -------------------------------------------------------------------------------- /bmp.py: -------------------------------------------------------------------------------- 1 | def bmp(fname, display): 2 | f=open(fname,'rb') 3 | b=bytearray(54) 4 | b=f.read(54) 5 | # header check 6 | if b[0]==0x42 and b[1]==0x4D: 7 | # is bitmap 8 | size = b[2] + (b[3]<<8) + (b[4]<<16) +(b[5]<<24) 9 | offset = b[10] + (b[11]<<8) + (b[12]<<16) +(b[13]<<24) 10 | width = b[18] + (b[19]<<8) + (b[20]<<16) +(b[21]<<24) 11 | height = b[22] + (b[23]<<8) + (b[24]<<16) +(b[25]<<24) 12 | color_planes = b[26] + (b[27]<<8) 13 | bits_per_pixel = b[28] + (b[29]<<8) 14 | compression = b[30] + (b[31]<<8) + (b[32]<<16) +(b[33]<<24) 15 | image_size = b[34] + (b[35]<<8) + (b[36]<<16) +(b[37]<<24) 16 | 17 | f.seek(offset) 18 | 19 | row_bytes = int(bits_per_pixel/8) * width 20 | # Add up to multiple of 4 21 | if row_bytes % 4 > 0: 22 | row_bytes += 4 - row_bytes % 4 23 | 24 | buffer = bytearray(row_bytes) 25 | for row in range(height): 26 | # print(row) 27 | # read in a whole row 28 | buffer=f.read(row_bytes) 29 | index = 0 30 | for index in range(width): 31 | y = (height-1) - row 32 | x = index 33 | if buffer[index*3]!=0xff: 34 | display.pixel(x,y,1) 35 | f.close() 36 | 37 | -------------------------------------------------------------------------------- /font5x7.fnt: -------------------------------------------------------------------------------- 1 | _$**#db6IU"P"AA"**>P0`` >QIE>B@BaQIF!AEK1'EEE9~~III6>AAA"AA"IIIA >AAQ2AA @A?"A@@@@>AAA> >AQ!^ )FFIII1?@@@? @   ccxaQIECAA AA@@@@@ TTTxHDD88DDD 8DDH8TTT~ TT<xD}@ @D=(DA@|x|x8DDD8|||HTTT ?D@ <@@ | @ <@0@ abs(x1 - x0); 8 | if steep: 9 | x0, y0 = y0, x0 10 | x1, y1 = y1, x1 11 | if x0>x1: 12 | x0, x1 = x1, x0 13 | y0, y1 = y1, y0 14 | dx=x1-x0 15 | dy=abs(y1-y0) 16 | err=dx/2 17 | ystep=-1 18 | if y0 < y1: ystep = 1 19 | for xx in range(x0, x1): 20 | if steep: 21 | thelcd.pixel(y0,xx,fill) 22 | else: 23 | thelcd.pixel(xx,y0,fill) 24 | err-=dy 25 | if err<0: 26 | y0+=ystep 27 | err+=dx 28 | 29 | def drawTrie(x0,y0,x1,y1,x2,y2,thelcd,fill): 30 | drawLine(x0,y0,x1,y1,thelcd,fill) 31 | drawLine(x2,y2,x1,y1,thelcd,fill) 32 | drawLine(x2,y2,x0,y0,thelcd,fill) 33 | 34 | def drawFillTrie(x0,y0,x1,y1,x2,y2,thelcd,fill): 35 | if y0 > y1: 36 | y0, y1=y1, y0 37 | x0, x1=x1, x0 38 | 39 | if y1 > y2: 40 | y2, y1=y1,y2 41 | x2, x1=x1,x2 42 | 43 | if y0 > y1: 44 | y0, y1=y1,y0 45 | x0, x1=x1,x0 46 | 47 | if y0 == y2: 48 | a = x0 49 | b = x0 50 | if x1 < a: 51 | a = x1 52 | else: 53 | if x1 > b: 54 | b = x1 55 | if x2 < a: 56 | a = x2 57 | else: 58 | if x2 > b: 59 | b = x2 60 | drawLine(a, y0,b+1,y0,thelcd,fill) 61 | return 62 | 63 | dx01 = x1 - x0 64 | dy01 = y1 - y0 65 | dx02 = x2 - x0 66 | dy02 = y2 - y0 67 | dx12 = x2 - x1 68 | dy12 = y2 - y1 69 | sa = 0 70 | sb = 0 71 | 72 | if y1 == y2: 73 | last = y1 74 | else: 75 | last = y1-1 76 | 77 | y=y0 78 | 79 | for y in range(y0, last+1): 80 | a= x0 + sa / dy01 81 | b= x0 + sb / dy02 82 | sa += dx01 83 | sb += dx02 84 | if a > b: 85 | a,b=b,a 86 | drawLine(int(a), y,int(b+1),y,thelcd,fill) 87 | 88 | sa = dx12 * (y - y1) 89 | sb = dx02 * (y - y0) 90 | 91 | for y in range(last+1, y2+1): 92 | a = x1 + sa / dy12 93 | b = x0 + sb / dy02 94 | sa += dx12 95 | sb += dx02 96 | if a > b: 97 | a,b=b,a 98 | drawLine(int(a), y,int(b+1),y,thelcd,fill) 99 | 100 | 101 | def drawRect(x,y,w,h,thelcd,fill): 102 | drawLine(x,y,x+w,y,thelcd,fill) 103 | drawLine(x+w-1,y,x+w-1,y+h,thelcd,fill) 104 | drawLine(x+w,y+h-1,x,y+h-1,thelcd,fill) 105 | drawLine(x,y+h,x,y,thelcd,fill) 106 | 107 | def drawFillRect(x,y,w,h,thelcd,fill): 108 | xa=x 109 | xe=x+w 110 | ya=y 111 | ye=y+h 112 | if xa>xe: 113 | xa,xe = xe,xa 114 | 115 | if ya>ye: 116 | ya,ye=ye,ya 117 | 118 | for yy in range(ya, ye): 119 | for xx in range(xa,xe): 120 | thelcd.pixel(xx,yy,fill) 121 | 122 | def drawCircle(x0,y0,r,thelcd,fill): 123 | f=1-r 124 | ddF_x = 1 125 | ddF_y = -2 * r 126 | x = 0 127 | y = r 128 | thelcd.pixel(x0 , y0+r,fill) 129 | thelcd.pixel(x0 , y0-r,fill) 130 | thelcd.pixel(x0+r, y0 ,fill) 131 | thelcd.pixel(x0-r, y0 ,fill) 132 | while x=0: 134 | y-=1 135 | ddF_y+=2 136 | f+=ddF_y 137 | x+=1 138 | ddF_x+=2 139 | f+=ddF_x 140 | thelcd.pixel(x0+x, y0+y, fill); 141 | thelcd.pixel(x0-x, y0+y, fill); 142 | thelcd.pixel(x0+x, y0-y, fill); 143 | thelcd.pixel(x0-x, y0-y, fill); 144 | thelcd.pixel(x0+y, y0+x, fill); 145 | thelcd.pixel(x0-y, y0+x, fill); 146 | thelcd.pixel(x0+y, y0-x, fill); 147 | thelcd.pixel(x0-y, y0-x, fill); 148 | 149 | def drawfillCircle(x0,y0,r,thelcd,fill): 150 | drawLine(x0, y0-r, x0,y0-r+2*r+1,thelcd, fill) 151 | f = 1 - r 152 | ddF_x = 1 153 | ddF_y = -2 * r 154 | x = 0 155 | y = r 156 | while x=0: 158 | y-=1 159 | ddF_y+=2 160 | f+=ddF_y 161 | x+=1 162 | ddF_x+=2 163 | f+=ddF_x 164 | drawLine(x0+x, y0-y, x0+x, y0-y+2*y+1,thelcd, fill); 165 | drawLine(x0+y, y0-x, x0+y, y0-x+2*x+1,thelcd, fill); 166 | drawLine(x0-x, y0-y, x0-x, y0-y+2*y+1,thelcd, fill); 167 | drawLine(x0-y, y0-x, x0-y, y0-x+2*x+1,thelcd, fill); 168 | 169 | -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | import time 2 | from SSD1306 import SSD1306 3 | import lcd_gfx 4 | import network 5 | import bmp 6 | d = SSD1306() 7 | d.poweron() 8 | d.init_display() 9 | 10 | d.clear() 11 | d.p_string('The quick brown fox jumped over the lazy dog') 12 | d.display() 13 | time.sleep(5) 14 | 15 | d.clear() 16 | lcd_gfx.drawTrie(42,2,21,23,63,23,d,1) 17 | d.display() 18 | time.sleep(1) 19 | 20 | lcd_gfx.drawFillRect(10,12,20,20,d,1) 21 | d.display() 22 | time.sleep(1) 23 | 24 | lcd_gfx.drawCircle(70,24,10,d,1) 25 | d.display() 26 | time.sleep(1) 27 | 28 | d.clear() 29 | bmp.bmp('icon.bmp',d) 30 | d.display() 31 | time.sleep(5) 32 | 33 | nic = network.WLAN(network.STA_IF) 34 | ip, subnet, gateway, dns = nic.ifconfig() 35 | d.clear() 36 | d._row = 0 37 | d._col = 2 38 | d.p_string('IP Address') 39 | d._row = 1 40 | d._col = 0 41 | d.p_string(ip) 42 | d._row = 3 43 | d._col = 3 44 | d.p_string('Gateway') 45 | d._row = 4 46 | d._col = 0 47 | d.p_string(gateway) 48 | d.display() 49 | time.sleep(5) 50 | d.clear() 51 | 52 | --------------------------------------------------------------------------------