Browse Source

OLED Driver Feature

pjones-keymap
Ryan Caltabiano 4 months ago
parent
commit
0a645225b9

+ 7
- 0
common_features.mk View File

@@ -336,3 +336,10 @@ ifeq ($(strip $(SPLIT_KEYBOARD)), yes)
endif
COMMON_VPATH += $(QUANTUM_PATH)/split_common
endif

ifeq ($(strip $(OLED_DRIVER_ENABLE)), yes)
OPT_DEFS += -DOLED_DRIVER_ENABLE
COMMON_VPATH += $(DRIVER_PATH)/oled
QUANTUM_LIB_SRC += i2c_master.c
SRC += oled_driver.c
endif

+ 246
- 0
docs/feature_oled_driver.md View File

@@ -0,0 +1,246 @@
# OLED Driver

## OLED Supported Hardware

128x32 OLED modules using SSD1306 driver IC over I2C. Supported on AVR based keyboards. Possible but untested hardware includes ARM based keyboards and other sized OLED modules using SSD1306 over I2C, such as 128x64.

!> Warning: This OLED Driver currently uses the new i2c_master driver from split common code. If your split keyboard uses i2c to communication between sides this driver could cause an address conflict (serial is fine). Please contact your keyboard vendor and ask them to migrate to the latest split common code to fix this.

## Usage

To enable the OLED feature, there are three steps. First, when compiling your keyboard, you'll need to set `OLED_DRIVER_ENABLE=yes` in `rules.mk`, e.g.:

```
BOOTMAGIC_ENABLE = no
MOUSEKEY_ENABLE = no
STENO_ENABLE = no
EXTRAKEY_ENABLE = yes
OLED_DRIVER_ENABLE = yes
```

This enables the feature and the `OLED_DRIVER_ENABLE` define. Then in your `keymap.c` file, you will need to implement the user task call, e.g:

```C++
#ifdef OLED_DRIVER_ENABLE
void oled_task_user(void) {
// Host Keyboard Layer Status
oled_write_P(PSTR("Layer: "), false);
switch (biton32(layer_state)) {
case _QWERTY:
oled_write_P(PSTR("Default\n"), false);
break;
case _FN:
oled_write_P(PSTR("FN\n"), false);
break;
case _ADJ:
oled_write_P(PSTR("ADJ\n"), false);
break;
default:
// Or use the write_ln shortcut
oled_write_P(PSTR("Undefined\n"), false);
}

// Host Keyboard LED Status
uint8_t led_usb_state = host_keyboard_leds();
oled_write_P(led_usb_state & (1<<USB_LED_NUM_LOCK) ? PSTR("NUMLCK ") : PSTR(" "), false);
oled_write_P(led_usb_state & (1<<USB_LED_CAPS_LOCK) ? PSTR("CAPLCK ") : PSTR(" "), false);
oled_write_P(led_usb_state & (1<<USB_LED_SCROLL_LOCK) ? PSTR("SCRLCK ") : PSTR(" "), false);
}
#endif
```


## Other Examples

In split keyboards, it is very common to have two OLED displays that each render different content and oriented flipped differently. You can do this by switching which content to render by using the return from `is_keyboard_master()` or `is_keyboard_left()` found in `split_util.h`, e.g:

```C++
#ifdef OLED_DRIVER_ENABLE
uint8_t oled_init_user(uint8_t rotation) {
if (!is_keyboard_master())
return OLED_ROTATION_180; // flips the display 180 degrees if offhand
return rotation;
}

void oled_task_user(void) {
if (is_keyboard_master()) {
render_status(); // Renders the current keyboard state (layer, lock, caps, scroll, etc)
} else {
render_logo(); // Renders a statuc logo
oled_scroll_left(); // Turns on scrolling
}
}
#endif
```


## Basic Configuration

|Define |Default |Description |
|-----------------------|---------------|------------------------------------------------|
|`OLED_DISPLAY_ADDRESS` |`0x3C` |The i2c address of the OLED Display |
|`OLED_FONT_H` |`"glcdfont.c"` |The font code file to use for custom fonts |
|`OLED_FONT_START` |`0` |The starting characer index for custom fonts |
|`OLED_FONT_END` |`224` |The ending characer index for custom fonts |
|`OLED_FONT_WIDTH` |`6` |The font width |
|`OLED_FONT_HEIGHT` |`8` |The font height (untested) |
|`OLED_DISABLE_TIMEOUT` |*Not defined* |Disables the built in OLED timeout feature. Useful when implementing custom timeout rules.|



## 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.

|Define |Default |Description |
|-----------------------|---------------|-----------------------------------------------------------------|
|`OLED_DISPLAY_128X64` |*Not defined* |Changes the display defines for use with 128x64 displays. |
|`OLED_DISPLAY_CUSTOM` |*Not defined* |Changes the display defines for use with custom displays.<br />Requires user to implement the below defines. |
|`OLED_DISPLAY_WIDTH` |`128` |The width of the OLED display. |
|`OLED_DISPLAY_HEIGHT` |`32` |The height of the OLED display. |
|`OLED_MATRIX_SIZE` |`512` |The local buffer size to allocate.<br />`(OLED_DISPLAY_HEIGHT / 8 * OLED_DISPLAY_WIDTH)`|
|`OLED_BLOCK_TYPE` |`uint8_t` |The unsigned integer type to use for dirty rendering.|
|`OLED_BLOCK_COUNT` |`8` |The number of blocks the display is divided into for dirty rendering.<br />`(sizeof(OLED_BLOCK_TYPE) * 8)`|
|`OLED_BLOCK_SIZE` |`64` |The size of each block for dirty rendering<br />`(OLED_MATRIX_SIZE / OLED_BLOCK_COUNT)`|
|`OLED_SOURCE_MAP` |`{ 0, ... N }` |Precalculated source array to use for mapping source buffer to target OLED memory in 90 degree rendering. |
|`OLED_TARGET_MAP` |`{ 48, ... N }`|Precalculated target array to use for mapping source buffer to target OLED memory in 90 degree rendering. |


### 90 Degree Rotation - Technical Mumbo Jumbo

OLED displays driven by SSD1306 drivers only natively support in hard ware 0 degree and 180 degree rendering. This feature is done in software and not free. Using this feature will increase the time to calculate what data to send over i2c to the OLED. If you are strapped for cycles, this can cause keycodes to not register. In testing however, the rendering time on an `atmega32u4` board only went from 2ms to 5ms and keycodes not registering was only noticed once we hit 15ms.
90 Degree Rotated Rendering is achieved by using bitwise operations to rotate each 8 block of memory and uses two precalculated arrays to remap buffer memory to OLED memory. The memory map defines are precalculated for remap performance and are calculated based on the OLED Height, Width, and Block Size. For example, in the default 128x32 implementation we have a 64 byte block size. This gives us eight 8 byte blocks that need to be rotated and rendered. The OLED renders horizontally two 8 byte blocks before moving down a page, e.g:

| | | | | | |
|---|---|---|---|---|---|
| 0 | 1 | | | | |
| 2 | 3 | | | | |
| 4 | 5 | | | | |
| 6 | 7 | | | | |

However the local buffer is stored as if it was Height x Width display instead of Width x Height, e.g:

| | | | | | |
|---|---|---|---|---|---|
| 3 | 7 | | | | |
| 2 | 6 | | | | |
| 1 | 5 | | | | |
| 0 | 4 | | | | |

So those precalculated arrays just index the memory offsets in the order in which each one iterates its data.

## OLED API

```C++
// Initialize the OLED display, rotating the rendered output 180 degrees if true.
// Returns true if the OLED was initialized successfully
bool oled_init(bool flip180);

// Called at the start of oled_init, weak function overridable by the user
// flip180 - the value passed into oled_init
// Return true if you want the oled to be flip180
bool oled_init_user(bool flip180);

// Clears the display buffer, resets cursor position to 0, and sets the buffer to dirty for rendering
void oled_clear(void);

// Renders the dirty chunks of the buffer to OLED display
void oled_render(void);

// Moves cursor to character position indicated by column and line, wraps if out of bounds
// Max column denoted by 'oled_max_chars()' and max lines by 'oled_max_lines()' functions
void oled_set_cursor(uint8_t col, uint8_t line);

// Advances the cursor to the next page, writing ' ' if true
// Wraps to the begining when out of bounds
void oled_advance_page(bool clearPageRemainder);

// Moves the cursor forward 1 character length
// Advance page if there is not enough room for the next character
// Wraps to the begining when out of bounds
void oled_advance_char(void);

// Writes a single character to the buffer at current cursor position
// Advances the cursor while writing, inverts the pixels if true
// Main handler that writes character data to the display buffer
void oled_write_char(const char data, bool invert);

// Writes a string to the buffer at current cursor position
// Advances the cursor while writing, inverts the pixels if true
void oled_write(const char *data, bool invert);

// Writes a string to the buffer at current cursor position
// Advances the cursor while writing, inverts the pixels if true
// Advances the cursor to the next page, wiring ' ' to the remainder of the current page
void oled_write_ln(const char *data, bool invert);

// Writes a PROGMEM string to the buffer at current cursor position
// Advances the cursor while writing, inverts the pixels if true
// Remapped to call 'void oled_write(const char *data, bool invert);' on ARM
void oled_write_P(const char *data, bool invert);

// Writes a PROGMEM string to the buffer at current cursor position
// Advances the cursor while writing, inverts the pixels if true
// Advances the cursor to the next page, wiring ' ' to the remainder of the current page
// Remapped to call 'void oled_write_ln(const char *data, bool invert);' on ARM
void oled_write_ln_P(const char *data, bool invert);

// Can be used to manually turn on the screen if it is off
// Returns true if the screen was on or turns on
bool oled_on(void);

// Can be used to manually turn off the screen if it is on
// Returns true if the screen was off or turns off
bool oled_off(void);

// Basically it's oled_render, but with timeout management and oled_task_user calling!
void oled_task(void);

// Called at the start of oled_task, weak function overridable by the user
void oled_task_user(void);

// Scrolls the entire display right
// Returns true if the screen was scrolling or starts scrolling
// NOTE: display contents cannot be changed while scrolling
bool oled_scroll_right(void);

// Scrolls the entire display left
// Returns true if the screen was scrolling or starts scrolling
// NOTE: display contents cannot be changed while scrolling
bool oled_scroll_left(void);

// Turns off display scrolling
// Returns true if the screen was not scrolling or stops scrolling
bool oled_scroll_off(void);

// Returns the maximum number of characters that will fit on a line
uint8_t oled_max_chars(void);

// Returns the maximum number of lines that will fit on the oled
uint8_t oled_max_lines(void);
```

## SSD1306.h driver conversion guide

