Coder Social home page Coder Social logo

sh1106's Introduction

Driver for the SH1106 display

This driver consists mostly of the work of Radomir Dopieralski (@deshipu). I added a few functions and changed the existing ones so it matches better my needs for a project.

A modified version of this driver compatible with nano-gui widget library is hosted in that project.

Features

Use OLED display with the SH1106 driver with SPI or I2C. It is based on the MicroPython framebuffer class and consists wrappers for this class as well as special methods for controlling the display.

Content Rotation

The rotate parameter in the constructor allows you to rotate the display by a 90, 180 or 270 degrees clockwise. 180 degrees are easy, because this can be done using only hardware flags of the SH1106 display. 90 and 270 degrees however are not. These come at a price: Since we will have to it in software, a second, internal framebuffer will be created, using an additional width * height / 8 bytes of RAM. Also, each call to show() will take about 33% longer.

Set width and height in the constructor to the physical dimensions of your display, regardless of how you would like to rotate it.

You can use the flip() method to toggle between 0 and 180 degrees of rotation, or between 90 and 270 degrees, at runtime, which is equivalent to rotating the contents for 180 degrees compared to whatever the rotation was before. It is however not possible to switch from "portrait" to "landscape" or vice versa at runtime, because of the additional buffer required.

Connection

The SH1106 supports next to thers the I2C or SPI interface. The connection depends on the interface used and the number of devices in the system. Especially the ESP8266 with their small number of GPIO ports may require optimization.

I2C

SCL and SDA have to be connected as minimum. The driver also resets the device by the reset PIN. If your are low on GPIO ports, reset can be applied by a dedicated circuit, like the MCP100-300.

SPI

SCLK, MOSI, D/C are always required. If the display is the only SPI device in the set-up, CS may be tied to GND. Reset has also to be connected, unless it is driven by an external circuit.

Class

The driver contains the SH1106 class and the derived SH1106_I2C and SH1106_SPI classes. Besides the constructors, the methods are the same.

I2C

display = sh1106.SH1106_I2C(width, height, i2c, reset, address, rotate=0, delay=0)
  • width and height define the size of the display
  • i2c is an I2C object, which has to be created beforehand and tells the ports for SDA and SCL.
  • res is the GPIO Pin object for the reset connection. It will be initialized by the driver. If it is not needed, None has to be supplied.
  • adr is the I2C address of the display. Default 0x3c or 60
  • rotate defines display content rotation. See above for details and caveats.
  • delay specifies an optional delay during poweron. The quantity is ms.

SPI

display = sh1106.SH1106_SPI(width, height, spi, dc, res, cs, rotate=0, delay=0)
  • width and height define the size of the display
  • spi is an SPI object, which has to be created beforehand and tells the ports for SCLJ and MOSI. MISO is not used.
  • dc is the GPIO Pin object for the Data/Command selection. It will be initialized by the driver.
  • res is the GPIO Pin object for the reset connection. It will be initialized by the driver. If it is not needed, it can be set to None or omitted. In this case the default value of None applies.
  • cs is the GPIO Pin object for the CS connection. It will be initialized by the driver. If it is not needed, it can be set to None or omitted. In this case the default value of None applies.
  • rotate defines display content rotation. See above for details and caveats.
  • delay specifies an optional delay during poweron. The quantity is ms.

Methods

display.init_display()

display.init_display()

Initializes the display, fills it with the color 0 and displays the empty screen. It also tries to apply the reset signal, if it is connected ( = not None).

display.power_on() and display.power_off()

display.poweron()
display.poweroff()
display.sleep(state)

Enable and disable the display. display.sleep(True) is identical to display.poweroff(), display.sleep(False) is equivalent to display.poweron(). Other than the literal meaning could tell, it does not switch the power line(Vcc) of the display.

display.contrast()

display.contrast(level)

Set the display's contrast to the given level. The range is 0..255. For a single color display like the SH1106, this is actually the brightness.

display.invert()

display.invert(flag)

Invert the content of the display, depending on the value of Flag. This is immediately effective for the whole display.

  • flag = True Invert
  • flag = False Normal mode

display.flip()

display.flip([flag=None[, update=True]])

Rotate the content of the display an additional 180 degrees, depending on the value of flag.

  • True: If you selected 0 or 90 degrees of rotation in the constructor, rotation will be set to 180 or 270, respectively. Else, it has no effect.
  • False: If you selected 180 or 270 degrees of rotation in the constructor, rotation will be set to 0 or 90, respectively. Else, it has no effect.
  • None: Toggle flip on or off: 0 degrees will become 180, 90 will become 270, 180 will become 0 and 270 will become 90.

