1. Problem generation
I don’t know if you have noticed the following line of warning when using micropython to play SSD1306:
Warning: I2C(-1, …) is deprecated, use SoftI2C(…) instead
It probably means that you are using the I2C bus, suggesting that you should use the SoftI2C class better.
We know that the difference between hardware I2C and software I2C is that software I2C uses software programming to make the CPU pull up and pull down the SDA and SCL pins to simulate the I2C bus; while hardware I2C uses the I2C hardware driver inside ESP32 to realize the bus. read and write.
Obviously, hardware I2C saves CPU resources more than software I2C, because the CPU does not need to frequently operate the SDA and SCL pins. If you operate the screen frequently, hardware I2C will be your best choice.
ESP32 obviously has a hardware I2C bus, why does this prompt appear?
2. Solutions
1. Existing driver version
The root of the problem must be on the drive of SSD1306. The most uploaded SSD1306 micropython driver version on the Internet should be this:
# MicroPython SSD1306 OLED driver, I2C and SPI interfaces from micropython import const import time import framebuf import sys currentBoard="" if(sys. platform=="esp8266"): currentBoard="esp8266" elif(sys. platform=="esp32"): currentBoard="esp32" elif(sys. platform=="pyboard"): currentBoard="pyboard" import pyb # register definitions SET_CONTRAST = const(0x81) SET_ENTIRE_ON = const(0xa4) SET_NORM_INV = const(0xa6) SET_DISP = const(0xae) SET_MEM_ADDR = const(0x20) SET_COL_ADDR = const(0x21) SET_PAGE_ADDR = const(0x22) SET_DISP_START_LINE = const(0x40) SET_SEG_REMAP = const(0xa0) SET_MUX_RATIO = const(0xa8) SET_COM_OUT_DIR = const(0xc0) SET_DISP_OFFSET = const(0xd3) SET_COM_PIN_CFG = const(0xda) SET_DISP_CLK_DIV = const(0xd5) SET_PRECHARGE = const(0xd9) SET_VCOM_DESEL = const(0xdb) SET_CHARGE_PUMP = const(0x8d) class SSD1306: def __init__(self, width, height, external_vcc): self. width = width self. height = height self.external_vcc = external_vcc self.pages = self.height // 8 self.buffer = bytearray(self.pages * self.width) self.framebuf = framebuf.FrameBuffer(self.buffer, self.width, self.height, framebuf.MVLSB) self. poweron() self.init_display() def init_display(self): for cmd in ( SET_DISP | 0x00, # off # address setting SET_MEM_ADDR, 0x00, #horizontal # resolution and layout SET_DISP_START_LINE | 0x00, SET_SEG_REMAP | 0x01, # column addr 127 mapped to SEG0 SET_MUX_RATIO, self. height - 1, SET_COM_OUT_DIR | 0x08, # scan from COM[N] to COM0 SET_DISP_OFFSET, 0x00, SET_COM_PIN_CFG, 0x02 if self.height == 32 else 0x12, # timing and driving scheme SET_DISP_CLK_DIV, 0x80, SET_PRECHARGE, 0x22 if self.external_vcc else 0xf1, SET_VCOM_DESEL, 0x30, # 0.83*Vcc # display SET_CONTRAST, 0xff, # maximum SET_ENTIRE_ON, # output follows RAM contents SET_NORM_INV, # not inverted # charge pump SET_CHARGE_PUMP, 0x10 if self.external_vcc else 0x14, SET_DISP | 0x01): # on self. write_cmd(cmd) self. fill(0) self. show() def poweroff(self): self.write_cmd(SET_DISP | 0x00) def contrast(self, contrast): self.write_cmd(SET_CONTRAST) self. write_cmd(contrast) def invert(self, invert): self.write_cmd(SET_NORM_INV | (invert & amp; 1)) def show(self): x0 = 0 x1 = self. width - 1 if self. width == 64: # displays with width of 64 pixels are shifted by 32 x0 + = 32 x1 + = 32 self.write_cmd(SET_COL_ADDR) self. write_cmd(x0) self. write_cmd(x1) self.write_cmd(SET_PAGE_ADDR) self. write_cmd(0) self.write_cmd(self.pages - 1) self. write_data(self. buffer) def fill(self, col): self. framebuf. fill(col) def pixel(self, x, y, col): self. framebuf. pixel(x, y, col)? def scroll(self, dx, dy): self. framebuf. scroll(dx, dy) def text(self, string, x, y, col=1): self. framebuf. text(string, x, y, col) def hline(self, x, y, w, col): self.framebuf.hline(x, y, w, col) def vline(self, x, y, h, col): self.framebuf.vline(x, y, h, col) def line(self, x1, y1, x2, y2, col): self.framebuf.line(x1, y1, x2, y2, col) def rect(self, x, y, w, h, col): self. framebuf. rect(x, y, w, h, col) def fill_rect(self, x, y, w, h, col): self.framebuf.fill_rect(x, y, w, h, col) def blit(self, fbuf, x, y): self. framebuf. blit(fbuf, x, y) def test(self, value): self.buffer[0]=value class SSD1306_I2C(SSD1306): def __init__(self, width, height, i2c, addr=0x3c, external_vcc=False): self.i2c = i2c self.addr = addr self. temp = bytearray(2) super().__init__(width, height, external_vcc) def write_cmd(self, cmd): self.temp[0] = 0x80 # Co=1, D/C#=0 self.temp[1] = cmd #IF SYS : global currentBoard if currentBoard=="esp8266" or currentBoard=="esp32": self.i2c.writeto(self.addr, self.temp) elif currentBoard=="pyboard": self.i2c.send(self.temp,self.addr) #ELSE: def write_data(self, buf): self.temp[0] = self.addr << 1 self.temp[1] = 0x40 # Co=0, D/C#=1 global currentBoard if currentBoard=="esp8266" or currentBoard=="esp32": self.i2c.start() self.i2c.write(self.temp) self.i2c.write(buf) self.i2c.stop() #self.i2c.writeto_mem(self.temp[1],self.temp[0],buf) elif currentBoard=="pyboard": #self.i2c.send(self.temp,self.addr) #self.i2c.send(buf,self.addr) self.i2c.mem_write(buf,self.addr,0x40) def poweron(self): pass class SSD1306_SPI(SSD1306): def __init__(self, width, height, spi, dc, res, cs, external_vcc=False): self.rate = 10 * 1024 * 1024 dc.init(dc.OUT, value=0) res.init(res.OUT, value=0) cs.init(cs.OUT, value=1) self.spi = spi self.dc = dc self.res = res self.cs = cs super().__init__(width, height, external_vcc) def write_cmd(self, cmd): global currentBoard if currentBoard=="esp8266" or currentBoard=="esp32": self.spi.init(baudrate=self.rate, polarity=0, phase=0) elif currentBoard=="pyboard": self.spi.init(mode = pyb.SPI.MASTER, baudrate=self.rate, polarity=0, phase=0) self.cs.high() self.dc.low() self.cs.low() global currentBoard if currentBoard=="esp8266" or currentBoard=="esp32": self. spi. write(bytearray([cmd])) elif currentBoard=="pyboard": self. spi. send(bytearray([cmd])) self.cs.high() def write_data(self, buf): global currentBoard if currentBoard=="esp8266" or currentBoard=="esp32": self.spi.init(baudrate=self.rate, polarity=0, phase=0) elif currentBoard=="pyboard": self.spi.init(mode = pyb.SPI.MASTER, baudrate=self.rate, polarity=0, phase=0) self.cs.high() self.dc.high() self.cs.low() global currentBoard if currentBoard=="esp8266" or currentBoard=="esp32": self. spi. write(buf) elif currentBoard=="pyboard": self. spi. send(buf) self.cs.high() def poweron(self): self.res.high() time. sleep_ms(1) self.res.low() time. sleep_ms(10) self.res.high()
The method used is this:
from machine import Pin i2c=I2C(sda=Pin(22), scl=Pin(21), freq=400000) oled = SSD1306_I2C(128, 64, i2c)
Then you can use oled.text(…….) to start operating the screen display.
Note: here i2c=I2C(sda=Pin(22), scl=Pin(21), freq=400000) does not specify the I2C channel number, the program will default you to use SoftI2C to drive. For specific instructions, see the micropython documentation:
https://docs.micropython.org/en/latest/library/machine.I2C.html#machine-i2c
2. Try to use hardware I2C to drive
Since you want to switch to hardware I2C, shouldn’t you just specify the channel number of I2C? so:
from machine import Pin #Specify the use of a hardware I2C channel number, 0 or 1, see micropython documentation i2c=I2C(0,sda=Pin(22), scl=Pin(21), freq=400000) oled = SSD1306_I2C(128, 64, i2c)
But the problem comes, there is a runtime error:
File “ssd1306.py”, line 137, in write_data
OSError: I2C operation not supported
After looking at it, the problem lies in line 137 of the ssd1306.py driver, at the code self.i2c.start(). After consulting the relevant information, I found that the reason is that the hardware I2C mode does not support the analog bus operation methods unique to the software I2C, such as start(), stop(), Write(), etc., and the hardware I2C can only use writeto() and other methods.
That being the case, why would the author of the driver use the software I2C method to write the driver? unknown.
3. Cause analysis
Now that you are determined to use hardware I2C to drive the screen, then study this driver.
Look carefully at the part of class SSD1306_I2C (SSD1306), in fact, only the write_data(self, buf) method uses the software I2C-specific start(), stop(), Write() methods, and write_cmd(self, cmd) does not (used writeto).
Then we can try to rewrite write_data(self, buf).
First, carefully analyze the reasons why the original author wrote this. Checking the SSD1306 manual, we found that when writing data to SSD1306, you need to write the bus address (Slave address) first, then write the control word (control byte), and then the data (data), see the figure below:
Correspondingly, let’s look at the original driver (I add comments):
def write_data(self, buf): self.temp[0] = self.addr << 1#slave address address, decimal 120 self.temp[1] = 0x40 # Co=0, D/C#=1# control word control byte global currentBoard if currentBoard=="esp8266" or currentBoard=="esp32": self.i2c.start() self.i2c.write(self.temp)#write address, write control word self.i2c.write(buf)#write data self.i2c.stop() #self.i2c.writeto_mem(self.temp[1],self.temp[0],buf)
The writeto of hardware I2C is used like this:
self.i2c.writeto(slave address,data)
When calling this method, the bus automatically writes the address first, and then writes the data.
I think I should have guessed the reason why the author wrote this way, that is, there is a control word between the slave address and data, and the author did not find a better way how to write this control word. If the writeto method is called twice, there will be two writes to the slave address, which is naturally impossible, so I just got lazy and used soft I2C to write the slave address, control byte and data in sequence.
4. Solution
Modify the write_data method and give the code directly: (Of course I ignored the pyboard part, but this is not important because my CPU is ESP32)
def write_data(self,buf): temp1=bytearray(1) temp1[0]=0x40 temp1. extend(buf) self.i2c.writeto(self.addr,temp1)
Here we use the extend method of bytearray to insert the control word 0x40 into the front end of buf (that is, the data to be written to the screen), and let the writeto method write the control word as a data, which realizes the sequential writing of slave address, control byte and data.
Replace this code with the def write_data(self,buf) part of the original driver, and then use the hardware I2C to drive the screen when using the driver:
from machine import Pin #Specify the use of a hardware I2C channel number, 0 or 1, see micropython documentation i2c=I2C(0,sda=Pin(22), scl=Pin(21), freq=400000) oled = SSD1306_I2C(128, 64, i2c)
Well, the annoying Warning: I2C(-1, …) is deprecated, use SoftI2C(…) instead warning has also disappeared, the screen refresh will not take up so much CPU time, and the program runs much smoother.