|Old API |Recommended New API |
|---------------------------|-----------------------------------|
|`struct CharacterMatrix` |*removed - delete all references* |
|`iota_gfx_init` |`oled_init` |
|`iota_gfx_on` |`oled_on` |
|`iota_gfx_off` |`oled_off` |
|`iota_gfx_flush` |`oled_render` |
|`iota_gfx_write_char` |`oled_write_char` |
|`iota_gfx_write` |`oled_write` |
|`iota_gfx_write_P` |`oled_write_P` |
|`iota_gfx_clear_screen` |`oled_clear` |
|`matrix_clear` |*removed - delete all references* |
|`matrix_write_char_inner` |`oled_write_char` |
|`matrix_write_char` |`oled_write_char` |
|`matrix_write` |`oled_write` |
|`matrix_write_ln` |`oled_write_ln` |
|`matrix_write_P` |`oled_write_P` |
|`matrix_write_ln_P` |`oled_write_ln_P` |
|`matrix_render` |`oled_render` |
|`iota_gfx_task` |`oled_task` |
|`iota_gfx_task_user` |`oled_task_user` |

+ 3
- 3
docs/hardware_drivers.md View File

@@ -14,9 +14,9 @@ QMK is used on a lot of different hardware. While support for the most common MC

Support for addressing pins on the ProMicro by their Arduino name rather than their AVR name. This needs to be better documented, if you are trying to do this and reading the code doesn't help please [open an issue](https://github.com/qmk/qmk_firmware/issues/new) and we can help you through the process.

## SSD1306 (AVR Only)
## SSD1306 OLED Driver

Support for SSD1306 based OLED displays. This needs to be better documented, if you are trying to do this and reading the code doesn't help please [open an issue](https://github.com/qmk/qmk_firmware/issues/new) and we can help you through the process.
Support for SSD1306 based OLED displays. For more information see the [OLED Driver Feature](feature_oled_driver.md) page.

## uGFX

@@ -32,4 +32,4 @@ Support for up to 2 drivers. Each driver impliments 2 charlieplex matrices to in

## IS31FL3733

Support for up to a single driver with room for expansion. Each driver can control 192 individual LEDs or 64 RGB LEDs. For more information on how to setup the driver see the [RGB Matrix](feature_rgb_matrix.md) page.
Support for up to a single driver with room for expansion. Each driver can control 192 individual LEDs or 64 RGB LEDs. For more information on how to setup the driver see the [RGB Matrix](feature_rgb_matrix.md) page.

+ 27
- 13
drivers/arm/i2c_master.c View File

@@ -42,6 +42,18 @@ static const I2CConfig i2cconfig = {
0
};

static i2c_status_t chibios_to_qmk(const msg_t* status) {
switch (*status) {
case I2C_NO_ERROR:
return I2C_STATUS_SUCCESS;
case I2C_TIMEOUT:
return I2C_STATUS_TIMEOUT;
// I2C_BUS_ERROR, I2C_ARBITRATION_LOST, I2C_ACK_FAILURE, I2C_OVERRUN, I2C_PEC_ERROR, I2C_SMB_ALERT
default:
return I2C_STATUS_ERROR;
}
}

__attribute__ ((weak))
void i2c_init(void)
{
@@ -57,29 +69,30 @@ void i2c_init(void)
//i2cInit(); //This is invoked by halInit() so no need to redo it.
}

// This is usually not needed
uint8_t i2c_start(uint8_t address)
i2c_status_t i2c_start(uint8_t address)
{
i2c_address = address;
i2cStart(&I2C_DRIVER, &i2cconfig);
return 0;
return I2C_STATUS_SUCCESS;
}

uint8_t i2c_transmit(uint8_t address, uint8_t* data, uint16_t length, uint16_t timeout)
i2c_status_t i2c_transmit(uint8_t address, const uint8_t* data, uint16_t length, uint16_t timeout)
{
i2c_address = address;
i2cStart(&I2C_DRIVER, &i2cconfig);
return i2cMasterTransmitTimeout(&I2C_DRIVER, (i2c_address >> 1), data, length, 0, 0, MS2ST(timeout));
msg_t status = i2cMasterTransmitTimeout(&I2C_DRIVER, (i2c_address >> 1), data, length, 0, 0, MS2ST(timeout));
return chibios_to_qmk(&status);
}

uint8_t i2c_receive(uint8_t address, uint8_t* data, uint16_t length, uint16_t timeout)
i2c_status_t i2c_receive(uint8_t address, uint8_t* data, uint16_t length, uint16_t timeout)
{
i2c_address = address;
i2cStart(&I2C_DRIVER, &i2cconfig);
return i2cMasterReceiveTimeout(&I2C_DRIVER, (i2c_address >> 1), data, length, MS2ST(timeout));
msg_t status = i2cMasterReceiveTimeout(&I2C_DRIVER, (i2c_address >> 1), data, length, MS2ST(timeout));
return chibios_to_qmk(&status);
}

uint8_t i2c_writeReg(uint8_t devaddr, uint8_t regaddr, uint8_t* data, uint16_t length, uint16_t timeout)
i2c_status_t i2c_writeReg(uint8_t devaddr, uint8_t regaddr, const uint8_t* data, uint16_t length, uint16_t timeout)
{
i2c_address = devaddr;
i2cStart(&I2C_DRIVER, &i2cconfig);
@@ -91,18 +104,19 @@ uint8_t i2c_writeReg(uint8_t devaddr, uint8_t regaddr, uint8_t* data, uint16_t l
}
complete_packet[0] = regaddr;

return i2cMasterTransmitTimeout(&I2C_DRIVER, (i2c_address >> 1), complete_packet, length + 1, 0, 0, MS2ST(timeout));
msg_t status = i2cMasterTransmitTimeout(&I2C_DRIVER, (i2c_address >> 1), complete_packet, length + 1, 0, 0, MS2ST(timeout));
return chibios_to_qmk(&status);
}

uint8_t i2c_readReg(uint8_t devaddr, uint8_t* regaddr, uint8_t* data, uint16_t length, uint16_t timeout)
i2c_status_t i2c_readReg(uint8_t devaddr, uint8_t* regaddr, uint8_t* data, uint16_t length, uint16_t timeout)
{
i2c_address = devaddr;
i2cStart(&I2C_DRIVER, &i2cconfig);
return i2cMasterTransmitTimeout(&I2C_DRIVER, (i2c_address >> 1), regaddr, 1, data, length, MS2ST(timeout));
msg_t status = i2cMasterTransmitTimeout(&I2C_DRIVER, (i2c_address >> 1), regaddr, 1, data, length, MS2ST(timeout));
return chibios_to_qmk(&status);
}

uint8_t i2c_stop(void)
void i2c_stop(void)
{
i2cStop(&I2C_DRIVER);
return 0;
}

+ 13
- 7
drivers/arm/i2c_master.h View File

@@ -40,11 +40,17 @@
#define I2C_DRIVER I2CD1
#endif

typedef int16_t i2c_status_t;

#define I2C_STATUS_SUCCESS (0)
#define I2C_STATUS_ERROR (-1)
#define I2C_STATUS_TIMEOUT (-2)

void i2c_init(void);
uint8_t i2c_start(uint8_t address);
uint8_t i2c_transmit(uint8_t address, uint8_t* data, uint16_t length, uint16_t timeout);
uint8_t i2c_receive(uint8_t address, uint8_t* data, uint16_t length, uint16_t timeout);
uint8_t i2c_transmit_receive(uint8_t address, uint8_t * tx_body, uint16_t tx_length, uint8_t * rx_body, uint16_t rx_length);
uint8_t i2c_writeReg(uint8_t devaddr, uint8_t regaddr, uint8_t* data, uint16_t length, uint16_t timeout);
uint8_t i2c_readReg(uint8_t devaddr, uint8_t* regaddr, uint8_t* data, uint16_t length, uint16_t timeout);
uint8_t i2c_stop(void);
i2c_status_t i2c_start(uint8_t address);
i2c_status_t i2c_transmit(uint8_t address, const uint8_t* data, uint16_t length, uint16_t timeout);
i2c_status_t i2c_receive(uint8_t address, uint8_t* data, uint16_t length, uint16_t timeout);
i2c_status_t i2c_transmit_receive(uint8_t address, uint8_t * tx_body, uint16_t tx_length, uint8_t * rx_body, uint16_t rx_length);
i2c_status_t i2c_writeReg(uint8_t devaddr, uint8_t regaddr, const uint8_t* data, uint16_t length, uint16_t timeout);
i2c_status_t i2c_readReg(uint8_t devaddr, uint8_t* regaddr, uint8_t* data, uint16_t length, uint16_t timeout);
void i2c_stop(void);

+ 2
- 2
drivers/avr/i2c_master.c View File

@@ -121,7 +121,7 @@ int16_t i2c_read_nack(uint16_t timeout) {
return TWDR;
}