To become fully effective, you have to run display.show(). If the parameter update is True, show() is called by the function itself.

display.show()

Display the content of the frame buffer on the display.

display.show()

The usual program flow would set-up/update the frame buffer content with a sequence of calls an the call display.show() for the content to be shown (see examples below).

Framebuffer Methods

The below listed display methods of the framebuffer class are mirrored in this class. For a documentation, please look into the MicroPython documentation at http://docs.micropython.org/en/latest/pyboard/library/framebuf.html?highlight=framebuf#module-framebuf:

  • fill
  • fill_rect
  • line
  • vline
  • hline
  • rect
  • pixel
  • scroll
  • text
  • blit

The text is displayed with the built-in 8x8 pixel font, which support the ASCII character set values 32..127. The text overlays the previous content; 'on' pixels in a character will not overwrite existing 'off' pixels. If you want to rewrite an area of the screen, you have to clear it beforehand, e.g. with the fill_rect() method.

Remark: If you want to use other font styles and sizes, have a look at the work of Peter Hinch (@pythoncoder) at https://github.com/peterhinch/micropython-font-to-py

display.reset()

display.reset()

Attempt to reset the display by toggling the reset line. This is obviously only effective is reset is connected. Otherwise it's a No-Op.

Sample Code

SPI

# MicroPython SH1106 OLED driver
#
# Pin Map SPI for ESP8266
#   - 3v - xxxxxx   - Vcc
#   - G  - xxxxxx   - Gnd
#   - D7 - GPIO 13  - Din / MOSI fixed
#   - D5 - GPIO 14  - Clk / SCLK fixed
#   - D8 - GPIO 4   - CS (optional, if the only connected device)
#   - D2 - GPIO 5   - D/C
#   - D1 - GPIO 2   - Res (required, unless a Hardware reset circuit is connected)
#
# for CS, D/C and Res other ports may be chosen.
#
from machine import Pin, SPI
import sh1106

spi = SPI(1, baudrate=1000000)
display = sh1106.SH1106_SPI(128, 64, spi, Pin(5), Pin(2), Pin(4))
display.sleep(False)
display.fill(0)
display.text('Testing 1', 0, 0, 1)
display.show()

I2C

# MicroPython SH1106 OLED driver
#
# Pin Map I2C for ESP8266
#   - 3v - xxxxxx   - Vcc
#   - G  - xxxxxx   - Gnd
#   - D2 - GPIO 5   - SCK / SCL
#   - D1 - GPIO 4   - DIN / SDA
#   - D0 - GPIO 16  - Res (required, unless a Hardware reset circuit is connected)
#   - G  - xxxxxx     CS
#   - G  - xxxxxx     D/C
#
# Pin's for I2C can be set almost arbitrary
#
from machine import Pin, I2C
import sh1106

i2c = I2C(scl=Pin(5), sda=Pin(4), freq=400000)
display = sh1106.SH1106_I2C(128, 64, i2c, Pin(16), 0x3c)
display.sleep(False)
display.fill(0)
display.text('Testing 1', 0, 0, 1)
display.show()

sh1106's People

Contributors

crimier avatar freemansoft avatar pdg137 avatar peter-l5 avatar robert-hh avatar scy avatar shashfrankenstien avatar thijstriemstra avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

sh1106's Issues

hardware scroll

Hi Robert;

device is 8266.

Thanks for this driver, works great, is there a hardware scoll built in which would enable non-blocking continuous vertical and horizontal smooth scroll.

I tried adding this but it did not work:

SET_HWSCROLL_OFF    = const(0x2e)
SET_HWSCROLL_ON     = const(0x2f)
SET_HWSCROLL_RIGHT  = const(0x26)
SET_HWSCROLL_LEFT   = const(0x27)


def right(self,start=0,stop=63):
  self.write_cmd(SET_HWSCROLL_OFF)
  self.write_cmd(SET_HWSCROLL_RIGHT)
  self.write_cmd(0X00)
  self.write_cmd(start)
  self.write_cmd(0X00)
  self.write_cmd(stop)
  self.write_cmd(0X00)
  self.write_cmd(0XFF)
  self.write_cmd(SET_HWSCROLL_ON)

