rm-hull / luma.oled

Python module to drive a SSD1306 / SSD1309 / SSD1322 / SSD1325 / SSD1327 / SSD1331 / SSD1351 / SH1106 OLED
https://luma-oled.readthedocs.io
MIT License
812 stars 162 forks source link

Wrong column offset 1.54" SH1106 I2C #376

Open blenherr opened 1 year ago

blenherr commented 1 year ago

Type of Raspberry Pi

CM4 + Original cm4io Board

Linux Kernel version

Linux NasPi 6.1.36-v8+ #1659 SMP PREEMPT Fri Jun 30 13:19:42 BST 2023 aarch64 GNU/Linux

Test: python3 examples/demo.py -d sh1106 --i2c-port=1 --width=128 --height=64 Used display 4pin I2C white: https://de.aliexpress.com/item/1005002767134354.html

First 2 column where not used: K640_20230724_175757

I had to change self.command(set_page_address, 0x02, 0x10) to self.command(set_page_address, 0x00, 0x10) in class sh1106 / def display. K640_20230724_175818

Is this a bug or just depending on used display? Is this the correct way to fix the issue?

thijstriemstra commented 1 year ago

No idea but glad you got it working correctly. I haven't seen reports on SH1106 I2C having a wrong offset (lately).

thijstriemstra commented 1 year ago

I wonder if https://github.com/rm-hull/luma.oled/commit/72440dab28c9ab6daee85b5cf2381c1f8747617d is related. Could you try the older 3.11.0 luma.oled and see if that changes things @blenherr ?

blenherr commented 1 year ago

Successfully installed luma.core-2.4.0 luma.oled-3.11.0 pillow-10.0.0 pyftdi-0.54.0

Does not help, still 2 pixel offset. K640_20230730_125255

blenherr commented 1 year ago

I wonder if 72440da is related. Could you try the older 3.11.0 luma.oled and see if that changes things @blenherr ?

Is 72440da related to ssd1306 only? I use sh1106:

class sh1106(device):
    """
    Serial interface to a monochrome SH1106 OLED display.

    On creation, an initialization sequence is pumped to the display
    to properly configure it. Further control commands can then be called to
    affect the brightness and other settings.
    """

    def __init__(self, serial_interface=None, width=128, height=64, rotate=0, **kwargs):
        super(sh1106, self).__init__(luma.oled.const.sh1106, serial_interface)
        self.capabilities(width, height, rotate)
        self._pages = self._h // 8

        settings = {
            (128, 128): dict(multiplex=0xFF, displayoffset=0x02),
            (128, 64): dict(multiplex=0x3F, displayoffset=0x00),
            (128, 32): dict(multiplex=0x20, displayoffset=0x0F)
        }.get((width, height))

        if settings is None:
            raise luma.core.error.DeviceDisplayModeError(
                f"Unsupported display mode: {width} x {height}")

        self.command(
            self._const.DISPLAYOFF,
            self._const.MEMORYMODE,
            self._const.SETHIGHCOLUMN,      0xB0, 0xC8,
            self._const.SETLOWCOLUMN,       0x10, 0x40,
            self._const.SETSEGMENTREMAP,
            self._const.NORMALDISPLAY,
            self._const.SETMULTIPLEX,       settings['multiplex'],
            self._const.DISPLAYALLON_RESUME,
            self._const.SETDISPLAYOFFSET,   settings['displayoffset'],
            self._const.SETDISPLAYCLOCKDIV, 0xF0,
            self._const.SETPRECHARGE,       0x22,
            self._const.SETCOMPINS,         0x12,
            self._const.SETVCOMDETECT,      0x20,
            self._const.CHARGEPUMP,         0x14)

        self.contrast(0x7F)
        self.clear()
        self.show()

    def display(self, image):
        """
        Takes a 1-bit :py:mod:`PIL.Image` and dumps it to the SH1106
        OLED display.

        :param image: Image to display.
        :type image: :py:mod:`PIL.Image`
        """
        assert image.mode == self.mode
        assert image.size == self.size

        image = self.preprocess(image)

        set_page_address = 0xB0
        image_data = image.getdata()
        pixels_per_page = self.width * 8
        buf = bytearray(self.width)

        for y in range(0, int(self._pages * pixels_per_page), pixels_per_page):
            self.command(set_page_address, 0x00, 0x10) # <----- HERE -----
            set_page_address += 1
            offsets = [y + self.width * i for i in range(8)]

            for x in range(self.width):
                buf[x] = \
                    (image_data[x + offsets[0]] and 0x01) | \
                    (image_data[x + offsets[1]] and 0x02) | \
                    (image_data[x + offsets[2]] and 0x04) | \
                    (image_data[x + offsets[3]] and 0x08) | \
                    (image_data[x + offsets[4]] and 0x10) | \
                    (image_data[x + offsets[5]] and 0x20) | \
                    (image_data[x + offsets[6]] and 0x40) | \
                    (image_data[x + offsets[7]] and 0x80)

            self.data(list(buf))
blenherr commented 1 year ago

My test script:

from time import sleep
from luma.core.interface.serial import i2c
from luma.core.render import canvas
from luma.oled.device import sh1106

# Display
PORT = 1
ADDRESS = 0x3C
WIDTH = 128
HEIGHT = 64
# Initialize device
serial = i2c(port=PORT, address=ADDRESS)
device = sh1106(serial, width=WIDTH, height=HEIGHT)

while True:
    with canvas(device) as draw:
        draw.rectangle(device.bounding_box, outline="white", fill="black")
        draw.text((30, 26), "Hello World", fill="white")
    sleep(0.01)
dbilodeau commented 3 months ago

Picked up these two OLED displays for a small rpi4 project (note: they're not SSD1309 displays as advertised, they're CH1116 which is working with driver SH1106).

I had to change self.command(set_page_address, 0x02, 0x10) to self.command(set_page_address, 0x00, 0x10) in class sh1106 / def display.

This fix above was needed to rid these OLEDs of the 2px vertical lines that wouldn't clear on the left-hand side of the display. Thanks for sharing your fix!!!