i2c_status_t i2c_transmit(uint8_t address, uint8_t* data, uint16_t length, uint16_t timeout) {
i2c_status_t i2c_transmit(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++) {
@@ -155,7 +155,7 @@ i2c_status_t i2c_receive(uint8_t address, uint8_t* data, uint16_t length, uint16
return (status < 0) ? status : I2C_STATUS_SUCCESS;
}

i2c_status_t i2c_writeReg(uint8_t devaddr, uint8_t regaddr, uint8_t* data, uint16_t length, uint16_t timeout) {
i2c_status_t i2c_writeReg(uint8_t devaddr, uint8_t regaddr, const uint8_t* data, uint16_t length, uint16_t timeout) {
i2c_status_t status = i2c_start(devaddr | 0x00, timeout);
if (status >= 0) {
status = i2c_write(regaddr, timeout);

+ 3
- 3
drivers/avr/i2c_master.h View File

@@ -22,10 +22,10 @@ i2c_status_t i2c_start(uint8_t address, uint16_t timeout);
i2c_status_t i2c_write(uint8_t data, uint16_t timeout);
int16_t i2c_read_ack(uint16_t timeout);
int16_t i2c_read_nack(uint16_t timeout);
i2c_status_t i2c_transmit(uint8_t address, uint8_t* data, uint16_t length, uint16_t timeout);
i2c_status_t i2c_transmit(uint8_t address, const uint8_t* data, uint16_t length, uint16_t timeout);
i2c_status_t i2c_receive(uint8_t address, uint8_t* data, uint16_t length, uint16_t timeout);
i2c_status_t i2c_writeReg(uint8_t devaddr, uint8_t regaddr, uint8_t* data, uint16_t length, uint16_t timeout);
i2c_status_t i2c_writeReg(uint8_t devaddr, uint8_t regaddr, const uint8_t* data, uint16_t length, uint16_t timeout);
i2c_status_t i2c_readReg(uint8_t devaddr, uint8_t regaddr, uint8_t* data, uint16_t length, uint16_t timeout);
void i2c_stop(void);

#endif // I2C_MASTER_H
#endif // I2C_MASTER_H

+ 240
- 0
drivers/oled/glcdfont.c View File

@@ -0,0 +1,240 @@
#pragma once

#ifdef __AVR__
#include <avr/io.h>
#include <avr/pgmspace.h>
#elif defined(ESP8266)
#include <pgmspace.h>
#else
#define PROGMEM
#endif

// Helidox 8x6 font with QMK Firmware Logo
// Online editor: http://teripom.x0.com/

static const unsigned char font[] PROGMEM = {
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x3E, 0x5B, 0x4F, 0x5B, 0x3E, 0x00,
0x3E, 0x6B, 0x4F, 0x6B, 0x3E, 0x00,
0x1C, 0x3E, 0x7C, 0x3E, 0x1C, 0x00,
0x18, 0x3C, 0x7E, 0x3C, 0x18, 0x00,
0x1C, 0x57, 0x7D, 0x57, 0x1C, 0x00,
0x1C, 0x5E, 0x7F, 0x5E, 0x1C, 0x00,
0x00, 0x18, 0x3C, 0x18, 0x00, 0x00,
0xFF, 0xE7, 0xC3, 0xE7, 0xFF, 0x00,
0x00, 0x18, 0x24, 0x18, 0x00, 0x00,
0xFF, 0xE7, 0xDB, 0xE7, 0xFF, 0x00,
0x30, 0x48, 0x3A, 0x06, 0x0E, 0x00,
0x26, 0x29, 0x79, 0x29, 0x26, 0x00,
0x40, 0x7F, 0x05, 0x05, 0x07, 0x00,
0x40, 0x7F, 0x05, 0x25, 0x3F, 0x00,
0x5A, 0x3C, 0xE7, 0x3C, 0x5A, 0x00,
0x7F, 0x3E, 0x1C, 0x1C, 0x08, 0x00,
0x08, 0x1C, 0x1C, 0x3E, 0x7F, 0x00,
0x14, 0x22, 0x7F, 0x22, 0x14, 0x00,
0x5F, 0x5F, 0x00, 0x5F, 0x5F, 0x00,
0x06, 0x09, 0x7F, 0x01, 0x7F, 0x00,
0x00, 0x66, 0x89, 0x95, 0x6A, 0x00,
0x60, 0x60, 0x60, 0x60, 0x60, 0x00,
0x94, 0xA2, 0xFF, 0xA2, 0x94, 0x00,
0x08, 0x04, 0x7E, 0x04, 0x08, 0x00,
0x10, 0x20, 0x7E, 0x20, 0x10, 0x00,
0x08, 0x08, 0x2A, 0x1C, 0x08, 0x00,
0x08, 0x1C, 0x2A, 0x08, 0x08, 0x00,
0x1E, 0x10, 0x10, 0x10, 0x10, 0x00,
0x0C, 0x1E, 0x0C, 0x1E, 0x0C, 0x00,
0x30, 0x38, 0x3E, 0x38, 0x30, 0x00,
0x06, 0x0E, 0x3E, 0x0E, 0x06, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x5F, 0x00, 0x00, 0x00,
0x00, 0x07, 0x00, 0x07, 0x00, 0x00,
0x14, 0x7F, 0x14, 0x7F, 0x14, 0x00,
0x24, 0x2A, 0x7F, 0x2A, 0x12, 0x00,
0x23, 0x13, 0x08, 0x64, 0x62, 0x00,
0x36, 0x49, 0x56, 0x20, 0x50, 0x00,
0x00, 0x08, 0x07, 0x03, 0x00, 0x00,
0x00, 0x1C, 0x22, 0x41, 0x00, 0x00,
0x00, 0x41, 0x22, 0x1C, 0x00, 0x00,
0x2A, 0x1C, 0x7F, 0x1C, 0x2A, 0x00,
0x08, 0x08, 0x3E, 0x08, 0x08, 0x00,
0x00, 0x80, 0x70, 0x30, 0x00, 0x00,
0x08, 0x08, 0x08, 0x08, 0x08, 0x00,
0x00, 0x00, 0x60, 0x60, 0x00, 0x00,
0x20, 0x10, 0x08, 0x04, 0x02, 0x00,
0x3E, 0x51, 0x49, 0x45, 0x3E, 0x00,
0x00, 0x42, 0x7F, 0x40, 0x00, 0x00,
0x72, 0x49, 0x49, 0x49, 0x46, 0x00,
0x21, 0x41, 0x49, 0x4D, 0x33, 0x00,
0x18, 0x14, 0x12, 0x7F, 0x10, 0x00,
0x27, 0x45, 0x45, 0x45, 0x39, 0x00,
0x3C, 0x4A, 0x49, 0x49, 0x31, 0x00,
0x41, 0x21, 0x11, 0x09, 0x07, 0x00,
0x36, 0x49, 0x49, 0x49, 0x36, 0x00,
0x46, 0x49, 0x49, 0x29, 0x1E, 0x00,
0x00, 0x00, 0x14, 0x00, 0x00, 0x00,
0x00, 0x40, 0x34, 0x00, 0x00, 0x00,
0x00, 0x08, 0x14, 0x22, 0x41, 0x00,
0x14, 0x14, 0x14, 0x14, 0x14, 0x00,
0x00, 0x41, 0x22, 0x14, 0x08, 0x00,
0x02, 0x01, 0x59, 0x09, 0x06, 0x00,
0x3E, 0x41, 0x5D, 0x59, 0x4E, 0x00,
0x7C, 0x12, 0x11, 0x12, 0x7C, 0x00,
0x7F, 0x49, 0x49, 0x49, 0x36, 0x00,
0x3E, 0x41, 0x41, 0x41, 0x22, 0x00,
0x7F, 0x41, 0x41, 0x41, 0x3E, 0x00,
0x7F, 0x49, 0x49, 0x49, 0x41, 0x00,
0x7F, 0x09, 0x09, 0x09, 0x01, 0x00,
0x3E, 0x41, 0x41, 0x51, 0x73, 0x00,
0x7F, 0x08, 0x08, 0x08, 0x7F, 0x00,
0x00, 0x41, 0x7F, 0x41, 0x00, 0x00,
0x20, 0x40, 0x41, 0x3F, 0x01, 0x00,
0x7F, 0x08, 0x14, 0x22, 0x41, 0x00,
0x7F, 0x40, 0x40, 0x40, 0x40, 0x00,
0x7F, 0x02, 0x1C, 0x02, 0x7F, 0x00,
0x7F, 0x04, 0x08, 0x10, 0x7F, 0x00,
0x3E, 0x41, 0x41, 0x41, 0x3E, 0x00,
0x7F, 0x09, 0x09, 0x09, 0x06, 0x00,
0x3E, 0x41, 0x51, 0x21, 0x5E, 0x00,
0x7F, 0x09, 0x19, 0x29, 0x46, 0x00,
0x26, 0x49, 0x49, 0x49, 0x32, 0x00,
0x03, 0x01, 0x7F, 0x01, 0x03, 0x00,
0x3F, 0x40, 0x40, 0x40, 0x3F, 0x00,
0x1F, 0x20, 0x40, 0x20, 0x1F, 0x00,
0x3F, 0x40, 0x38, 0x40, 0x3F, 0x00,
0x63, 0x14, 0x08, 0x14, 0x63, 0x00,
0x03, 0x04, 0x78, 0x04, 0x03, 0x00,
0x61, 0x59, 0x49, 0x4D, 0x43, 0x00,
0x00, 0x7F, 0x41, 0x41, 0x41, 0x00,
0x02, 0x04, 0x08, 0x10, 0x20, 0x00,
0x00, 0x41, 0x41, 0x41, 0x7F, 0x00,
0x04, 0x02, 0x01, 0x02, 0x04, 0x00,
0x40, 0x40, 0x40, 0x40, 0x40, 0x00,
0x00, 0x03, 0x07, 0x08, 0x00, 0x00,
0x20, 0x54, 0x54, 0x78, 0x40, 0x00,
0x7F, 0x28, 0x44, 0x44, 0x38, 0x00,
0x38, 0x44, 0x44, 0x44, 0x28, 0x00,
0x38, 0x44, 0x44, 0x28, 0x7F, 0x00,
0x38, 0x54, 0x54, 0x54, 0x18, 0x00,
0x00, 0x08, 0x7E, 0x09, 0x02, 0x00,
0x18, 0xA4, 0xA4, 0x9C, 0x78, 0x00,
0x7F, 0x08, 0x04, 0x04, 0x78, 0x00,
0x00, 0x44, 0x7D, 0x40, 0x00, 0x00,
0x20, 0x40, 0x40, 0x3D, 0x00, 0x00,
0x7F, 0x10, 0x28, 0x44, 0x00, 0x00,
0x00, 0x41, 0x7F, 0x40, 0x00, 0x00,
0x7C, 0x04, 0x78, 0x04, 0x78, 0x00,
0x7C, 0x08, 0x04, 0x04, 0x78, 0x00,
0x38, 0x44, 0x44, 0x44, 0x38, 0x00,
0xFC, 0x18, 0x24, 0x24, 0x18, 0x00,
0x18, 0x24, 0x24, 0x18, 0xFC, 0x00,
0x7C, 0x08, 0x04, 0x04, 0x08, 0x00,
0x48, 0x54, 0x54, 0x54, 0x24, 0x00,
0x04, 0x04, 0x3F, 0x44, 0x24, 0x00,
0x3C, 0x40, 0x40, 0x20, 0x7C, 0x00,
0x1C, 0x20, 0x40, 0x20, 0x1C, 0x00,
0x3C, 0x40, 0x30, 0x40, 0x3C, 0x00,
0x44, 0x28, 0x10, 0x28, 0x44, 0x00,
0x4C, 0x90, 0x90, 0x90, 0x7C, 0x00,
0x44, 0x64, 0x54, 0x4C, 0x44, 0x00,
0x00, 0x08, 0x36, 0x41, 0x00, 0x00,
0x00, 0x00, 0x77, 0x00, 0x00, 0x00,
0x00, 0x41, 0x36, 0x08, 0x00, 0x00,
0x02, 0x01, 0x02, 0x04, 0x02, 0x00,
0x3C, 0x26, 0x23, 0x26, 0x3C, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x40, 0x40, 0x40, 0xF0, 0xF8, 0xF8,
0xFF, 0x38, 0xFF, 0xF8, 0xF8, 0x3F,
0xF8, 0xF8, 0xFF, 0x38, 0xFF, 0xF8,
0xF8, 0xF0, 0x40, 0x40, 0x40, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x80,
0xC0, 0xC0, 0xC0, 0x80, 0x00, 0x00,
0xC0, 0xC0, 0x80, 0x00, 0x00, 0x00,
0x80, 0xC0, 0xC0, 0x00, 0xC0, 0xC0,
0x00, 0x00, 0x80, 0xC0, 0xC0, 0x00,
0x00, 0x00, 0x00, 0x00, 0xC0, 0xC0,
0xC0, 0xC0, 0xC0, 0x00, 0xC0, 0xC0,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0xE0, 0xF0, 0xF0, 0xF0, 0xE0, 0xEC,
0xEE, 0xF7, 0xF3, 0x70, 0x20, 0x00,
0x7C, 0x7C, 0x7C, 0x7E, 0x00, 0x7E,
0x7E, 0x7E, 0x7F, 0x7F, 0x7F, 0x00,
0x00, 0x80, 0xC0, 0xE0, 0x7E, 0x5B,
0x4F, 0x5B, 0xFE, 0xC0, 0x00, 0x00,
0xC0, 0x00, 0xDC, 0xD7, 0xDE, 0xDE,
0xDE, 0xD7, 0xDC, 0x00, 0xC0, 0x00,
0x00, 0x00, 0x00, 0xE0, 0xEC, 0xDF,
0xFC, 0xE0, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x49, 0x49, 0x49, 0xFF, 0xFF, 0xFF,
0xFF, 0xE0, 0xDF, 0xBF, 0xBF, 0x00,
0xBF, 0xBF, 0xDF, 0xE0, 0xFF, 0xFF,
0xFF, 0xFF, 0x49, 0x49, 0x49, 0x00,
0x00, 0x00, 0x00, 0x00, 0x1F, 0x3F,
0x60, 0x60, 0xE0, 0xBF, 0x1F, 0x00,
0x7F, 0x7F, 0x07, 0x1E, 0x38, 0x1E,
0x07, 0x7F, 0x7F, 0x00, 0x7F, 0x7F,
0x0E, 0x1F, 0x3B, 0x71, 0x60, 0x00,
0x00, 0x00, 0x00, 0x00, 0x7F, 0x7F,
0x0C, 0x0C, 0x0C, 0x00, 0x7E, 0x7E,
0x00, 0x7F, 0x7E, 0x03, 0x03, 0x00,
0x7F, 0x7E, 0x03, 0x03, 0x7E, 0x7E,
0x03, 0x03, 0x7F, 0x7E, 0x00, 0x0F,
0x3E, 0x70, 0x3C, 0x06, 0x3C, 0x70,
0x3E, 0x0F, 0x00, 0x32, 0x7B, 0x49,
0x49, 0x3F, 0x7E, 0x00, 0x7F, 0x7E,
0x03, 0x03, 0x00, 0x1E, 0x3F, 0x69,
0x69, 0x6F, 0x26, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x0F, 0x1F, 0x3F, 0x7F, 0x7F, 0x7F,
0x7F, 0x7F, 0x3F, 0x1E, 0x0C, 0x00,
0x1F, 0x1F, 0x1F, 0x3F, 0x00, 0x3F,
0x3F, 0x3F, 0x7F, 0x7F, 0x7F, 0x00,
0x30, 0x7B, 0x7F, 0x78, 0x30, 0x20,
0x20, 0x30, 0x78, 0x7F, 0x3B, 0x00,
0x03, 0x00, 0x0F, 0x7F, 0x0F, 0x0F,
0x0F, 0x7F, 0x0F, 0x00, 0x03, 0x00,
0x40, 0x7C, 0x3F, 0x3F, 0x23, 0x01,
0x23, 0x3F, 0x37, 0x6C, 0x40, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x01, 0x01, 0x01, 0x07, 0x0F, 0x0F,
0x7F, 0x0F, 0x7F, 0x0F, 0x0F, 0x7E,
0x0F, 0x0F, 0x7F, 0x0F, 0x7F, 0x0F,
0x0F, 0x07, 0x01, 0x01, 0x01, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x01, 0x01, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
};

+ 528
- 0
drivers/oled/oled_driver.c View File

@@ -0,0 +1,528 @@
/*
Copyright 2019 Ryan Caltabiano <https://github.com/XScorpion2>

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
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 <http://www.gnu.org/licenses/>.
*/
#include "i2c_master.h"
#include "oled_driver.h"
#include OLED_FONT_H
#include "timer.h"
#include "print.h"

#include <string.h>

#if defined(__AVR__)
#include <avr/io.h>
#include <avr/pgmspace.h>
#elif defined(ESP8266)
#include <pgmspace.h>
#else // defined(ESP8266)
#define PROGMEM
#define memcpy_P(des, src, len) memcpy(des, src, len)
#endif // defined(__AVR__)

// Used commands from spec sheet: https://cdn-shop.adafruit.com/datasheets/SSD1306.pdf
// Fundamental Commands
#define CONTRAST 0x81
#define DISPLAY_ALL_ON 0xA5
#define DISPLAY_ALL_ON_RESUME 0xA4
#define NORMAL_DISPLAY 0xA6
#define DISPLAY_ON 0xAF
#define DISPLAY_OFF 0xAE

// Scrolling Commands
#define ACTIVATE_SCROLL 0x2F
#define DEACTIVATE_SCROLL 0x2E
#define SCROLL_RIGHT 0x26
#define SCROLL_LEFT 0x27
#define SCROLL_RIGHT_UP 0x29
#define SCROLL_LEFT_UP 0x2A

// Addressing Setting Commands
#define MEMORY_MODE 0x20
#define COLUMN_ADDR 0x21
#define PAGE_ADDR 0x22

// Hardware Configuration Commands
#define DISPLAY_START_LINE 0x40
#define SEGMENT_REMAP 0xA0
#define SEGMENT_REMAP_INV 0xA1
#define MULTIPLEX_RATIO 0xA8
#define COM_SCAN_INC 0xC0
#define COM_SCAN_DEC 0xC8
#define DISPLAY_OFFSET 0xD3
#define COM_PINS 0xDA

// Timing & Driving Commands
#define DISPLAY_CLOCK 0xD5
#define PRE_CHARGE_PERIOD 0xD9
#define VCOM_DETECT 0xDB

// Charge Pump Commands
#define CHARGE_PUMP 0x8D

// Misc defines
#define OLED_TIMEOUT 60000
#define OLED_BLOCK_COUNT (sizeof(OLED_BLOCK_TYPE) * 8)
#define OLED_BLOCK_SIZE (OLED_MATRIX_SIZE / OLED_BLOCK_COUNT)

// i2c defines
#define I2C_CMD 0x00
#define I2C_DATA 0x40
#if defined(__AVR__)
// already defined on ARM
#define I2C_TIMEOUT 100
#define I2C_TRANSMIT_P(data) i2c_transmit_P((OLED_DISPLAY_ADDRESS << 1), &data[0], sizeof(data), I2C_TIMEOUT)
#else // defined(__AVR__)
#define I2C_TRANSMIT_P(data) i2c_transmit((OLED_DISPLAY_ADDRESS << 1), &data[0], sizeof(data), I2C_TIMEOUT)
#endif // defined(__AVR__)
#define I2C_TRANSMIT(data) i2c_transmit((OLED_DISPLAY_ADDRESS << 1), &data[0], sizeof(data), I2C_TIMEOUT)
#define I2C_WRITE_REG(mode, data, size) i2c_writeReg((OLED_DISPLAY_ADDRESS << 1), mode, data, size, I2C_TIMEOUT)

#define HAS_FLAGS(bits, flags) ((bits & flags) == flags)

// Display buffer's is the same as the OLED memory layout
// this is so we don't end up with rounding errors with
// parts of the display unusable or don't get cleared correctly
// and also allows for drawing & inverting
uint8_t oled_buffer[OLED_MATRIX_SIZE];
uint8_t* oled_cursor;
OLED_BLOCK_TYPE oled_dirty = 0;
bool oled_initialized = false;
bool oled_active = false;
bool oled_scrolling = false;
uint8_t oled_rotation = 0;
uint8_t oled_rotation_width = 0;
#if !defined(OLED_DISABLE_TIMEOUT)
uint16_t oled_last_activity;
#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;
while (cursor < end) {
*cursor = ~(*cursor);
cursor++;
}
}

bool oled_init(uint8_t rotation) {
oled_rotation = oled_init_user(rotation);
if (!HAS_FLAGS(oled_rotation, OLED_ROTATION_90)) {
oled_rotation_width = OLED_DISPLAY_WIDTH;
} else {
oled_rotation_width = OLED_DISPLAY_HEIGHT;
}
i2c_init();

static const uint8_t PROGMEM display_setup1[] = {
I2C_CMD,
DISPLAY_OFF,
DISPLAY_CLOCK, 0x80,
MULTIPLEX_RATIO, OLED_DISPLAY_HEIGHT - 1,
DISPLAY_OFFSET, 0x00,
DISPLAY_START_LINE | 0x00,
CHARGE_PUMP, 0x14,
MEMORY_MODE, 0x00, }; // Horizontal addressing mode
if (I2C_TRANSMIT_P(display_setup1) != I2C_STATUS_SUCCESS) {
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) {
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) {
print("display_flipped failed\n");
return false;
}
}

static const uint8_t PROGMEM display_setup2[] = {
I2C_CMD,
COM_PINS, 0x02,
CONTRAST, 0x8F,
PRE_CHARGE_PERIOD, 0xF1,
VCOM_DETECT, 0x40,
DISPLAY_ALL_ON_RESUME,
NORMAL_DISPLAY,
DEACTIVATE_SCROLL,
DISPLAY_ON };
if (I2C_TRANSMIT_P(display_setup2) != I2C_STATUS_SUCCESS) {
print("display_setup2 failed\n");
return false;
}

oled_clear();
oled_initialized = true;
oled_active = true;
oled_scrolling = false;
return true;
}

__attribute__((weak))
uint8_t oled_init_user(uint8_t rotation) {
return rotation;
}

void oled_clear(void) {
memset(oled_buffer, 0, sizeof(oled_buffer));
oled_cursor = &oled_buffer[0];
oled_dirty = -1; // -1 will be max value as long as display_dirty is unsigned type
}

static void calc_bounds(uint8_t update_start, uint8_t* cmd_array)
{
cmd_array[1] = OLED_BLOCK_SIZE * update_start % OLED_DISPLAY_WIDTH;
cmd_array[4] = OLED_BLOCK_SIZE * update_start / OLED_DISPLAY_WIDTH;
cmd_array[2] = (OLED_BLOCK_SIZE + OLED_DISPLAY_WIDTH - 1) % OLED_DISPLAY_WIDTH + cmd_array[1];
cmd_array[5] = (OLED_BLOCK_SIZE + OLED_DISPLAY_WIDTH - 1) / OLED_DISPLAY_WIDTH - 1;
}

static void calc_bounds_90(uint8_t update_start, uint8_t* cmd_array)
{
cmd_array[1] = OLED_BLOCK_SIZE * update_start / OLED_DISPLAY_HEIGHT * 8;
cmd_array[4] = OLED_BLOCK_SIZE * update_start % OLED_DISPLAY_HEIGHT;
cmd_array[2] = (OLED_BLOCK_SIZE + OLED_DISPLAY_HEIGHT - 1) / OLED_DISPLAY_HEIGHT * 8 - 1 + cmd_array[1];;
cmd_array[5] = (OLED_BLOCK_SIZE + OLED_DISPLAY_HEIGHT - 1) % OLED_DISPLAY_HEIGHT / 8;
}

uint8_t crot(uint8_t a, int8_t n)
{
const uint8_t mask = 0x7;
n &= mask;
return a << n | a >> (-n & mask);
}

static void rotate_90(const uint8_t* src, uint8_t* dest)
{
for (uint8_t i = 0, shift = 7; i < 8; ++i, --shift) {
uint8_t selector = (1 << i);
for (uint8_t j = 0; j < 8; ++j) {
dest[i] |= crot(src[j] & selector, shift - (int8_t)j);
}
}
}

void oled_render(void) {
// Do we have work to do?
if (!oled_dirty || oled_scrolling) {
return;
}

// Find first dirty block
uint8_t update_start = 0;
while (!(oled_dirty & (1 << update_start))) { ++update_start; }

// 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 };
if (!HAS_FLAGS(oled_rotation, OLED_ROTATION_90)) {
calc_bounds(update_start, &display_start[1]); // Offset from I2C_CMD byte at the start
} else {
calc_bounds_90(update_start, &display_start[1]); // Offset from I2C_CMD byte at the start
}

// Send column & page position
if (I2C_TRANSMIT(display_start) != I2C_STATUS_SUCCESS) {
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) {
print("oled_render data failed\n");
return;
}
} else {
// Rotate the render chunks
const static uint8_t source_map[] = OLED_SOURCE_MAP;
const static uint8_t target_map[] = OLED_TARGET_MAP;

static uint8_t temp_buffer[OLED_BLOCK_SIZE];
memset(temp_buffer, 0, sizeof(temp_buffer));
for(uint8_t i = 0; i < sizeof(source_map); ++i) {
rotate_90(&oled_buffer[OLED_BLOCK_SIZE * update_start + source_map[i]], &temp_buffer[target_map[i]]);
}

// Send render data chunk after rotating
if (I2C_WRITE_REG(I2C_DATA, &temp_buffer[0], OLED_BLOCK_SIZE) != I2C_STATUS_SUCCESS) {
print("oled_render data failed\n");
return;
}
}

// Turn on display if it is off
oled_on();

// Clear dirty flag
oled_dirty &= ~(1 << update_start);
}

void oled_set_cursor(uint8_t col, uint8_t line) {
uint16_t index = line * oled_rotation_width + col * OLED_FONT_WIDTH;

// Out of bounds?
if (index >= OLED_MATRIX_SIZE) {
index = 0;
}

oled_cursor = &oled_buffer[index];
}

void oled_advance_page(bool clearPageRemainder) {
uint16_t index = oled_cursor - &oled_buffer[0];
uint8_t remaining = oled_rotation_width - (index % oled_rotation_width);

if (clearPageRemainder) {
// Remaining Char count
remaining = remaining / OLED_FONT_WIDTH;

// Write empty character until next line
while (remaining--)
oled_write_char(' ', false);
} else {
// Next page index out of bounds?
if (index + remaining >= OLED_MATRIX_SIZE) {
index = 0;
remaining = 0;
}

oled_cursor = &oled_buffer[index + remaining];
}
}

void oled_advance_char(void) {
uint16_t nextIndex = oled_cursor - &oled_buffer[0] + OLED_FONT_WIDTH;
uint8_t remainingSpace = oled_rotation_width - (nextIndex % oled_rotation_width);

// Do we have enough space on the current line for the next character
if (remainingSpace < OLED_FONT_WIDTH) {
nextIndex += remainingSpace;
}

// Did we go out of bounds
if (nextIndex >= OLED_MATRIX_SIZE) {
nextIndex = 0;
}

// Update cursor position
oled_cursor = &oled_buffer[nextIndex];
}

// Main handler that writes character data to the display buffer
void oled_write_char(const char data, bool invert) {
// Advance to the next line if newline
if (data == '\n') {
// Old source wrote ' ' until end of line...
oled_advance_page(true);
return;
}

// copy the current render buffer to check for dirty after
static uint8_t oled_temp_buffer[OLED_FONT_WIDTH];
memcpy(&oled_temp_buffer, oled_cursor, OLED_FONT_WIDTH);

// set the reder buffer data
uint8_t cast_data = (uint8_t)data; // font based on unsigned type for index
if (cast_data < OLED_FONT_START || cast_data > OLED_FONT_END) {
memset(oled_cursor, 0x00, OLED_FONT_WIDTH);
} else {
const uint8_t *glyph = &font[(cast_data - OLED_FONT_START) * OLED_FONT_WIDTH];
memcpy_P(oled_cursor, glyph, OLED_FONT_WIDTH);
}

// Invert if needed
if (invert) {
InvertCharacter(oled_cursor);
}

// Dirty check
if (memcmp(&oled_temp_buffer, oled_cursor, OLED_FONT_WIDTH)) {
oled_dirty |= (1 << ((oled_cursor - &oled_buffer[0]) / OLED_BLOCK_SIZE));
}

// Finally move to the next char
oled_advance_char();
}

void oled_write(const char *data, bool invert) {
const char *end = data + strlen(data);
while (data < end) {
oled_write_char(*data, invert);
data++;
}
}

void oled_write_ln(const char *data, bool invert) {
oled_write(data, invert);
oled_advance_page(true);
}

#if defined(__AVR__)
void oled_write_P(const char *data, bool invert) {
uint8_t c = pgm_read_byte(data);
while (c != 0) {
oled_write_char(c, invert);
c = pgm_read_byte(++data);
}
}

void oled_write_ln_P(const char *data, bool invert) {
oled_write_P(data, invert);
oled_advance_page(true);
}
#endif // defined(__AVR__)

bool oled_on(void) {
#if !defined(OLED_DISABLE_TIMEOUT)
oled_last_activity = timer_read();
#endif

static const uint8_t PROGMEM display_on[] = { I2C_CMD, DISPLAY_ON };
if (!oled_active) {
if (I2C_TRANSMIT_P(display_on) != I2C_STATUS_SUCCESS) {
print("oled_on cmd failed\n");
return oled_active;
}
oled_active = true;
}
return oled_active;
}

bool oled_off(void) {
static const uint8_t PROGMEM display_off[] = { I2C_CMD, DISPLAY_OFF };
if (oled_active) {
if (I2C_TRANSMIT_P(display_off) != I2C_STATUS_SUCCESS) {
print("oled_off cmd failed\n");
return oled_active;
}
oled_active = false;
}
return !oled_active;
}

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) {
static const uint8_t PROGMEM display_scroll_right[] = {
I2C_CMD, SCROLL_RIGHT, 0x00, 0x00, 0x00, 0x0F, 0x00, 0xFF, ACTIVATE_SCROLL };
if (I2C_TRANSMIT_P(display_scroll_right) != I2C_STATUS_SUCCESS) {
print("oled_scroll_right cmd failed\n");
return oled_scrolling;
}
oled_scrolling = true;
}
return oled_scrolling;
}

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) {
static const uint8_t PROGMEM display_scroll_left[] = {
I2C_CMD, SCROLL_LEFT, 0x00, 0x00, 0x00, 0x0F, 0x00, 0xFF, ACTIVATE_SCROLL };
if (I2C_TRANSMIT_P(display_scroll_left) != I2C_STATUS_SUCCESS) {
print("oled_scroll_left cmd failed\n");
return oled_scrolling;
}
oled_scrolling = true;
}
return oled_scrolling;
}

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) {
print("oled_scroll_off cmd failed\n");
return oled_scrolling;
}
oled_scrolling = false;
}
return !oled_scrolling;
}