This will do a vertical scroll, but of course it is blocking, it would be great if this scroll could happen non-blocking !!

for y in range(53):
  oled.line(0,y-1,127,y-1,0)
  oled.scroll(0,1)
  oled.show()

What I am trying to achieve is to scroll down a page of data, wait a few seconds, then scroll down another page of data, and keep repeating the 2 pages.

I suspect that without threads this is not possible ?!

Saving data

I am using 2 pi pico board with circuitpython
one is sending data and other is receiving data which I want to save in csv or txt file

Can someone please tell me how to save data. It is fine even if I am able to save data read by the board sending the data

Constructor needs an id?

I am using SH1106 library to use the OLED display with a Raspberry Pi Pico. The error I see is as follows:

MicroPython v1.19.1 on 2022-06-18; Raspberry Pi Pico with RP2040

Type "help()" for more information.

%Run -c $EDITOR_CONTENT
Traceback (most recent call last):
File "", line 12, in
TypeError: 'id' argument required

In your documentation, I see no mention of "id". If I add "id=0" to the constructor, it works, I get no error.
My constructor is now: "i2c = I2C( id=0, sda=sda, scl=scl, freq=400000)"

proposed enhancement to read a pixel's colour as well as set it

Since at least micropython v1.9 the framebuf pixel function has had the ability to read a pixel colour as well as well as set it, see: https://docs.micropython.org/en/v1.9/pyboard/library/framebuf.html

Currently the implementation of the pixel function is as follows, which does not implement the ability to read a pixel.

    def pixel(self, x, y, color):
        super().pixel(x, y, color)
        page = y // 8
        self.pages_to_update |= 1 << page

To implement the read ability additionally, I would suggest the following formulation instead:

    def pixel(self, x, y, color=None):
        if color is None:
            return super().pixel(x, y)
        else:
            super().pixel(x, y , color)
            page = y // 8
            self.pages_to_update |= 1 << page

I have tested this possible enhancement but haven't implemented it as a pull request. Would this work?

Is this compatible with a Micro:Bit V2?

I don't want to sound stupid, but if I add this driver to a Micro:Bit V2, will it function correctly? I just want to be sure it'll work before I waste a good $20 on a display.

Pi Pico UV sensor(veml6070)

The code below is my code on pi pico
I have installed all the libraries needed.

import digitalio
import time
import board
import radio
import bmp280
import UV

led = digitalio.DigitalInOut(board.LED)
led.direction = digitalio.Direction.OUTPUT
led.value = True
cansat_temperature = bmp280.read_temperature()
cansat_pressure = bmp280.read_pressure()
room_temp = cansat_temperature
room_pressure = cansat_pressure



print("T: {:.3f} P: {:.2f}".format(cansat_temperature, cansat_pressure))

while True:
    led.value = not led.value
    print("Radio message sent")
    time.sleep(1)
    cansat_temperature = bmp280.read_temperature()
    print(cansat_temperature)
    cansat_pressure = bmp280.read_pressure()
    print(cansat_pressure)


    radio.send(
        "[CGS] Temperature: {:.2f} Pressure {:.0f}".format(room_temp, room_pressure)
    )

I am trying to code veml6070 UV sensor and the code is:

import board
import adafruit_veml6070


i2c = board.i2c()

but I am getting error:
AttributeError: 'module' object has no attribute 'i2c'

Change Text size

I might be missing it, but i can't seem to find a command to change the text size on the SH1106 display.
Is it possible to increase and decrease text size with this library?

hardware I2C - write_data()

Hi again;

Do you know how the write_data() function can be modified to work with hardware SPI (ESP32) which does not have primitive I2C start() and i2c.stop() functions ?

I have tried just self.i2c.writeto(self.addr, buf) but the display just shows noise, I think the only function that calls this is show() ?

  def _write_data(self, buf):
    self.temp[0] = self.addr << 1
    self.temp[1] = 0x40 # Co=0, D/C#=1
    self.i2c.start()
    self.i2c.write(self.temp)
    self.i2c.write(buf)
    self.i2c.stop()
  
  def write_data(self, buf):
    self.i2c.writeto(self.addr, buf)

Only 8 pixels high available

With Framebuffer1 I can use only 8 pixels high dipslay. When I changes from Framebuffer1 to Framebuffer problem solved

Adding driver to nano-gui PR #48

