diff --git a/builddefs/common_features.mk b/builddefs/common_features.mk
index c976b8296d5a..3ca1af40c351 100644
--- a/builddefs/common_features.mk
+++ b/builddefs/common_features.mk
@@ -658,6 +658,10 @@ endif
VALID_OLED_DRIVER_TYPES := SSD1306 custom
OLED_DRIVER ?= SSD1306
+
+VALID_OLED_DRIVER_BUS_TYPES := spi i2c
+OLED_DRIVER_BUS ?= i2c
+
ifeq ($(strip $(OLED_ENABLE)), yes)
ifeq ($(filter $(OLED_DRIVER),$(VALID_OLED_DRIVER_TYPES)),)
$(call CATASTROPHIC_ERROR,Invalid OLED_DRIVER,OLED_DRIVER="$(OLED_DRIVER)" is not a valid OLED driver)
@@ -668,7 +672,17 @@ ifeq ($(strip $(OLED_ENABLE)), yes)
OPT_DEFS += -DOLED_DRIVER_$(strip $(shell echo $(OLED_DRIVER) | tr '[:lower:]' '[:upper:]'))
ifeq ($(strip $(OLED_DRIVER)), SSD1306)
SRC += ssd1306_sh1106.c
- QUANTUM_LIB_SRC += i2c_master.c
+ ifeq ($(filter $(OLED_DRIVER_BUS),$(VALID_OLED_DRIVER_BUS_TYPES)),)
+ $(call CATASTROPHIC_ERROR,Invalid OLED_DRIVER_BUS,OLED_DRIVER_BUS="$(OLED_DRIVER_BUS)" is not a valid OLED driver)
+ else
+ ifeq ($(strip $(OLED_DRIVER_BUS)), i2c)
+ QUANTUM_LIB_SRC += i2c_master.c
+ OPT_DEFS += -DOLED_BUS=0
+ else
+ QUANTUM_LIB_SRC += spi_master.c
+ OPT_DEFS += -DOLED_BUS=1
+ endif
+ endif
endif
endif
endif
diff --git a/docs/feature_oled_driver.md b/docs/feature_oled_driver.md
index 0d04f007f8bc..a4bf395ec8a1 100644
--- a/docs/feature_oled_driver.md
+++ b/docs/feature_oled_driver.md
@@ -2,7 +2,7 @@
## Supported Hardware
-OLED modules using SSD1306 or SH1106 driver ICs, communicating over I2C.
+OLED modules using SSD1306 or SH1106 driver ICs, communicating over I2C or SPI.
Tested combinations:
|IC |Size |Platform|Notes |
@@ -18,7 +18,7 @@ Hardware configurations using Arm-based microcontrollers or different sizes of O
## Usage
-To enable the OLED feature, there are two steps. First, when compiling your keyboard, you'll need to add the following to your `rules.mk`:
+To enable the OLED feature, there are typically two steps. First, when compiling your keyboard, you'll need to add the following to your `rules.mk`:
```make
OLED_ENABLE = yes
@@ -34,6 +34,17 @@ e.g.
OLED_DRIVER = SSD1306
```
+## OLED bus
+|OLED Bus |Tested Device |
+|-------------------|---------------------------|
+|I2C (default) |SSD1306 and SH1106 |
+|SPI |SH1106 |
+
+e.g.
+```make
+OLED_DRIVER_BUS = i2c
+```
+
Then in your `keymap.c` file, implement the OLED task call. This example assumes your keymap has three layers named `_QWERTY`, `_FN` and `_ADJ`:
```c
@@ -174,6 +185,23 @@ These configuration options should be placed in `config.h`. Example:
|`OLED_BRIGHTNESS` |`255` |The default brightness level of the OLED, from 0 to 255. |
|`OLED_UPDATE_INTERVAL` |`0` |Set the time interval for updating the OLED display in ms. This will improve the matrix scan rate. |
+
+## Configuring a SPI OLED
+
+If using a SPI OLED display, you'll need to define the following pins in your board's `config.h` and configure the (SPI Master Driver)[spi_driver.md]`
+
+|Defines |Description |
+|-------------------|----------------------------------------------------------------|
+|OLED_DC_PIN |Pin that determines whether the data pins are data or command |
+|OLED_CS_PIN |Pin that is used to select the chip |
+|OLED_RST_PIN |Pin to reset the display |
+
+You can also define the mode and divisor using the following defines:
+
+|Define |Default |Description |
+|---------------------------|-----------------|-----------------------------------------------------------|
+|`OLED_SPI_MODE` |`3` |The SPI mode to use with the OLED Display |
+|`OLED_SPI_DIVISOR` |`2` |The SPI clock divisoR to be used with the OLED Display |
## 128x64 & Custom sized OLED Displays
The default display size for this feature is 128x32 and all necessary defines are precalculated with that in mind. We have added a define, `OLED_DISPLAY_128X64`, to switch all the values to be used in a 128x64 display, as well as added a custom define, `OLED_DISPLAY_CUSTOM`, that allows you to provide the necessary values to the driver.
diff --git a/drivers/oled/oled_driver.h b/drivers/oled/oled_driver.h
index 918b837f07e8..7b184f562d19 100644
--- a/drivers/oled/oled_driver.h
+++ b/drivers/oled/oled_driver.h
@@ -23,6 +23,32 @@ along with this program. If not, see .
#define OLED_IC_SSD1306 0
#define OLED_IC_SH1106 1
+#define OLED_BUS_I2C 0
+#define OLED_BUS_SPI 1
+
+// Define the bus for the chip
+#ifndef OLED_BUS
+# define OLED_BUS OLED_BUS_I2C //Defaults to I2C
+#endif
+
+#if (OLED_BUS == OLED_BUS_SPI)
+# ifndef OLED_DC_PIN
+# error "The OLED driver in SPI needs a D/C pin defined"
+# endif
+# ifndef OLED_CS_PIN
+# error "The OLED driver in SPI needs a CS pin defined"
+# endif
+# ifndef OLED_CS_PIN
+# error "The OLED driver in SPI needs a CS pin defined"
+# endif
+# ifndef OLED_SPI_MODE
+# define OLED_SPI_MODE 3
+# endif
+# ifndef OLED_SPI_DIVISOR
+# define OLED_SPI_DIVISOR 2
+# endif
+#endif
+
#if defined(OLED_DISPLAY_CUSTOM)
// Expected user to implement the necessary defines
#elif defined(OLED_DISPLAY_128X64)
diff --git a/drivers/oled/ssd1306_sh1106.c b/drivers/oled/ssd1306_sh1106.c
index 30cfeb5648dd..e0bcf02c2d87 100644
--- a/drivers/oled/ssd1306_sh1106.c
+++ b/drivers/oled/ssd1306_sh1106.c
@@ -14,8 +14,16 @@ GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
*/
-#include "i2c_master.h"
#include "oled_driver.h"
+
+#if (OLED_BUS == OLED_BUS_I2C)
+# include "i2c_master.h"
+#endif
+#if (OLED_BUS == OLED_BUS_SPI)
+# include "spi_master.h"
+#endif
+
+#include
#include OLED_FONT_H
#include "timer.h"
#include "print.h"
@@ -92,16 +100,123 @@ along with this program. If not, see .
#define OLED_ALL_BLOCKS_MASK (((((OLED_BLOCK_TYPE)1 << (OLED_BLOCK_COUNT - 1)) - 1) << 1) | 1)
-// i2c defines
-#define I2C_CMD 0x00
-#define I2C_DATA 0x40
-#if defined(__AVR__)
-# define I2C_TRANSMIT_P(data) i2c_transmit_P((OLED_DISPLAY_ADDRESS << 1), &data[0], sizeof(data), OLED_I2C_TIMEOUT)
-#else // defined(__AVR__)
-# define I2C_TRANSMIT_P(data) i2c_transmit((OLED_DISPLAY_ADDRESS << 1), &data[0], sizeof(data), OLED_I2C_TIMEOUT)
-#endif // defined(__AVR__)
-#define I2C_TRANSMIT(data) i2c_transmit((OLED_DISPLAY_ADDRESS << 1), &data[0], sizeof(data), OLED_I2C_TIMEOUT)
-#define I2C_WRITE_REG(mode, data, size) i2c_writeReg((OLED_DISPLAY_ADDRESS << 1), mode, data, size, OLED_I2C_TIMEOUT)
+#define ARRAY_SIZE(arr) sizeof(arr)/sizeof(arr[0])
+
+#if OLED_BUS == OLED_BUS_I2C // i2c defines
+# define I2C_CMD 0x00
+# define I2C_DATA 0x40
+# define OLED_STATUS_SUCCESS I2C_STATUS_SUCCESS
+
+// Internal variables to reduce math instructions
+# if defined(__AVR__)
+// identical to i2c_transmit, but for PROGMEM since all initialization is in PROGMEM arrays currently
+// probably should move this into i2c_master...
+static i2c_status_t i2c_transmit_P(uint8_t address, const uint8_t *data, uint16_t length, uint16_t timeout) {
+ i2c_status_t status = i2c_start(address | I2C_WRITE, timeout);
+
+ for (uint16_t i = 0; i < length && status >= 0; i++) {
+ status = i2c_write(pgm_read_byte((const char *)data++), timeout);
+ if (status) break;
+ }
+
+ i2c_stop();
+
+ return status;
+}
+# endif
+#else // spi defines
+# define OLED_STATUS_SUCCESS SPI_STATUS_SUCCESS
+
+ void oled_spi_init(void) {
+ spi_init();
+
+ setPinOutput(OLED_CS_PIN);
+ writePinHigh(OLED_CS_PIN);
+
+ setPinOutput(OLED_DC_PIN);
+ writePinLow(OLED_DC_PIN);
+ }
+
+ void oled_spi_start(void) {
+ spi_start(OLED_CS_PIN, false, OLED_SPI_MODE, OLED_SPI_DIVISOR);
+ }
+
+ void oled_spi_stop(void) {
+ spi_stop();
+ }
+#endif
+
+
+// Transmit/Write Funcs.
+bool oled_cmd(const uint8_t *data, uint16_t size) {
+#if (OLED_BUS == OLED_BUS_I2C)
+ // Send the I2C command
+ if(i2c_transmit((OLED_DISPLAY_ADDRESS << 1), I2C_CMD, 1, OLED_I2C_TIMEOUT) != OLED_STATUS_SUCCESS){
+ return false;
+ }
+ // Send the commands
+ if(i2c_transmit((OLED_DISPLAY_ADDRESS << 1), data, size, OLED_I2C_TIMEOUT) != OLED_STATUS_SUCCESS){
+ return false;
+ }
+
+ return true;
+#else
+ oled_spi_start();
+ // Command Mode
+ writePinLow(OLED_DC_PIN);
+ // Send the commands
+ if(spi_transmit(data, size) != OLED_STATUS_SUCCESS){
+ oled_spi_stop();
+ return false;
+ }
+ oled_spi_stop();
+ return true;
+#endif // (OLED_BUS == OLED_BUS_I2C)
+}
+
+bool oled_cmd_p(const uint8_t *data, uint16_t size) {
+#if (OLED_BUS == OLED_BUS_I2C && defined(__AVR__))
+ // Send the I2C command
+ if(i2c_transmit_P((OLED_DISPLAY_ADDRESS << 1), I2C_CMD, 1, OLED_I2C_TIMEOUT) != OLED_STATUS_SUCCESS){
+ return false;
+ }
+ // Send the commands
+ if(i2c_transmit_P((OLED_DISPLAY_ADDRESS << 1), I2C_CMD, size, OLED_I2C_TIMEOUT) != OLED_STATUS_SUCCESS){
+ return false;
+ }
+
+ return true;
+#else
+ return oled_cmd(data, size);
+#endif // (OLED_BUS == OLED_BUS_I2C) && defined(__AVR__)
+}
+
+bool oled_write_reg(const uint8_t *data, uint16_t size)
+{
+#if (OLED_BUS == OLED_BUS_I2C)
+ // Send the I2C command
+ if(i2c_transmit((OLED_DISPLAY_ADDRESS << 1), I2C_CMD, 1, OLED_I2C_TIMEOUT) != OLED_STATUS_SUCCESS){
+ return false;
+ }
+ // Send the data
+ if(i2c_transmit((OLED_DISPLAY_ADDRESS << 1), data, size, OLED_I2C_TIMEOUT) != OLED_STATUS_SUCCESS){
+ return false;
+ }
+
+ return true;
+#else
+ oled_spi_start();
+ // Command Mode
+ writePinHigh(OLED_DC_PIN);
+ // Send the commands
+ if(spi_transmit(data, size) != OLED_STATUS_SUCCESS){
+ oled_spi_stop();
+ return false;
+ }
+ oled_spi_stop();
+ return true;
+#endif // (OLED_BUS == OLED_BUS_I2C)
+}
#define HAS_FLAGS(bits, flags) ((bits & flags) == flags)
@@ -132,25 +247,6 @@ uint32_t oled_scroll_timeout;
uint16_t oled_update_timeout;
#endif
-// Internal variables to reduce math instructions
-
-#if defined(__AVR__)
-// identical to i2c_transmit, but for PROGMEM since all initialization is in PROGMEM arrays currently
-// probably should move this into i2c_master...
-static i2c_status_t i2c_transmit_P(uint8_t address, const uint8_t *data, uint16_t length, uint16_t timeout) {
- i2c_status_t status = i2c_start(address | I2C_WRITE, timeout);
-
- for (uint16_t i = 0; i < length && status >= 0; i++) {
- status = i2c_write(pgm_read_byte((const char *)data++), timeout);
- if (status) break;
- }
-
- i2c_stop();
-
- return status;
-}
-#endif
-
// Flips the rendering bits for a character at the current cursor position
static void InvertCharacter(uint8_t *cursor) {
const uint8_t *end = cursor + OLED_FONT_WIDTH;
@@ -173,10 +269,23 @@ bool oled_init(oled_rotation_t rotation) {
} else {
oled_rotation_width = OLED_DISPLAY_HEIGHT;
}
+
+#if (OLED_BUS == OLED_BUS_I2C)
i2c_init();
+#else
+ oled_spi_init();
+#endif
+
+#ifdef OLED_RST_PIN
+ /* Reset device */
+ setPinOutput(OLED_RST_PIN);
+ writePinLow(OLED_RST_PIN);
+ wait_ms(20);
+ writePinHigh(OLED_RST_PIN);
+ wait_ms(20);
+#endif
static const uint8_t PROGMEM display_setup1[] = {
- I2C_CMD,
DISPLAY_OFF,
DISPLAY_CLOCK,
0x80,
@@ -193,27 +302,28 @@ bool oled_init(oled_rotation_t rotation) {
0x00, // Horizontal addressing mode
#endif
};
- if (I2C_TRANSMIT_P(display_setup1) != I2C_STATUS_SUCCESS) {
+
+ if (!oled_cmd_p(display_setup1, ARRAY_SIZE(display_setup1))) {
print("oled_init cmd set 1 failed\n");
return false;
}
if (!HAS_FLAGS(oled_rotation, OLED_ROTATION_180)) {
- static const uint8_t PROGMEM display_normal[] = {I2C_CMD, SEGMENT_REMAP_INV, COM_SCAN_DEC};
- if (I2C_TRANSMIT_P(display_normal) != I2C_STATUS_SUCCESS) {
+ static const uint8_t PROGMEM display_normal[] = {SEGMENT_REMAP_INV, COM_SCAN_DEC};
+ if (!oled_cmd_p(display_normal, ARRAY_SIZE(display_normal))) {
print("oled_init cmd normal rotation failed\n");
return false;
}
} else {
- static const uint8_t PROGMEM display_flipped[] = {I2C_CMD, SEGMENT_REMAP, COM_SCAN_INC};
- if (I2C_TRANSMIT_P(display_flipped) != I2C_STATUS_SUCCESS) {
+ static const uint8_t PROGMEM display_flipped[] = {SEGMENT_REMAP, COM_SCAN_INC};
+ if (!oled_cmd_p(display_flipped, ARRAY_SIZE(display_flipped))) {
print("display_flipped failed\n");
return false;
}
}
- static const uint8_t PROGMEM display_setup2[] = {I2C_CMD, COM_PINS, OLED_COM_PINS, CONTRAST, OLED_BRIGHTNESS, PRE_CHARGE_PERIOD, 0xF1, VCOM_DETECT, 0x20, DISPLAY_ALL_ON_RESUME, NORMAL_DISPLAY, DEACTIVATE_SCROLL, DISPLAY_ON};
- if (I2C_TRANSMIT_P(display_setup2) != I2C_STATUS_SUCCESS) {
+ static const uint8_t PROGMEM display_setup2[] = {COM_PINS, OLED_COM_PINS, CONTRAST, OLED_BRIGHTNESS, PRE_CHARGE_PERIOD, 0xF1, VCOM_DETECT, 0x20, DISPLAY_ALL_ON_RESUME, NORMAL_DISPLAY, DEACTIVATE_SCROLL, DISPLAY_ON};
+ if (!oled_cmd_p(display_setup2, ARRAY_SIZE(display_setup2))) {
print("display_setup2 failed\n");
return false;
}
@@ -308,22 +418,22 @@ void oled_render(void) {
}
// Set column & page position
- static uint8_t display_start[] = {I2C_CMD, COLUMN_ADDR, 0, OLED_DISPLAY_WIDTH - 1, PAGE_ADDR, 0, OLED_DISPLAY_HEIGHT / 8 - 1};
+ static uint8_t display_start[] = {COLUMN_ADDR, 0, OLED_DISPLAY_WIDTH - 1, PAGE_ADDR, 0, OLED_DISPLAY_HEIGHT / 8 - 1};
if (!HAS_FLAGS(oled_rotation, OLED_ROTATION_90)) {
- calc_bounds(update_start, &display_start[1]); // Offset from I2C_CMD byte at the start
+ calc_bounds(update_start, display_start);
} else {
- calc_bounds_90(update_start, &display_start[1]); // Offset from I2C_CMD byte at the start
+ calc_bounds_90(update_start, display_start);
}
// Send column & page position
- if (I2C_TRANSMIT(display_start) != I2C_STATUS_SUCCESS) {
+ if (!oled_cmd(display_start, ARRAY_SIZE(display_start))) {
print("oled_render offset command failed\n");
return;
}
if (!HAS_FLAGS(oled_rotation, OLED_ROTATION_90)) {
// Send render data chunk as is
- if (I2C_WRITE_REG(I2C_DATA, &oled_buffer[OLED_BLOCK_SIZE * update_start], OLED_BLOCK_SIZE) != I2C_STATUS_SUCCESS) {
+ if (!oled_write_reg(&oled_buffer[OLED_BLOCK_SIZE * update_start], OLED_BLOCK_SIZE)) {
print("oled_render data failed\n");
return;
}
@@ -339,7 +449,7 @@ void oled_render(void) {
}
// Send render data chunk after rotating
- if (I2C_WRITE_REG(I2C_DATA, &temp_buffer[0], OLED_BLOCK_SIZE) != I2C_STATUS_SUCCESS) {
+ if (!oled_write_reg(temp_buffer, OLED_BLOCK_SIZE)) {
print("oled_render90 data failed\n");
return;
}
@@ -563,13 +673,13 @@ bool oled_on(void) {
static const uint8_t PROGMEM display_on[] =
#ifdef OLED_FADE_OUT
- {I2C_CMD, FADE_BLINK, 0x00};
+ {FADE_BLINK, 0x00};
#else
- {I2C_CMD, DISPLAY_ON};
+ {DISPLAY_ON};
#endif
if (!oled_active) {
- if (I2C_TRANSMIT_P(display_on) != I2C_STATUS_SUCCESS) {
+ if (!oled_cmd_p(display_on, ARRAY_SIZE(display_on))) {
print("oled_on cmd failed\n");
return oled_active;
}
@@ -585,13 +695,13 @@ bool oled_off(void) {
static const uint8_t PROGMEM display_off[] =
#ifdef OLED_FADE_OUT
- {I2C_CMD, FADE_BLINK, ENABLE_FADE | OLED_FADE_OUT_INTERVAL};
+ {FADE_BLINK, ENABLE_FADE | OLED_FADE_OUT_INTERVAL};
#else
- {I2C_CMD, DISPLAY_OFF};
+ {DISPLAY_OFF};
#endif
if (oled_active) {
- if (I2C_TRANSMIT_P(display_off) != I2C_STATUS_SUCCESS) {
+ if (!oled_cmd_p(display_off, ARRAY_SIZE(display_off))) {
print("oled_off cmd failed\n");
return oled_active;
}
@@ -609,9 +719,9 @@ uint8_t oled_set_brightness(uint8_t level) {
return oled_brightness;
}
- uint8_t set_contrast[] = {I2C_CMD, CONTRAST, level};
+ uint8_t set_contrast[] = { CONTRAST, level};
if (oled_brightness != level) {
- if (I2C_TRANSMIT(set_contrast) != I2C_STATUS_SUCCESS) {
+ if (!oled_cmd(set_contrast, ARRAY_SIZE(set_contrast))) {
print("set_brightness cmd failed\n");
return oled_brightness;
}
@@ -657,8 +767,8 @@ bool oled_scroll_right(void) {
// Dont enable scrolling if we need to update the display
// This prevents scrolling of bad data from starting the scroll too early after init
if (!oled_dirty && !oled_scrolling) {
- uint8_t display_scroll_right[] = {I2C_CMD, SCROLL_RIGHT, 0x00, oled_scroll_start, oled_scroll_speed, oled_scroll_end, 0x00, 0xFF, ACTIVATE_SCROLL};
- if (I2C_TRANSMIT(display_scroll_right) != I2C_STATUS_SUCCESS) {
+ uint8_t display_scroll_right[] = {SCROLL_RIGHT, 0x00, oled_scroll_start, oled_scroll_speed, oled_scroll_end, 0x00, 0xFF, ACTIVATE_SCROLL};
+ if (!oled_cmd(display_scroll_right, ARRAY_SIZE(display_scroll_right))) {
print("oled_scroll_right cmd failed\n");
return oled_scrolling;
}
@@ -675,8 +785,8 @@ bool oled_scroll_left(void) {
// Dont enable scrolling if we need to update the display
// This prevents scrolling of bad data from starting the scroll too early after init
if (!oled_dirty && !oled_scrolling) {
- uint8_t display_scroll_left[] = {I2C_CMD, SCROLL_LEFT, 0x00, oled_scroll_start, oled_scroll_speed, oled_scroll_end, 0x00, 0xFF, ACTIVATE_SCROLL};
- if (I2C_TRANSMIT(display_scroll_left) != I2C_STATUS_SUCCESS) {
+ uint8_t display_scroll_left[] = {SCROLL_LEFT, 0x00, oled_scroll_start, oled_scroll_speed, oled_scroll_end, 0x00, 0xFF, ACTIVATE_SCROLL};
+ if (!oled_cmd(display_scroll_left, ARRAY_SIZE(display_scroll_left))) {
print("oled_scroll_left cmd failed\n");
return oled_scrolling;
}
@@ -691,8 +801,8 @@ bool oled_scroll_off(void) {
}
if (oled_scrolling) {
- static const uint8_t PROGMEM display_scroll_off[] = {I2C_CMD, DEACTIVATE_SCROLL};
- if (I2C_TRANSMIT_P(display_scroll_off) != I2C_STATUS_SUCCESS) {
+ static const uint8_t PROGMEM display_scroll_off[] = {DEACTIVATE_SCROLL};
+ if (!oled_cmd_p(display_scroll_off, ARRAY_SIZE(display_scroll_off))) {
print("oled_scroll_off cmd failed\n");
return oled_scrolling;
}
@@ -712,15 +822,15 @@ bool oled_invert(bool invert) {
}
if (invert && !oled_inverted) {
- static const uint8_t PROGMEM display_inverted[] = {I2C_CMD, INVERT_DISPLAY};
- if (I2C_TRANSMIT_P(display_inverted) != I2C_STATUS_SUCCESS) {
+ static const uint8_t PROGMEM display_inverted[] = {INVERT_DISPLAY};
+ if (!oled_cmd_p(display_inverted, ARRAY_SIZE(display_inverted))) {
print("oled_invert cmd failed\n");
return oled_inverted;
}
oled_inverted = true;
} else if (!invert && oled_inverted) {
- static const uint8_t PROGMEM display_normal[] = {I2C_CMD, NORMAL_DISPLAY};
- if (I2C_TRANSMIT_P(display_normal) != I2C_STATUS_SUCCESS) {
+ static const uint8_t PROGMEM display_normal[] = {NORMAL_DISPLAY};
+ if (!oled_cmd_p(display_normal, ARRAY_SIZE(display_normal))) {
print("oled_invert cmd failed\n");
return oled_inverted;
}