uint8_t oled_max_chars(void) {
if (!HAS_FLAGS(oled_rotation, OLED_ROTATION_90)) {
return OLED_DISPLAY_WIDTH / OLED_FONT_WIDTH;
}
return OLED_DISPLAY_HEIGHT / OLED_FONT_WIDTH;
}

uint8_t oled_max_lines(void) {
if (!HAS_FLAGS(oled_rotation, OLED_ROTATION_90)) {
return OLED_DISPLAY_HEIGHT / OLED_FONT_HEIGHT;
}
return OLED_DISPLAY_WIDTH / OLED_FONT_HEIGHT;
}

void oled_task(void) {
if (!oled_initialized) {
return;
}

oled_set_cursor(0, 0);

oled_task_user();

// Smart render system, no need to check for dirty
oled_render();

// Display timeout check
#if !defined(OLED_DISABLE_TIMEOUT)
if (oled_active && timer_elapsed(oled_last_activity) > OLED_TIMEOUT) {
oled_off();
}
#endif
}

__attribute__((weak))
void oled_task_user(void) {
}

+ 183
- 0
drivers/oled/oled_driver.h View File

@@ -0,0 +1,183 @@
/*
Copyright 2019 Ryan Caltabiano <https://github.com/XScorpion2>

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
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 <http://www.gnu.org/licenses/>.
*/
#pragma once