I've submitted a slightly modified version of this driver to nano-gui in PR 48. The change basically lets nano-gui treat the device as a flattened color display so that the color code can run on monochrome displays. nano-gui supported this. I just pasted in the appropriate boilerplate into the driver.

The PR has not yet been merged so there may be other changes. This message as a courtesy so that you know this is happening. It might make sense to add a link to nano-gui if the PR goes through, you are ok with it and think it is appropriate.

https://github.com/peterhinch/micropython-nano-gui
peterhinch/micropython-nano-gui#48

Rotate does not work as expected

I can't seem to get the display to rotate so it is upside down.
The following code always shows the same image, regardless of the rotate parameter.
I've tried 0 and 180.
Do I have to call additional methods or am I missing something?
Thank you for your help

_DSP_PIXEL_WIDTH = const(128)
_DSP_PIXEL_HEIGHT = const(64)
I2C = SoftI2C(scl=Pin(32), sda=Pin(16))
DSP = sh1106.SH1106_I2C(_DSP_PIXEL_WIDTH, _DSP_PIXEL_HEIGHT, I2C, rotate=180)
for y in (0, 14, 28, 42, 56):
    DSP.text('_Startup_...!_', 0, y)
DSP.show()
MicroPython v1.16 on 2021-06-23; ESP32 module with ESP32

OLED does not display when writing firmware for the first time

This code was invalid the first time it was run

from machine import Pin, SPI
import sh1106

spi = SPI(1, baudrate=1000000,sck=Pin(14),mosi=Pin(13))
display = sh1106.SH1106_SPI(128, 64, spi, Pin(5), Pin(2), Pin(4),rotate= 0, delay=0)
display.reset()
display.flip(True)
display.sleep(False)
display.fill(0)
display.text('hello world', 0,0, 1)
display.rect(0, 0, 50, 50, 1)
display.show()

print("testing")

This code is effective when OLED suddenly lights up,
It's very strange that I suddenly get better and can use it, but it won't show up when I write the program

OSError: I2C bus error (-1)

When I try and run this on the ESP32 with the loboris micropython version, I get the following error:

>>> from machine import Pin, I2C
>>> import sh1106
>>> i2c = I2C(scl=Pin(5), sda=Pin(4), freq=400000)
>>> display = sh1106.SH1106_I2C(128, 64, i2c, None, 0x3c)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "sh1106.py", line 173, in __init__
  File "sh1106.py", line 109, in __init__
  File "sh1106.py", line 114, in init_display
  File "sh1106.py", line 121, in poweron
  File "sh1106.py", line 178, in write_cmd
OSError: I2C bus error (-1)
>>> 

AttributeError: 'str' object has no attribute 'init'

MicroPython v1.11 on 2019-05-29; PYBv1.0 with STM32F405RG
Type "help()" for more information.

from machine import I2C, Pin
i2c=I2C(1)
i2c.scan()
[76]
import sh1106

display = sh1106.SH1106_I2C(128,32, i2c, 'none', 76)
Traceback (most recent call last):
File "", line 1, in
File "sh1106.py", line 176, in init
AttributeError: 'str' object has no attribute 'init'

Using default I2C pins

I'm trying to use a SH1106 display on a "black pill" (STM32F411CEU6). The display is connected to the first I2C module. When I specify i2c=I2C(1) - which is a supported method according to the machine.I2C docs - I get a write_data error:

"sh1106.py", line 190, in sw_write_data
OSError: I2C operation not supported

I do not understand why I get this error, since it is a supported method of machine.I2C.
With a print(i2c) I see:

I2C(1, scl=B6, sda=B7, freq=480000)

Which shows that the proper pins are used (the default pins).
When I specify these pins explicitly:

