KMKfw / kmk_firmware

Clackety Keyboards Powered by Python
https://kmkfw.zulipchat.com
Other
1.34k stars 462 forks source link

Add SH1106 display support #846

Closed Anomalocaridid closed 11 months ago

Anomalocaridid commented 12 months ago

Currently, the OLED extension only supports displays that use the SSD1306 chipset. This PR adds support for displays with the SH1106 chipset.

Essentially, I accomplished this by making both displays display types through subclasses of a new class called OledDisplayType. These would be created and passed to the constructor for Oled rather than passing display-specific parameters such as I2C and SPI pins. Display-specific logic is handled by methods in each display's class, which are called by the Oled class's methods with the same names.

Using an SH1106 display requires the adafruit_displayio_sh1106 library, but I made it so that you only need the library for the display(s) you are using by importing them within the displays' classes' methods rather than globally.

I did some minor testing and it looks like it works fine. However, I noticed that one short line of pixels one the edge of the screen, presumably from the CircuitPython boot screen, lingers and I am not entirely sure what the cause is. Also I do not own a keyboard with a SSD1306 display, so I need someone else who has one to check that I did not break anything.

Unfortunately, this PR introduces some breaking changes for users and might interfere with #830. However, I am more than happy to wait until #830 gets merged first and then rebase my PR branch and update the documentation myself if necessary.

Instead of initializing the OLED extension with something like:

import board
import busio
from kmk.extensions.oled import Oled

i2c_bus = busio.I2C(board.SCL, board.SDA)
Oled(i2c=i2c_bus)

You would use something like:

import board
import busio
from kmk.extensions.oled import Oled, SSD1306

i2c_bus = busio.I2C(board.SCL, board.SDA)
display_type = SSD1306(i2c=i2c_bus)

oled = Oled(display_type=display)

For an SH1106 display, it would look like:

import board
import busio
from kmk.extensions.oled import Oled, SH1106

spi_bus = busio.SPI(board.SCK, board.MOSI)
display_type = SH1106(spi=spi_bus, command=board.DC, chip_select=board.CS, reset=board.RESET)

oled = Oled(display_type=display)

Note that for the SH1106 display, it requires pins other than the ones used to create spi_bus. Also, I made sure to include the option to just specify the pins and have the classes' __init__() methods create the I2C/SPI bus for you like it was before.

Anomalocaridid commented 11 months ago

Apparently some displays' drivers are built into CircuitPython and do not need any manual initialization. So I added support for those. Interestingly, this includes my own macropad and using this instead of the SH1106 library solves the problem I mentioned earlier:

However, I noticed that one short line of pixels one the edge of the screen, presumably from the CircuitPython boot screen, lingers and I am not entirely sure what the cause is.

You would initialize one of these displays like this:

import board
from kmk.extensions.oled import Oled, BuiltInDisplay

display_type = BuiltInDisplay(display=board.DISPLAY)

oled = Oled(display_type=display)

Of couse, using displayio.release_keymaps() would interfere with already-initialized displays, so I moved that to the other displays' __init__() methods

xs5871 commented 11 months ago

I generally approve, give some time to go into the review.

Anomalocaridid commented 11 months ago

Sorry for adding another change after you already started reviewing my pull request, but I discovered that my last change breaks sleep and wake functionality for built-in displays, so I went ahead and fixed it.

BuiltInDisplay's constructor now takes numbers corresponding to display-specific sleep and wake commands. Sleeping and waking is now handled by the respective methods in the different display types' classes rather than directly by the display's methods.

Using a BuiltInDisplay now looks like this:

import board
from kmk.extensions.oled import Oled, BuiltInDisplay

# Command arguments shown are for example purposes. I assume different displays have different commands for this.
display_type = BuiltInDisplay(display=board.DISPLAY, sleep_command=0xAE, wake_command=0xAF)

oled = Oled(display_type=display)
Anomalocaridid commented 11 months ago

How's this?

Anomalocaridid commented 11 months ago

Thank you!

regicidalplutophage commented 11 months ago

Good work @Anomalocaridid @xs5871 !