#include <stdint.h>
#include <stdbool.h>


#if defined(OLED_DISPLAY_CUSTOM)
// Expected user to implement the necessary defines
#elif defined(OLED_DISPLAY_128X64)
// Double height 128x64
#define OLED_DISPLAY_WIDTH 128
#define OLED_DISPLAY_HEIGHT 64
#define OLED_MATRIX_SIZE (OLED_DISPLAY_HEIGHT / 8 * OLED_DISPLAY_WIDTH) // 1024 (compile time mathed)
#define OLED_BLOCK_TYPE uint16_t
#define OLED_BLOCK_COUNT (sizeof(OLED_BLOCK_TYPE) * 8) // 16 (compile time mathed)
#define OLED_BLOCK_SIZE (OLED_MATRIX_SIZE / OLED_BLOCK_COUNT) // 64 (compile time mathed)

// For 90 degree rotation, we map our internal matrix to oled matrix using fixed arrays
// The OLED writes to it's memory horizontally, starting top left, but our memory starts bottom left in this mode
#define OLED_SOURCE_MAP { 0, 8, 16, 24, 32, 40, 48, 56 }
#define OLED_TARGET_MAP { 56, 48, 40, 32, 24, 16, 8, 0 }
// If OLED_BLOCK_TYPE is uint8_t, these tables would look like:
// #define OLED_SOURCE_MAP { 0, 8, 16, 24, 32, 40, 48, 56, 64, 72, 80, 88, 96, 104, 112, 120 }
// #define OLED_TARGET_MAP { 56, 120, 48, 112, 40, 104, 32, 96, 24, 88, 16, 80, 8, 72, 0, 64 }
#else // defined(OLED_DISPLAY_128X64)
// Default 128x32
#define OLED_DISPLAY_WIDTH 128
#define OLED_DISPLAY_HEIGHT 32
#define OLED_MATRIX_SIZE (OLED_DISPLAY_HEIGHT / 8 * OLED_DISPLAY_WIDTH) // 512 (compile time mathed)
#define OLED_BLOCK_TYPE uint8_t // Type to use for segmenting the oled display for smart rendering, use unsigned types only
#define OLED_BLOCK_COUNT (sizeof(OLED_BLOCK_TYPE) * 8) // 8 (compile time mathed)
#define OLED_BLOCK_SIZE (OLED_MATRIX_SIZE / OLED_BLOCK_COUNT) // 128 (compile time mathed)