i2c=I2C(scl=Pin("PB6", sda=Pin("PB7"))

the program/library doesn't complain. Why cannot I simply use I2C(1) ?

BTW: The reason that I do not like to specify the pins is that I want to explore 'alternate pin functions' moving the default I2C pins to e.g. PB8/PB9. Note: when I specify B8/B9 (without using the alternate pin functions) then the display works also fine. Looks like software-I2C is used.

Two omissions in SPI example

Hi Robert,
Thank you for your repo.
I am using a RPi Pico on a Pimoroni Pico Breakout Garden base PIM549and a Pimoroni OLED SPI 1.12in 128x128px monochrome (SH1107) PIM473.
For this SH1107 there are several Circuitpython solutions and solutions to run on a Raspberry Pi (Raspberry Pi Operating System). But all these use various libraries with a lot of dependencies. So I was looking for a Micropython solution. I google'd and found your repo.
In your README.md, in the remarks about SPI you wrote: SCLK, MOSI, D/C are always required. .
In the example for SPI in your sh1106.py script, line 44, you wrote: spi = SPI(1, baudrate=1000000).
TMHO there are in this call to SPI() :
missing parameters for mosi and sclk;

For my hardware the call to SPI, first parameter has to be a value 0. So, for my hardware the call to SPI is: spi = SPI(0, 1000000, mosi=Pin(mosi), sck=Pin(sclk)).
And with this it works for my SH1107. See the image:

OLED_SH1107_test_w_SH1106_class_micropython

SPI bus getting reinitialized to 10 MHz

In SPI mode, it seems that before each write the bus gets reinitialized to a hard-coded 10 MHz:

SH1106/sh1106.py

Lines 280 to 282 in 5374dc5

def write_cmd(self, cmd):
self.spi.init(baudrate=self.rate, polarity=0, phase=0)
if self.cs is not None:

SH1106/sh1106.py

Lines 292 to 294 in 5374dc5

def write_data(self, buf):
self.spi.init(baudrate=self.rate, polarity=0, phase=0)
if self.cs is not None:

It works on my devices at that speed, but the SH1106 seems to only be rated for 2 or 4 MHz depending on Vdd. And since the user already initialized the SPI object, why change the speed that they presumably selected? Maybe they want to go faster or slower. It also seems like this will introduce some unnecessary delays and risks glitches on the output pins depending on what init() does exactly.

i2c not working on certain brand of 1.3" oled display

Hi,

Using this with the PICO and the library works fine with one brand of 1.3" display.

But it doesn't work with another brand.

https://www.ebay.com/itm/163020994321

I believe it's sh1106 - same code..same wiring...I just swap the units and restart.

The test code I used was your sample at the top of the lib source code.

Both have the same port address - 0x78 in the back and I'm using 0x3c in the constructor.

The error I get is:

Traceback (most recent call last):
File "", line 9, in
File "/lib/sh1106.py", line 183, in init
File "/lib/sh1106.py", line 114, in init
File "/lib/sh1106.py", line 119, in init_display
File "/lib/sh1106.py", line 127, in poweron
File "/lib/sh1106.py", line 188, in write_cmd
OSError: [Errno 5] EIO

I tested 2 separate units of the new display type and also the wiring.

Pi Pico

Hi, i can't get this driver to work on the Pi Pico

I use; display = sh1106.SH1106_I2C(128, 64, i2c, Pin(16), 0x3c)

all I get is;

File "sh1106.py", line 21, in
AttributeError: 'module' object has no attribute 'SH1106_I2C'

any ideas?

.blit with negative y offset fails

If you blit an image on the sh1106 framebuf with negative offset, this line makes it attempt to register changes off the screen, which fails with an error:

start_page = y0 // 8

I think changing it to start_page = max(0, y0 // 8) solves the problem.

Incorrect calculation of pages to update

When using a framebuffer method with a h parameter the number of pages to update can sometimes be calculated incorrectly. This is especially an issue if clearing a row of text by writing a rectangle on top of it.

So, for example, instead of:
def rect(self, x, y, w, h, color):
super().rect(x, y, w, h, color)
self.register_updates(y, y+h)

the code should read, as follows (-1 added to third line):
def rect(self, x, y, w, h, color):
super().rect(x, y, w, h, color)
self.register_updates(y, y+h-1)

When the screen is rotated by 90 degrees the partial updates are not working properly

Partial updates do not work properly when the screen is rotated by 90 degrees.
in normal orientation the display can be written in horizontal pages, 8 pixels high.
if the framebuffer is effective rotated by 90 degrees by using colour mode: MONO_HMSB, then writing pages to the display writes vertical stripes of 8 pixels rather than horizontal ones. As a result partial updates for a rotated display do not work properly.
It is possible to fix this by changing the display's memory addressing mode to vertical addressing and writing 1 row at a time.
I have written some code that implements this for the similar SH1107 display driver IC and will post it in the coming days.

Use this project for Python-Raspberry Pi

Hi, I was really happy to see this directory, very much I am looking for a screen with driver sh1122 lib.
But I was sorry to see that it was not for Python but for Micro Python. Do you think you have the option to match Python?
Or I would fit in if it weren't too complicated.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.