// For 90 degree rotation, we map our internal matrix to oled matrix using fixed arrays
// The OLED writes to it's memory horizontally, starting top left, but our memory starts bottom left in this mode
#define OLED_SOURCE_MAP { 0, 8, 16, 24, 32, 40, 48, 56 }
#define OLED_TARGET_MAP { 48, 32, 16, 0, 56, 40, 24, 8 }
#endif // defined(OLED_DISPLAY_CUSTOM)

// Address to use for tthe i2d oled communication
#if !defined(OLED_DISPLAY_ADDRESS)
#define OLED_DISPLAY_ADDRESS 0x3C
#endif

// Custom font file to use
#if !defined(OLED_FONT_H)
#define OLED_FONT_H "glcdfont.c"
#endif
// unsigned char value of the first character in the font file
#if !defined(OLED_FONT_START)
#define OLED_FONT_START 0
#endif
// unsigned char value of the last character in the font file
#if !defined(OLED_FONT_END)
#define OLED_FONT_END 224
#endif
// Font render width
#if !defined(OLED_FONT_WIDTH)
#define OLED_FONT_WIDTH 6
#endif
// Font render height
#if !defined(OLED_FONT_HEIGHT)
#define OLED_FONT_HEIGHT 8
#endif

#define OLED_ROTATION_0 0x00
#define OLED_ROTATION_90 0x01
#define OLED_ROTATION_180 0x02
#define OLED_ROTATION_270 0x03

// Initialize the oled display, rotating the rendered output based on the define passed in.
// Returns true if the OLED was initialized successfully
bool oled_init(uint8_t rotation);

// Called at the start of oled_init, weak function overridable by the user
// rotation - the value passed into oled_init
// Return new uint8_t if you want to override default rotation
uint8_t oled_init_user(uint8_t rotation);

// Clears the display buffer, resets cursor position to 0, and sets the buffer to dirty for rendering
void oled_clear(void);

// Renders the dirty chunks of the buffer to oled display
void oled_render(void);

// Moves cursor to character position indicated by column and line, wraps if out of bounds
// Max column denoted by 'oled_max_chars()' and max lines by 'oled_max_lines()' functions
void oled_set_cursor(uint8_t col, uint8_t line);

// Advances the cursor to the next page, writing ' ' if true
// Wraps to the begining when out of bounds
void oled_advance_page(bool clearPageRemainder);

// Moves the cursor forward 1 character length
// Advance page if there is not enough room for the next character
// Wraps to the begining when out of bounds
void oled_advance_char(void);

// Writes a single character to the buffer at current cursor position
// Advances the cursor while writing, inverts the pixels if true
// Main handler that writes character data to the display buffer
void oled_write_char(const char data, bool invert);

// Writes a string to the buffer at current cursor position
// Advances the cursor while writing, inverts the pixels if true
void oled_write(const char *data, bool invert);

// Writes a string to the buffer at current cursor position
// Advances the cursor while writing, inverts the pixels if true
// Advances the cursor to the next page, wiring ' ' to the remainder of the current page
void oled_write_ln(const char *data, bool invert);

#if defined(__AVR__)
// Writes a PROGMEM string to the buffer at current cursor position
// Advances the cursor while writing, inverts the pixels if true
// Remapped to call 'void oled_write(const char *data, bool invert);' on ARM
void oled_write_P(const char *data, bool invert);

// Writes a PROGMEM string to the buffer at current cursor position
// Advances the cursor while writing, inverts the pixels if true
// Advances the cursor to the next page, wiring ' ' to the remainder of the current page
// Remapped to call 'void oled_write_ln(const char *data, bool invert);' on ARM
void oled_write_ln_P(const char *data, bool invert);
#else
// Writes a string to the buffer at current cursor position
// Advances the cursor while writing, inverts the pixels if true
#define oled_write_P(data, invert) oled_write(data, invert)

// Writes a string to the buffer at current cursor position
// Advances the cursor while writing, inverts the pixels if true
// Advances the cursor to the next page, wiring ' ' to the remainder of the current page
#define oled_write_ln_P(data, invert) oled_write(data, invert)
#endif // defined(__AVR__)

// Can be used to manually turn on the screen if it is off
// Returns true if the screen was on or turns on
bool oled_on(void);

// Can be used to manually turn off the screen if it is on
// Returns true if the screen was off or turns off
bool oled_off(void);

// Basically it's oled_render, but with timeout management and oled_task_user calling!
void oled_task(void);

// Called at the start of oled_task, weak function overridable by the user
void oled_task_user(void);

// Scrolls the entire display right
// Returns true if the screen was scrolling or starts scrolling
// NOTE: display contents cannot be changed while scrolling
bool oled_scroll_right(void);

// Scrolls the entire display left
// Returns true if the screen was scrolling or starts scrolling
// NOTE: display contents cannot be changed while scrolling
bool oled_scroll_left(void);

// Turns off display scrolling
// Returns true if the screen was not scrolling or stops scrolling
bool oled_scroll_off(void);

// Returns the maximum number of characters that will fit on a line
uint8_t oled_max_chars(void);

// Returns the maximum number of lines that will fit on the oled
uint8_t oled_max_lines(void);

+ 27
- 19
keyboards/cannonkeys/satisfaction75/i2c_master.c View File

@@ -29,8 +29,6 @@
#include "quantum.h"
#include <string.h>
#include <hal.h>
#include "chtypes.h"
#include "ch.h"

static uint8_t i2c_address;

@@ -44,6 +42,18 @@ static const I2CConfig i2cconfig = {
0
};

static i2c_status_t chibios_to_qmk(const msg_t* status) {
switch (*status) {
case I2C_NO_ERROR:
return I2C_STATUS_SUCCESS;
case I2C_TIMEOUT:
return I2C_STATUS_TIMEOUT;
// I2C_BUS_ERROR, I2C_ARBITRATION_LOST, I2C_ACK_FAILURE, I2C_OVERRUN, I2C_PEC_ERROR, I2C_SMB_ALERT
default:
return I2C_STATUS_ERROR;
}
}

__attribute__ ((weak))
void i2c_init(void)
{
@@ -59,34 +69,32 @@ void i2c_init(void)
//i2cInit(); //This is invoked by halInit() so no need to redo it.
}

// This is usually not needed
uint8_t i2c_start(uint8_t address)
i2c_status_t i2c_start(uint8_t address)
{
i2c_address = address;
i2cStart(&I2C_DRIVER, &i2cconfig);
return 0;
return I2C_STATUS_SUCCESS;
}

uint8_t i2c_transmit(uint8_t address, uint8_t* data, uint16_t length, uint16_t timeout)
i2c_status_t i2c_transmit(uint8_t address, const uint8_t* data, uint16_t length, uint16_t timeout)
{
msg_t status = MSG_OK;

i2c_address = address;
i2cStart(&I2C_DRIVER, &i2cconfig);
i2cAcquireBus(&I2C_DRIVER);
status = i2cMasterTransmitTimeout(&I2C_DRIVER, (i2c_address >> 1), data, length, 0, 0, MS2ST(timeout));
msg_t status = i2cMasterTransmitTimeout(&I2C_DRIVER, (i2c_address >> 1), data, length, 0, 0, MS2ST(timeout));
i2cReleaseBus(&I2C_DRIVER);
return status;
return chibios_to_qmk(&status);
}

uint8_t i2c_receive(uint8_t address, uint8_t* data, uint16_t length, uint16_t timeout)
i2c_status_t i2c_receive(uint8_t address, uint8_t* data, uint16_t length, uint16_t timeout)
{
i2c_address = address;
i2cStart(&I2C_DRIVER, &i2cconfig);
return i2cMasterReceiveTimeout(&I2C_DRIVER, (i2c_address >> 1), data, length, MS2ST(timeout));
msg_t status = i2cMasterReceiveTimeout(&I2C_DRIVER, (i2c_address >> 1), data, length, MS2ST(timeout));
return chibios_to_qmk(&status);
}

uint8_t i2c_writeReg(uint8_t devaddr, uint8_t regaddr, uint8_t* data, uint16_t length, uint16_t timeout)
i2c_status_t i2c_writeReg(uint8_t devaddr, uint8_t regaddr, const uint8_t* data, uint16_t length, uint16_t timeout)
{
i2c_address = devaddr;
i2cStart(&I2C_DRIVER, &i2cconfig);
@@ -98,19 +106,19 @@ uint8_t i2c_writeReg(uint8_t devaddr, uint8_t regaddr, uint8_t* data, uint16_t l
}
complete_packet[0] = regaddr;

return i2cMasterTransmitTimeout(&I2C_DRIVER, (i2c_address >> 1), complete_packet, length + 1, 0, 0, MS2ST(timeout));
msg_t status = i2cMasterTransmitTimeout(&I2C_DRIVER, (i2c_address >> 1), complete_packet, length + 1, 0, 0, MS2ST(timeout));
return chibios_to_qmk(&status);
}

uint8_t i2c_readReg(uint8_t devaddr, uint8_t* regaddr, uint8_t* data, uint16_t length, uint16_t timeout)
i2c_status_t i2c_readReg(uint8_t devaddr, uint8_t* regaddr, uint8_t* data, uint16_t length, uint16_t timeout)
{
i2c_address = devaddr;
i2cStart(&I2C_DRIVER, &i2cconfig);
return i2cMasterTransmitTimeout(&I2C_DRIVER, (i2c_address >> 1), regaddr, 1, data, length, MS2ST(timeout));
msg_t status = i2cMasterTransmitTimeout(&I2C_DRIVER, (i2c_address >> 1), regaddr, 1, data, length, MS2ST(timeout));
return chibios_to_qmk(&status);
}

// This is usually not needed. It releases the driver to allow pins to become GPIO again.
uint8_t i2c_stop(void)
void i2c_stop(void)
{
i2cStop(&I2C_DRIVER);
return 0;
}

+ 68
- 72
keyboards/sol/common/glcdfont.c View File

@@ -1,8 +1,4 @@
// This is the 'classic' fixed-space bitmap font for Adafruit_GFX since 1.0.
// See gfxfont.h for newer custom bitmap font info.

#ifndef FONT5X7_H
#define FONT5X7_H
#pragma once

#ifdef __AVR__
#include <avr/io.h>
@@ -13,7 +9,8 @@
#define PROGMEM
#endif

// Standard ASCII 5x7 font
// Helidox 8x6 font with RGBKB SOL Logo
// Online editor: http://teripom.x0.com/

static const unsigned char font[] PROGMEM = {
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
@@ -144,27 +141,27 @@ static const unsigned char font[] PROGMEM = {
0x00, 0x41, 0x36, 0x08, 0x00, 0x00,
0x02, 0x01, 0x02, 0x04, 0x02, 0x00,
0x3C, 0x26, 0x23, 0x26, 0x3C, 0x00,
0x03, 0x07, 0x1F, 0x7F, 0xFF, 0xFF,
0xFE, 0xF8, 0xF0, 0xC0, 0x20, 0xF8,
0xFE, 0xFF, 0xFE, 0x79, 0x27, 0x1F,
0x7F, 0xFF, 0xFF, 0xFE, 0xF8, 0xF0,
0xC0, 0x20, 0xF8, 0xFE, 0xFF, 0xFF,
0x7F, 0x3F, 0x3F, 0x7F, 0xFF, 0xFE,
0xF8, 0xF0, 0xC0, 0x00, 0x00, 0x00,
0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0xFF, 0x7F, 0x7F, 0x7F,
0xBF, 0xBF, 0xC0, 0xC0, 0xC0, 0xE0,
0xE0, 0xE0, 0xE0, 0xF0, 0xF0, 0xF0,
0xF8, 0x78, 0x78, 0x7C, 0x3C, 0x3C,
0xFE, 0xFE, 0xFE, 0xFF, 0xFF, 0xFF,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x80, 0x00,
0x00, 0x00, 0x00, 0x80, 0x80, 0x80,
0x00, 0x00, 0x00, 0x00, 0x80, 0x80,
0xBF, 0xBF, 0xDF, 0xDF, 0xEF, 0xEF,
0x00, 0x03, 0x07, 0x1F, 0x7F, 0xFF,
0xFF, 0xFF, 0xFE, 0xF8, 0xE0, 0xC0,
0xE0, 0xF8, 0xFE, 0xFF, 0xFF, 0xFF,
0x7F, 0x1F, 0x07, 0x03, 0x00, 0x00,
0x80, 0x80, 0x80, 0x80, 0x80, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x80, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x80, 0x00, 0x00, 0x0C, 0x90,
0xB0, 0xE0, 0x72, 0x31, 0x9B, 0xDE,
0xCE, 0xEC, 0xEE, 0xE9, 0xE9, 0xEC,
0xCF, 0xDA, 0x99, 0x3E, 0x62, 0xE4,
0xC4, 0x70, 0x10, 0x10, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x60,
0xC0, 0xC0, 0x80, 0x80, 0x02, 0x85,
0x85, 0x87, 0x85, 0x89, 0x89, 0x92,
0xEA, 0xC6, 0xC4, 0x48, 0x50, 0x60,
0x40, 0x40, 0x40, 0x40, 0xC0, 0xE0,
0x50, 0x28, 0x10, 0x10, 0x60, 0xC0,
0x40, 0x40, 0x40, 0x40, 0x80, 0x80,
0x80, 0x80, 0x80, 0xE0, 0xF8, 0xFC,
0xF8, 0xF0, 0x00, 0x00, 0x00, 0x00,
0xE0, 0xF0, 0xF0, 0xF0, 0xE0, 0xEC,
0xEE, 0xF7, 0xF3, 0x70, 0x20, 0x00,
0x7C, 0x7C, 0x7C, 0x7E, 0x00, 0x7E,
@@ -173,30 +170,30 @@ static const unsigned char font[] PROGMEM = {
0x4F, 0x5B, 0xFE, 0xC0, 0x00, 0x00,
0xC0, 0x00, 0xDC, 0xD7, 0xDE, 0xDE,
0xDE, 0xD7, 0xDC, 0x00, 0xC0, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0xC1, 0xF3,
0xCF, 0xBF, 0x7F, 0xFF, 0xFF, 0xFC,
0xFB, 0xE7, 0x81, 0x00, 0x00, 0x00,
0x00, 0x80, 0xE3, 0xCF, 0x3F, 0xFF,
0xFF, 0xFF, 0xFC, 0xFB, 0xE7, 0x81,
0x00, 0x00, 0x00, 0x00, 0x81, 0xE7,
0xFF, 0xFF, 0xFF, 0xFF, 0x3C, 0x00,
0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xF8, 0xF8, 0xFC, 0x7C, 0x7E,
0x7E, 0x3E, 0xFE, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xF7, 0xF7, 0xF7, 0xFB,
0xFB, 0x7D, 0x7D, 0x7D, 0xBE, 0xBE,
0xBE, 0xDF, 0xDF, 0xE0, 0xE0, 0x00,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0x00, 0xFC, 0xFC, 0x7C, 0x7E, 0x7E,
0x3E, 0x3E, 0x1F, 0x1F, 0x1F, 0x0F,
0x00, 0x00, 0x00, 0xE0, 0xEC, 0xDF,
0xFC, 0xE0, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x7F, 0x80, 0x80,
0x80, 0x70, 0x0F, 0x00, 0x00, 0x80,
0x7F, 0x00, 0x00, 0x7F, 0x80, 0x80,
0x80, 0x80, 0x80, 0x80, 0x80, 0x7F,
0x00, 0x00, 0x80, 0x80, 0x80, 0x80,
0x80, 0x80, 0x80, 0xFF, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x40, 0x21, 0x33, 0x3B, 0x7B,
0xFF, 0x00, 0x7C, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0x00, 0x00, 0x00, 0x00, 0x00, 0x81,
0xE7, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xE7, 0x81,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0xFF, 0xFF, 0xFF, 0xFF, 0x7C, 0x01,
0xFF, 0xDE, 0x8C, 0x04, 0x0C, 0x08,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x01, 0x01, 0x01, 0x7F, 0x80,
0x80, 0xBE, 0xBE, 0x80, 0x80, 0x80,
0xC1, 0xFF, 0x80, 0x04, 0x32, 0x5E,
0x1C, 0x3D, 0x26, 0x10, 0xC1, 0xFF,
0x3E, 0x00, 0x00, 0x08, 0x36, 0xC1,
0x08, 0x08, 0x14, 0x77, 0x94, 0x94,
0x94, 0xF7, 0x94, 0xF7, 0x9C, 0x9C,
0xFF, 0xFF, 0x1E, 0x00, 0x00, 0x00,
0x0F, 0x1F, 0x3F, 0x7F, 0x7F, 0x7F,
0x7F, 0x7F, 0x3F, 0x1E, 0x0C, 0x00,
0x1F, 0x1F, 0x1F, 0x3F, 0x00, 0x3F,
@@ -205,30 +202,31 @@ static const unsigned char font[] PROGMEM = {
0x20, 0x30, 0x78, 0x7F, 0x3B, 0x00,
0x03, 0x00, 0x0F, 0x7F, 0x0F, 0x0F,
0x0F, 0x7F, 0x0F, 0x00, 0x03, 0x00,
0x40, 0x7C, 0x3F, 0x3F, 0x23, 0x01,
0x23, 0x3F, 0x37, 0x6C, 0x40, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x06, 0x02, 0x06,
0x4D, 0x4F, 0x8C, 0xF9, 0x73, 0x37,
0x27, 0x2F, 0x2F, 0xAF, 0xEF, 0x6F,
0x77, 0x17, 0x33, 0x79, 0xCC, 0x1F,
0x31, 0x20, 0x21, 0x02, 0x02, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x40, 0xE0,
0xA0, 0xA0, 0xD0, 0x90, 0x48, 0x48,
0x25, 0x2B, 0x11, 0x09, 0x05, 0x03,
0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
0x01, 0x03, 0x02, 0x04, 0x03, 0x01,
0x01, 0x01, 0x01, 0x01, 0x01, 0x00,
0x00, 0x00, 0x00, 0x03, 0x0F, 0x1F,
0x0F, 0x03, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x60, 0x70, 0x7C, 0x7F, 0x7F, 0x7F,
0x7F, 0x1F, 0x06, 0x01, 0x03, 0x0F,
0x3F, 0x7F, 0x7F, 0x7E, 0x7C, 0x7C,
0x7E, 0x7F, 0x7F, 0x7F, 0x1F, 0x06,
0x01, 0x07, 0x0F, 0x3F, 0x7F, 0x7F,
0x7E, 0x7C, 0x7C, 0x7E, 0x7F, 0x7F,
0x3F, 0x0F, 0x03, 0x00, 0x00, 0x00,
0x00, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F,
0x7F, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x7F, 0x7F, 0x7F, 0x7F,
0x7F, 0x7F, 0x7D, 0x7D, 0x3D, 0x3E,
0x1E, 0x1F, 0x1F, 0x1F, 0x0F, 0x0F,
0x07, 0x07, 0x07, 0x03, 0x03, 0x00,
0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F,
0x7C, 0x7C, 0x7C, 0x7C, 0x7C, 0x7C,
0x7C, 0x7C, 0x7C, 0x7C, 0x7C, 0x00,
0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F,
0x00, 0x40, 0x70, 0x78, 0x7E, 0x7F,
0x7F, 0x7F, 0x3F, 0x0F, 0x03, 0x01,
0x03, 0x0F, 0x3F, 0x7F, 0x7F, 0x7F,
0x7E, 0x78, 0x70, 0x40, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
@@ -239,6 +237,4 @@ static const unsigned char font[] PROGMEM = {
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00
};
#endif // FONT5X7_H

+ 0
- 329
keyboards/sol/common/ssd1306.c View File

@@ -1,329 +0,0 @@
#ifdef SSD1306OLED

#include "ssd1306.h"
#include "i2c.h"
#include <string.h>
#include "print.h"
#ifndef LOCAL_GLCDFONT
#include "common/glcdfont.c"
#else
#include <helixfont.h>
#endif
#ifdef ADAFRUIT_BLE_ENABLE
#include "adafruit_ble.h"
#endif
#ifdef PROTOCOL_LUFA
#include "lufa.h"
#endif
#include "sendchar.h"
#include "timer.h"

// Set this to 1 to help diagnose early startup problems
// when testing power-on with ble. Turn it off otherwise,
// as the latency of printing most of the debug info messes
// with the matrix scan, causing keys to drop.
#define DEBUG_TO_SCREEN 0

//static uint16_t last_battery_update;
//static uint32_t vbat;
//#define BatteryUpdateInterval 10000 /* milliseconds */
#define ScreenOffInterval 300000 /* milliseconds */
#if DEBUG_TO_SCREEN
static uint8_t displaying;
#endif
static uint16_t last_flush;

// Write command sequence.
// Returns true on success.
static inline bool _send_cmd1(uint8_t cmd) {
bool res = false;

if (i2c_start_write(SSD1306_ADDRESS)) {
xprintf("failed to start write to %d\n", SSD1306_ADDRESS);
goto done;
}

if (i2c_master_write(0x0 /* command byte follows */)) {
print("failed to write control byte\n");

goto done;
}

if (i2c_master_write(cmd)) {
xprintf("failed to write command %d\n", cmd);
goto done;
}
res = true;
done:
i2c_master_stop();
return res;
}

// Write 2-byte command sequence.
// Returns true on success
static inline bool _send_cmd2(uint8_t cmd, uint8_t opr) {
if (!_send_cmd1(cmd)) {
return false;
}
return _send_cmd1(opr);
}

// Write 3-byte command sequence.
// Returns true on success
static inline bool _send_cmd3(uint8_t cmd, uint8_t opr1, uint8_t opr2) {
if (!_send_cmd1(cmd)) {
return false;
}
if (!_send_cmd1(opr1)) {
return false;
}
return _send_cmd1(opr2);
}

#define send_cmd1(c) if (!_send_cmd1(c)) {goto done;}
#define send_cmd2(c,o) if (!_send_cmd2(c,o)) {goto done;}
#define send_cmd3(c,o1,o2) if (!_send_cmd3(c,o1,o2)) {goto done;}

static void clear_display(void) {
matrix_clear(&display);

// Clear all of the display bits (there can be random noise
// in the RAM on startup)
send_cmd3(PageAddr, 0, (DisplayHeight / 8) - 1);
send_cmd3(ColumnAddr, 0, DisplayWidth - 1);

if (i2c_start_write(SSD1306_ADDRESS)) {
goto done;
}
if (i2c_master_write(0x40)) {
// Data mode
goto done;
}
for (uint8_t row = 0; row < MatrixRows; ++row) {
for (uint8_t col = 0; col < DisplayWidth; ++col) {
i2c_master_write(0);
}
}

display.dirty = false;

done:
i2c_master_stop();
}

#if DEBUG_TO_SCREEN
#undef sendchar
static int8_t capture_sendchar(uint8_t c) {
sendchar(c);
iota_gfx_write_char(c);

if (!displaying) {
iota_gfx_flush();
}
return 0;
}
#endif

bool iota_gfx_init(bool rotate) {
bool success = false;

i2c_master_init();
send_cmd1(DisplayOff);
send_cmd2(SetDisplayClockDiv, 0x80);
send_cmd2(SetMultiPlex, DisplayHeight - 1);

send_cmd2(SetDisplayOffset, 0);


send_cmd1(SetStartLine | 0x0);
send_cmd2(SetChargePump, 0x14 /* Enable */);
send_cmd2(SetMemoryMode, 0 /* horizontal addressing */);

if(rotate){
// the following Flip the display orientation 180 degrees
send_cmd1(SegRemap);
send_cmd1(ComScanInc);
}else{
// Flips the display orientation 0 degrees
send_cmd1(SegRemap | 0x1);
send_cmd1(ComScanDec);
}

send_cmd2(SetComPins, 0x2);
send_cmd2(SetContrast, 0x8f);
send_cmd2(SetPreCharge, 0xf1);
send_cmd2(SetVComDetect, 0x40);
send_cmd1(DisplayAllOnResume);
send_cmd1(NormalDisplay);
send_cmd1(DeActivateScroll);
send_cmd1(DisplayOn);

send_cmd2(SetContrast, 0); // Dim

clear_display();

success = true;

iota_gfx_flush();

#if DEBUG_TO_SCREEN
print_set_sendchar(capture_sendchar);
#endif

done:
return success;
}

bool iota_gfx_off(void) {
bool success = false;

send_cmd1(DisplayOff);
success = true;

done:
return success;
}

bool iota_gfx_on(void) {
bool success = false;

send_cmd1(DisplayOn);
success = true;

done:
return success;
}

void matrix_write_char_inner(struct CharacterMatrix *matrix, uint8_t c) {
*matrix->cursor = c;
++matrix->cursor;

if (matrix->cursor - &matrix->display[0][0] == sizeof(matrix->display)) {
// We went off the end; scroll the display upwards by one line
memmove(&matrix->display[0], &matrix->display[1],
MatrixCols * (MatrixRows - 1));
matrix->cursor = &matrix->display[MatrixRows - 1][0];
memset(matrix->cursor, ' ', MatrixCols);
}
}

void matrix_write_char(struct CharacterMatrix *matrix, uint8_t c) {
matrix->dirty = true;

if (c == '\n') {
// Clear to end of line from the cursor and then move to the
// start of the next line
uint8_t cursor_col = (matrix->cursor - &matrix->display[0][0]) % MatrixCols;

while (cursor_col++ < MatrixCols) {
matrix_write_char_inner(matrix, ' ');
}
return;
}

matrix_write_char_inner(matrix, c);
}

void iota_gfx_write_char(uint8_t c) {
matrix_write_char(&display, c);
}

void matrix_write(struct CharacterMatrix *matrix, const char *data) {
const char *end = data + strlen(data);
while (data < end) {
matrix_write_char(matrix, *data);
++data;
}
}

void iota_gfx_write(const char *data) {
matrix_write(&display, data);
}

void matrix_write_P(struct CharacterMatrix *matrix, const char *data) {
while (true) {
uint8_t c = pgm_read_byte(data);
if (c == 0) {
return;
}
matrix_write_char(matrix, c);
++data;
}
}

void iota_gfx_write_P(const char *data) {
matrix_write_P(&display, data);
}

void matrix_clear(struct CharacterMatrix *matrix) {
memset(matrix->display, ' ', sizeof(matrix->display));
matrix->cursor = &matrix->display[0][0];
matrix->dirty = true;
}

void iota_gfx_clear_screen(void) {
matrix_clear(&display);
}

void matrix_render(struct CharacterMatrix *matrix) {
last_flush = timer_read();
iota_gfx_on();
#if DEBUG_TO_SCREEN
++displaying;
#endif

// Move to the home position
send_cmd3(PageAddr, 0, MatrixRows - 1);
send_cmd3(ColumnAddr, 0, (MatrixCols * FontWidth) - 1);

if (i2c_start_write(SSD1306_ADDRESS)) {
goto done;
}
if (i2c_master_write(0x40)) {
// Data mode
goto done;
}

for (uint8_t row = 0; row < MatrixRows; ++row) {
for (uint8_t col = 0; col < MatrixCols; ++col) {
const uint8_t *glyph = font + (matrix->display[row][col] * FontWidth);

for (uint8_t glyphCol = 0; glyphCol < FontWidth; ++glyphCol) {
uint8_t colBits = pgm_read_byte(glyph + glyphCol);
i2c_master_write(colBits);
}

// 1 column of space between chars (it's not included in the glyph)
//i2c_master_write(0);
}
}

matrix->dirty = false;

done:
i2c_master_stop();
#if DEBUG_TO_SCREEN
--displaying;
#endif
}

void iota_gfx_flush(void) {
matrix_render(&display);
}

__attribute__ ((weak))
void iota_gfx_task_user(void) {
}

void iota_gfx_task(void) {
iota_gfx_task_user();

if (display.dirty) {
iota_gfx_flush();
}

if (timer_elapsed(last_flush) > ScreenOffInterval) {
iota_gfx_off();
}
}
#endif

+ 0
- 92
keyboards/sol/common/ssd1306.h View File

@@ -1,92 +0,0 @@
#ifndef SSD1306_H
#define SSD1306_H

#include <stdbool.h>
#include <stdio.h>
#include "pincontrol.h"

enum ssd1306_cmds {
DisplayOff = 0xAE,
DisplayOn = 0xAF,

SetContrast = 0x81,
DisplayAllOnResume = 0xA4,

DisplayAllOn = 0xA5,
NormalDisplay = 0xA6,
InvertDisplay = 0xA7,
SetDisplayOffset = 0xD3,
SetComPins = 0xda,
SetVComDetect = 0xdb,
SetDisplayClockDiv = 0xD5,
SetPreCharge = 0xd9,
SetMultiPlex = 0xa8,
SetLowColumn = 0x00,
SetHighColumn = 0x10,
SetStartLine = 0x40,

SetMemoryMode = 0x20,
ColumnAddr = 0x21,
PageAddr = 0x22,

ComScanInc = 0xc0,
ComScanDec = 0xc8,
SegRemap = 0xa0,
SetChargePump = 0x8d,
ExternalVcc = 0x01,
SwitchCapVcc = 0x02,

ActivateScroll = 0x2f,
DeActivateScroll = 0x2e,
SetVerticalScrollArea = 0xa3,
RightHorizontalScroll = 0x26,
LeftHorizontalScroll = 0x27,
VerticalAndRightHorizontalScroll = 0x29,
VerticalAndLeftHorizontalScroll = 0x2a,
};

// Controls the SSD1306 128x32 OLED display via i2c

#ifndef SSD1306_ADDRESS
#define SSD1306_ADDRESS 0x3C
#endif

#define DisplayHeight 32
#define DisplayWidth 128

#define FontHeight 8
#define FontWidth 6

#define MatrixRows (DisplayHeight / FontHeight)
#define MatrixCols (DisplayWidth / FontWidth)

struct CharacterMatrix {
uint8_t display[MatrixRows][MatrixCols];
uint8_t *cursor;
bool dirty;
};

struct CharacterMatrix display;

bool iota_gfx_init(bool rotate);
void iota_gfx_task(void);
bool iota_gfx_off(void);
bool iota_gfx_on(void);
void iota_gfx_flush(void);
void iota_gfx_write_char(uint8_t c);
void iota_gfx_write(const char *data);
void iota_gfx_write_P(const char *data);
void iota_gfx_clear_screen(void);

void iota_gfx_task_user(void);

void matrix_clear(struct CharacterMatrix *matrix);
void matrix_write_char_inner(struct CharacterMatrix *matrix, uint8_t c);
void matrix_write_char(struct CharacterMatrix *matrix, uint8_t c);
void matrix_write(struct CharacterMatrix *matrix, const char *data);
void matrix_write_P(struct CharacterMatrix *matrix, const char *data);
void matrix_render(struct CharacterMatrix *matrix);



#endif

+ 0
- 162
keyboards/sol/i2c.c View File

@@ -1,162 +0,0 @@
#include <util/twi.h>
#include <avr/io.h>
#include <stdlib.h>
#include <avr/interrupt.h>
#include <util/twi.h>
#include <stdbool.h>
#include "i2c.h"

#ifdef USE_I2C

// Limits the amount of we wait for any one i2c transaction.
// Since were running SCL line 100kHz (=> 10μs/bit), and each transactions is
// 9 bits, a single transaction will take around 90μs to complete.
//
// (F_CPU/SCL_CLOCK) => # of μC cycles to transfer a bit
// poll loop takes at least 8 clock cycles to execute
#define I2C_LOOP_TIMEOUT (9+1)*(F_CPU/SCL_CLOCK)/8

#define BUFFER_POS_INC() (slave_buffer_pos = (slave_buffer_pos+1)%SLAVE_BUFFER_SIZE)

volatile uint8_t i2c_slave_buffer[SLAVE_BUFFER_SIZE];

static volatile uint8_t slave_buffer_pos;
static volatile bool slave_has_register_set = false;

// Wait for an i2c operation to finish
inline static
void i2c_delay(void) {
uint16_t lim = 0;
while(!(TWCR & (1<<TWINT)) && lim < I2C_LOOP_TIMEOUT)
lim++;

// easier way, but will wait slightly longer
// _delay_us(100);
}

// Setup twi to run at 100kHz or 400kHz (see ./i2c.h SCL_CLOCK)
void i2c_master_init(void) {