SSD1306 OLED Display driver refactored (#290)

* SSD1306 OLED Display driver SPI refactored:
- SPI connection support
- different display sizes support

* I2C address added to device descriptor

* Small fix
This commit is contained in:
Ruslan V. Uss 2016-11-29 04:57:22 +06:00 committed by sheinz
parent 2d933cf0e4
commit b807eefeaf
11 changed files with 710 additions and 343 deletions

View file

@ -0,0 +1,3 @@
# SSD1306 I2C/SPI OLED LCD Example
To run this example connect the SSD1306 OLED LCD and configure protocol, display size and pins in main.c file.

View file

@ -0,0 +1,96 @@
#include <espressif/esp_common.h>
#include <esp/uart.h>
#include <FreeRTOS.h>
#include <task.h>
#include <queue.h>
#include <string.h>
#include <ssd1306/ssd1306.h>
/* Remove this line if your display connected by SPI */
#define I2C_CONNECTION
#ifdef I2C_CONNECTION
#include <i2c/i2c.h>
#endif
#include "image.xbm"
/* Change this according to you schematics and display size */
#define DISPLAY_WIDTH 128
#define DISPLAY_HEIGHT 64
#ifdef I2C_CONNECTION
#define PROTOCOL SSD1306_PROTO_I2C
#define ADDR SSD1306_I2C_ADDR_0
#define SCL_PIN 5
#define SDA_PIN 4
#else
#define PROTOCOL SSD1306_PROTO_SPI4
#define CS_PIN 5
#define DC_PIN 4
#endif
/* Declare device descriptor */
static const ssd1306_t dev = {
.protocol = PROTOCOL,
#ifdef I2C_CONNECTION
.addr = ADDR,
#else
.cs_pin = CS_PIN,
.dc_pin = DC_PIN,
#endif
.width = DISPLAY_WIDTH,
.height = DISPLAY_HEIGHT
};
/* Local frame buffer */
static uint8_t buffer[DISPLAY_WIDTH * DISPLAY_HEIGHT / 8];
static void ssd1306_task(void *pvParameters)
{
printf("%s: Started user interface task\n", __FUNCTION__);
vTaskDelay(1000/portTICK_PERIOD_MS);
if (ssd1306_load_xbm(&dev, image_bits, buffer))
goto error_loop;
ssd1306_set_whole_display_lighting(&dev, false);
bool fwd = false;
while (1) {
vTaskDelay(2000 / portTICK_PERIOD_MS);
printf("%s: still alive, flipping!\n", __FUNCTION__);
ssd1306_set_scan_direction_fwd(&dev, fwd);
fwd = !fwd;
}
error_loop:
printf("%s: error while loading framebuffer into SSD1306\n", __func__);
for(;;){
vTaskDelay(2000 / portTICK_PERIOD_MS);
printf("%s: error loop\n", __FUNCTION__);
}
}
void user_init(void)
{
// Setup HW
uart_set_baud(0, 115200);
printf("SDK version:%s\n", sdk_system_get_sdk_version());
#ifdef I2C_CONNECTION
i2c_init(SCL_PIN, SDA_PIN);
#endif
while (ssd1306_init(&dev) != 0)
{
printf("%s: failed to init SSD1306 lcd\n", __func__);
vTaskDelay(1000/portTICK_PERIOD_MS);
}
ssd1306_set_whole_display_lighting(&dev, true);
vTaskDelay(1000/portTICK_PERIOD_MS);
// Create user interface task
xTaskCreate(ssd1306_task, "ssd1306_task", 256, NULL, 2, NULL);
}

View file

@ -1,3 +0,0 @@
# I2C / SSD1306 OLED LCD Example
To run this example connect the SSD1306 OLED LCD and configure SDA/SCL pins in ssd1306_i2c.c file.

View file

@ -1,65 +0,0 @@
#include "espressif/esp_common.h"
#include "esp/uart.h"
#include "FreeRTOS.h"
#include "task.h"
#include "queue.h"
#include <string.h>
#include "i2c/i2c.h"
#include "ssd1306/ssd1306.h"
#include "image.xbm"
/* Change this according to you schematics */
#define SCL_PIN GPIO_ID_PIN((14))
#define SDA_PIN GPIO_ID_PIN((12))
/* Local frame buffer */
static uint8_t buffer[SSD1306_ROWS * SSD1306_COLS / 8];
static void ssd1306_task(void *pvParameters)
{
printf("%s: Started user interface task\n", __FUNCTION__);
vTaskDelay(1000/portTICK_PERIOD_MS);
if (ssd1306_load_xbm(image_bits, buffer))
goto error_loop;
ssd1306_set_whole_display_lighting(false);
while (1) {
vTaskDelay(2000 / portTICK_PERIOD_MS);
printf("%s: steel alive\n", __FUNCTION__);
}
error_loop:
printf("%s: error while loading framebuffer into SSD1306\n", __func__);
for(;;){
vTaskDelay(2000 / portTICK_PERIOD_MS);
printf("%s: error loop\n", __FUNCTION__);
}
}
void user_init(void)
{
// Setup HW
uart_set_baud(0, 115200);
printf("SDK version:%s\n", sdk_system_get_sdk_version());
i2c_init(SCL_PIN, SDA_PIN);
if (ssd1306_init()){
for (;;) {
printf("%s: failed to init SSD1306 lcd\n", __func__);
vTaskDelay(1000/portTICK_PERIOD_MS);
}
}
ssd1306_set_whole_display_lighting(true);
vTaskDelay(1000/portTICK_PERIOD_MS);
// Create user interface task
xTaskCreate(ssd1306_task, "ssd1306_task", 256, NULL, 2, NULL);
}

View file

@ -1,27 +1,80 @@
# Driver for I2C SSD1306 128x64 OLED LCD # Driver for SSD1306 OLED LCD
This driver is written for usage with the ESP8266 and FreeRTOS ([esp-open-rtos](https://github.com/SuperHouse/esp-open-rtos)). This driver is written for usage with the ESP8266 and FreeRTOS ([esp-open-rtos](https://github.com/SuperHouse/esp-open-rtos)).
### Usage ## Supported display sizes
Before using the SSD1306 LCD module, the function `i2c_init(SCL_PIN, SDA_PIN)` needs to be called to setup the I2C interface and then you must call ssd1306_init(). - 128x64
- 128x32
- 128x16
- 96x16
## Supported connection interfaces
Currently supported two of them: I2C and SPI4.
## Usage
If Reset pin is accesible in your display module, connect it to the RESET pin of ESP8266.
If you don't do this, display RAM may be glitchy after the power lost/restore.
### I2C protocol
Before using the SSD1306 LCD module the function `i2c_init(SCL_PIN, SDA_PIN)` needs to be
called to setup the I2C interface and then you must call `ssd1306_init()`.
#### Example #### Example
``` ```C
#define SCL_PIN GPIO_ID_PIN(0) #define SCL_PIN 5
#define SDA_PIN GPIO_ID_PIN(2) #define SDA_PIN 4
...
static const ssd1306_t device = {
.protocol = SSD1306_PROTO_I2C,
.width = 128,
.height = 64
};
... ...
i2c_init(SCL_PIN, SDA_PIN); i2c_init(SCL_PIN, SDA_PIN);
if (ssd1306_init()) { if (ssd1306_init(&device)) {
// An error occured, while performing SSD1306 init init (E.g device not found etc.) // An error occured, while performing SSD1306 init (E.g device not found etc.)
} }
// rest of the code // rest of the code
``` ```
### SPI4 protocol
This protocol MUCH faster than I2C but uses 2 additional GPIO pins (beside of HSPI CLK
and HSPI MOSI): Data/Command pin and Chip Select pin.
No additional function calls are required before `ssd1306_init()`.
#### Example
```C
#define CS_PIN 5
#define DC_PIN 4
...
static const ssd1306_t device = {
.protocol = SSD1306_PROTO_SPI4,
.cs_pin = CS_PIN,
.dc_pin = DC_PIN,
.width = 128,
.height = 64
};
...
if (ssd1306_init(&device)) {
// An error occured, while performing SSD1306 init
}
// rest of the code
```

View file

@ -3,7 +3,15 @@
# expected anyone using ssd1306 driver includes it as 'ssd1306/ssd1306.h' # expected anyone using ssd1306 driver includes it as 'ssd1306/ssd1306.h'
INC_DIRS += $(ssd1306_ROOT).. INC_DIRS += $(ssd1306_ROOT)..
# I2C support is on by default
SSD1306_I2C_SUPPORT ?= 1
# SPI4 support is on by default
SSD1306_SPI4_SUPPORT ?= 1
# args for passing into compile rule generation # args for passing into compile rule generation
ssd1306_SRC_DIR = $(ssd1306_ROOT) ssd1306_SRC_DIR = $(ssd1306_ROOT)
ssd1306_CFLAGS = -DSSD1306_I2C_SUPPORT=${SSD1306_I2C_SUPPORT} -DSSD1306_SPI4_SUPPORT=${SSD1306_SPI4_SUPPORT} $(CFLAGS)
$(eval $(call component_compile_rules,ssd1306)) $(eval $(call component_compile_rules,ssd1306))

12
extras/ssd1306/config.h Normal file
View file

@ -0,0 +1,12 @@
#ifndef _EXTRAS_SSD1306_CONFIG_H_
#define _EXTRAS_SSD1306_CONFIG_H_
#ifndef SSD1306_I2C_SUPPORT
#define SSD1306_I2C_SUPPORT 1
#endif
#ifndef SSD1306_SPI4_SUPPORT
#define SSD1306_SPI4_SUPPORT 1
#endif
#endif /* _EXTRAS_SSD1306_CONFIG_H_ */

View file

@ -1,14 +1,31 @@
/**
* SSD1306 OLED display driver for esp-open-rtos.
*
* Copyright (c) 2016 urx (https://github.com/urx),
* Ruslan V. Uss (https://github.com/UncleRus)
*
* MIT Licensed as described in the file LICENSE
*
* @todo Scrolling, fonts
*/
#include "ssd1306.h"
#include <stdio.h> #include <stdio.h>
#if (SSD1306_I2C_SUPPORT)
#include <i2c/i2c.h> #include <i2c/i2c.h>
#endif
#if (SSD1306_SPI4_SUPPORT)
#include <esp/spi.h>
#endif
#include <esp/gpio.h>
#include <FreeRTOS.h> #include <FreeRTOS.h>
#include <task.h> #include <task.h>
#include "ssd1306.h"
#define SPI_BUS 1
//#define SSD1306_DEBUG
/* SSD1306 commands */ /* SSD1306 commands */
#define SSD1306_SET_MEM_ADDR_MODE (0x20) #define SSD1306_SET_MEM_ADDR_MODE (0x20)
#define SSD1306_ADDR_MODE_HORIZ (0x0)
#define SSD1306_ADDR_MODE_VERT (0x1)
#define SSD1306_ADDR_MODE_PAGE (0x2)
#define SSD1306_SET_COL_ADDR (0x21) #define SSD1306_SET_COL_ADDR (0x21)
#define SSD1306_SET_PAGE_ADDR (0x22) #define SSD1306_SET_PAGE_ADDR (0x22)
@ -46,22 +63,26 @@
#define SSD1306_CHARGE_PUMP_DIS (0x10) #define SSD1306_CHARGE_PUMP_DIS (0x10)
#ifdef SSD1306_DEBUG #ifdef SSD1306_DEBUG
#define debug(fmt, ...) printf("%s" fmt "\n", "SSD1306", ## __VA_ARGS__); #define debug(fmt, ...) printf("%s: " fmt "\n", "SSD1306", ## __VA_ARGS__)
#else #else
#define debug(fmt, ...) #define debug(fmt, ...)
#endif #endif
/* Issue a command to SSD1306 device /* Issue a command to SSD1306 device
* format such follows: * I2C proto format:
* |S|Slave Address|W|ACK|0x00|Command|Ack|P| * |S|Slave Address|W|ACK|0x00|Command|Ack|P|
* *
* in case of two-bytes command here will be Data byte * in case of two-bytes command here will be Data byte
* right after command byte. * right after the command byte.
*/ */
int ssd1306_command(uint8_t cmd) int ssd1306_command(const ssd1306_t *dev, uint8_t cmd)
{ {
debug("Command: 0x%02x", cmd);
switch (dev->protocol) {
#if (SSD1306_I2C_SUPPORT)
case SSD1306_PROTO_I2C:
i2c_start(); i2c_start();
if (!i2c_write(SSD1306_I2C_ADDR << 1)) { if (!i2c_write(dev->addr << 1)) {
debug("Error while xmitting I2C slave address\n"); debug("Error while xmitting I2C slave address\n");
i2c_stop(); i2c_stop();
return -EIO; return -EIO;
@ -71,59 +92,104 @@ int ssd1306_command(uint8_t cmd)
i2c_stop(); i2c_stop();
return -EIO; return -EIO;
} }
if (!i2c_write(cmd)) { if (!i2c_write(cmd)) {
debug("Error while xmitting command: 0x%02X\n", cmd); debug("Error while xmitting command: 0x%02X\n", cmd);
i2c_stop(); i2c_stop();
return -EIO; return -EIO;
} }
i2c_stop(); i2c_stop();
break;
#endif
#if (SSD1306_SPI4_SUPPORT)
case SSD1306_PROTO_SPI4:
gpio_write(dev->dc_pin, false); // command mode
gpio_write(dev->cs_pin, false);
spi_transfer_8(SPI_BUS, cmd);
gpio_write(dev->cs_pin, true);
break;
#endif
default:
debug("Unsupported protocol");
return -EPROTONOSUPPORT;
}
return 0; return 0;
} }
/* Perform default init routine according /* Perform default init routine according
* to SSD1306 datasheet from adafruit.com */ * to SSD1306 datasheet from adafruit.com */
int ssd1306_init() int ssd1306_init(const ssd1306_t *dev)
{ {
if (!ssd1306_display_on(false) && uint8_t pin_cfg;
!ssd1306_set_osc_freq(0x80) && switch (dev->height) {
!ssd1306_set_mux_ratio(SSD1306_ROWS-1) && case 16:
!ssd1306_set_display_offset(0x0) && case 32:
!ssd1306_set_display_start_line(0x0) && pin_cfg = 0x02;
!ssd1306_set_charge_pump_enabled(true) && break;
!ssd1306_set_mem_addr_mode(SSD1306_ADDR_MODE_HORIZ) && case 64:
!ssd1306_set_segment_remapping_enabled(false) && pin_cfg = 0x12;
!ssd1306_set_scan_direction_fwd(true) && break;
!ssd1306_set_com_pin_hw_config(SSD1306_ALT_COM_PINS_CFG) && default:
!ssd1306_set_contrast(0x9f) && debug("Unsupported screen height");
!ssd1306_set_precharge_period(0xf1) && return -ENOTSUP;
!ssd1306_set_deseltct_lvl(0x40) && }
!ssd1306_set_whole_display_lighting(true) &&
!ssd1306_set_inversion(false) && switch (dev->protocol) {
!ssd1306_display_on(true)) { #if (SSD1306_I2C_SUPPORT)
case SSD1306_PROTO_I2C:
break;
#endif
#if (SSD1306_SPI4_SUPPORT)
case SSD1306_PROTO_SPI4:
gpio_enable(dev->cs_pin, GPIO_OUTPUT);
gpio_write(dev->cs_pin, true);
gpio_enable(dev->dc_pin, GPIO_OUTPUT);
spi_init(SPI_BUS, SPI_MODE0, SPI_FREQ_DIV_8M, true, SPI_LITTLE_ENDIAN, true);
break;
#endif
default:
debug("Unsupported protocol");
return -EPROTONOSUPPORT;
}
if (!ssd1306_display_on(dev, false) &&
!ssd1306_set_osc_freq(dev, 0x80) &&
!ssd1306_set_mux_ratio(dev, dev->height - 1) &&
!ssd1306_set_display_offset(dev, 0x0) &&
!ssd1306_set_display_start_line(dev, 0x0) &&
!ssd1306_set_charge_pump_enabled(dev, true) &&
!ssd1306_set_mem_addr_mode(dev, SSD1306_ADDR_MODE_HORIZONTAL) &&
!ssd1306_set_segment_remapping_enabled(dev, false) &&
!ssd1306_set_scan_direction_fwd(dev, true) &&
!ssd1306_set_com_pin_hw_config(dev, pin_cfg) &&
!ssd1306_set_contrast(dev, 0x9f) &&
!ssd1306_set_precharge_period(dev, 0xf1) &&
!ssd1306_set_deseltct_lvl(dev, 0x40) &&
!ssd1306_set_whole_display_lighting(dev, true) &&
!ssd1306_set_inversion(dev, false) &&
!ssd1306_display_on(dev, true)) {
return 0; return 0;
} }
return -EIO; return -EIO;
} }
/* int ssd1306_load_frame_buffer(const ssd1306_t *dev, uint8_t buf[])
* frame buffer of SSD1306 consists of 8 pages of 128 bits each
*
*/
int ssd1306_load_frame_buffer(uint8_t buf[], uint16_t len)
{ {
uint16_t i; uint16_t i;
uint8_t j; uint8_t j;
ssd1306_set_column_addr(0, 127); ssd1306_set_column_addr(dev, 0, dev->width - 1);
ssd1306_set_page_addr(0, 7); ssd1306_set_page_addr(dev, 0, dev->height / 8 - 1);
size_t len = dev->width * dev->height / 8;
switch (dev->protocol) {
#if (SSD1306_I2C_SUPPORT)
case SSD1306_PROTO_I2C:
for (i = 0; i < len; i++) { for (i = 0; i < len; i++) {
i2c_start(); i2c_start();
if (!i2c_write(SSD1306_I2C_ADDR << 1)) { if (!i2c_write(dev->addr << 1)) {
debug("Error while xmitting I2C slave address\n"); debug("Error while xmitting I2C slave address\n");
i2c_stop(); i2c_stop();
return -EIO; return -EIO;
@ -135,7 +201,7 @@ int ssd1306_load_frame_buffer(uint8_t buf[], uint16_t len)
} }
for (j = 0; j < 16; j++) { for (j = 0; j < 16; j++) {
if (!i2c_write(buf[i])) { if (!i2c_write(buf ? buf[i] : 0)) {
debug("Error while writing to GDDRAM\n"); debug("Error while writing to GDDRAM\n");
i2c_stop(); i2c_stop();
return -EIO; return -EIO;
@ -146,231 +212,195 @@ int ssd1306_load_frame_buffer(uint8_t buf[], uint16_t len)
i2c_stop(); i2c_stop();
taskYIELD(); taskYIELD();
} }
break;
return 0; #endif
#if (SSD1306_SPI4_SUPPORT)
case SSD1306_PROTO_SPI4:
gpio_write(dev->dc_pin, true); // data mode
gpio_write(dev->cs_pin, false);
if (buf)
spi_transfer(SPI_BUS, buf, NULL, len, SPI_8BIT);
else
for (i = 0; i < len; i ++) {
spi_transfer_8(SPI_BUS, 0);
} }
gpio_write(dev->cs_pin, true);
int ssd1306_clear_screen() break;
{ #endif
uint16_t i = 0; default:
uint8_t j = 0; debug("Unsupported protocol");
return -EPROTONOSUPPORT;
ssd1306_set_column_addr(0, 127);
ssd1306_set_page_addr(0, 7);
while (i < (SSD1306_ROWS*SSD1306_COLS/8)) {
i2c_start();
if (!i2c_write(SSD1306_I2C_ADDR << 1)) {
debug("Error while xmitting I2C slave address\n");
i2c_stop();
return -EIO;
}
if (!i2c_write(0x40)) {
debug("Error while xmitting transmission type\n");
i2c_stop();
return -EIO;
}
/* write 16 bytes of data and then give resources to another task */
while (j < 16) {
if (!i2c_write(0x0)) {
debug("Error while writing to GDDRAM\n");
i2c_stop();
return -EIO;
}
i++;
j++;
}
i--;
j = 0;
i2c_stop();
taskYIELD();
} }
return 0; return 0;
} }
int ssd1306_display_on(bool on) int ssd1306_display_on(const ssd1306_t *dev, bool on)
{ {
if (on) return ssd1306_command(dev, on ? SSD1306_SET_DISPLAY_ON : SSD1306_SET_DISPLAY_OFF);
return ssd1306_command(SSD1306_SET_DISPLAY_ON);
return ssd1306_command(SSD1306_SET_DISPLAY_OFF);
} }
int ssd1306_set_display_start_line(uint8_t start) int ssd1306_set_display_start_line(const ssd1306_t *dev, uint8_t start)
{ {
return ssd1306_command(SSD1306_SET_DISP_START_LINE | start); if (start > 63)
return -EINVAL;
return ssd1306_command(dev, SSD1306_SET_DISP_START_LINE | start);
} }
int ssd1306_set_display_offset(uint8_t offset) int ssd1306_set_display_offset(const ssd1306_t *dev, uint8_t offset)
{ {
int err = 0; if (offset > 63)
if ((err = ssd1306_command(SSD1306_SET_DISPLAY_OFFSET)))
return err;
return ssd1306_command(offset);
}
int ssd1306_set_charge_pump_enabled(bool enabled)
{
int err = 0;
if ((err = ssd1306_command(SSD1306_SET_CHARGE_PUMP)))
return err;
if (enabled)
return ssd1306_command(SSD1306_CHARGE_PUMP_EN);
return ssd1306_command(SSD1306_CHARGE_PUMP_DIS);
}
int ssd1306_set_mem_addr_mode(uint8_t mode)
{
if (mode >= 0x3)
return -EINVAL; return -EINVAL;
int err = 0; int err = 0;
if ((err = ssd1306_command(SSD1306_SET_MEM_ADDR_MODE))) if ((err = ssd1306_command(dev, SSD1306_SET_DISPLAY_OFFSET)))
return err; return err;
return ssd1306_command(mode); return ssd1306_command(dev, offset);
} }
int ssd1306_set_segment_remapping_enabled(bool on) int ssd1306_set_charge_pump_enabled(const ssd1306_t *dev, bool enabled)
{
if (on)
return ssd1306_command(SSD1306_SET_SEGMENT_REMAP1);
return ssd1306_command(SSD1306_SET_SEGMENT_REMAP0);
}
int ssd1306_set_scan_direction_fwd(bool fwd)
{
if (fwd)
return ssd1306_command(SSD1306_SET_SCAN_DIR_FWD);
return ssd1306_command(SSD1306_SET_SCAN_DIR_BWD);
}
int ssd1306_set_com_pin_hw_config(uint8_t config)
{ {
int err = 0; int err = 0;
if ((err = ssd1306_command(SSD1306_SET_COM_PINS_HW_CFG))) if ((err = ssd1306_command(dev, SSD1306_SET_CHARGE_PUMP)))
return err; return err;
return ssd1306_command(config & SSD1306_COM_PINS_HW_CFG_MASK); return ssd1306_command(dev, enabled ? SSD1306_CHARGE_PUMP_EN : SSD1306_CHARGE_PUMP_DIS);
} }
int ssd1306_set_contrast(uint8_t contrast) int ssd1306_set_mem_addr_mode(const ssd1306_t *dev, ssd1306_mem_addr_mode_t mode)
{ {
int err = 0; int err = 0;
if ((err = ssd1306_command(SSD1306_SET_CONTRAST))) if ((err = ssd1306_command(dev, SSD1306_SET_MEM_ADDR_MODE)))
return err; return err;
return ssd1306_command(contrast); return ssd1306_command(dev, mode);
} }
int ssd1306_set_inversion(bool on) int ssd1306_set_segment_remapping_enabled(const ssd1306_t *dev, bool on)
{ {
if (on) return ssd1306_command(dev, on ? SSD1306_SET_SEGMENT_REMAP1 : SSD1306_SET_SEGMENT_REMAP0);
return ssd1306_command(SSD1306_SET_INVERSION_ON);
return ssd1306_command(SSD1306_SET_INVERSION_OFF);
} }
int ssd1306_set_osc_freq(uint8_t osc_freq) int ssd1306_set_scan_direction_fwd(const ssd1306_t *dev, bool fwd)
{
return ssd1306_command(dev, fwd ? SSD1306_SET_SCAN_DIR_FWD : SSD1306_SET_SCAN_DIR_BWD);
}
int ssd1306_set_com_pin_hw_config(const ssd1306_t *dev, uint8_t config)
{ {
int err = 0; int err = 0;
if ((err = ssd1306_command(SSD1306_SET_OSC_FREQ))) if ((err = ssd1306_command(dev, SSD1306_SET_COM_PINS_HW_CFG)))
return err; return err;
return ssd1306_command(osc_freq); return ssd1306_command(dev, config & SSD1306_COM_PINS_HW_CFG_MASK);
} }
int ssd1306_set_mux_ratio(uint8_t ratio) int ssd1306_set_contrast(const ssd1306_t *dev, uint8_t contrast)
{ {
if (ratio < 15) int err = 0;
if ((err = ssd1306_command(dev, SSD1306_SET_CONTRAST)))
return err;
return ssd1306_command(dev, contrast);
}
int ssd1306_set_inversion(const ssd1306_t *dev, bool on)
{
return ssd1306_command(dev, on ? SSD1306_SET_INVERSION_ON : SSD1306_SET_INVERSION_OFF);
}
int ssd1306_set_osc_freq(const ssd1306_t *dev, uint8_t osc_freq)
{
int err = 0;
if ((err = ssd1306_command(dev, SSD1306_SET_OSC_FREQ)))
return err;
return ssd1306_command(dev, osc_freq);
}
int ssd1306_set_mux_ratio(const ssd1306_t *dev, uint8_t ratio)
{
if (ratio < 15 || ratio > 63)
return -EINVAL; return -EINVAL;
int err = 0; int err = 0;
if ((err = ssd1306_command(SSD1306_SET_MUX_RATIO))) if ((err = ssd1306_command(dev, SSD1306_SET_MUX_RATIO)))
return err; return err;
return ssd1306_command(ratio); return ssd1306_command(dev, ratio);
} }
int ssd1306_set_column_addr(uint8_t start, uint8_t stop) int ssd1306_set_column_addr(const ssd1306_t *dev, uint8_t start, uint8_t stop)
{ {
int err = 0; int err = 0;
if ((err = ssd1306_command(SSD1306_SET_COL_ADDR))) if ((err = ssd1306_command(dev, SSD1306_SET_COL_ADDR)))
return err; return err;
if ((err = ssd1306_command(start))) if ((err = ssd1306_command(dev, start)))
return err; return err;
return ssd1306_command(stop); return ssd1306_command(dev, stop);
} }
int ssd1306_set_page_addr(uint8_t start, uint8_t stop) int ssd1306_set_page_addr(const ssd1306_t *dev, uint8_t start, uint8_t stop)
{ {
int err = 0; int err = 0;
if ((err = ssd1306_command(SSD1306_SET_PAGE_ADDR))) if ((err = ssd1306_command(dev, SSD1306_SET_PAGE_ADDR)))
return err; return err;
if ((err = ssd1306_command(start))) if ((err = ssd1306_command(dev, start)))
return err; return err;
return ssd1306_command(stop); return ssd1306_command(dev, stop);
} }
int ssd1306_set_precharge_period(uint8_t prchrg) int ssd1306_set_precharge_period(const ssd1306_t *dev, uint8_t prchrg)
{ {
int err = 0; int err = 0;
if ((err = ssd1306_command(SSD1306_SET_PRE_CHRG_PER))) if ((err = ssd1306_command(dev, SSD1306_SET_PRE_CHRG_PER)))
return err; return err;
return ssd1306_command(prchrg); return ssd1306_command(dev, prchrg);
} }
int ssd1306_set_deseltct_lvl(uint8_t lvl) int ssd1306_set_deseltct_lvl(const ssd1306_t *dev, uint8_t lvl)
{ {
int err = 0; int err = 0;
if ((err = ssd1306_command(SSD1306_SET_DESEL_LVL))) if ((err = ssd1306_command(dev, SSD1306_SET_DESEL_LVL)))
return err; return err;
return ssd1306_command(lvl); return ssd1306_command(dev, lvl);
} }
int ssd1306_set_whole_display_lighting(bool light) int ssd1306_set_whole_display_lighting(const ssd1306_t *dev, bool light)
{ {
if (light) return ssd1306_command(dev, light ? SSD1306_SET_ENTIRE_DISP_ON : SSD1306_SET_ENTIRE_DISP_OFF);
return ssd1306_command(SSD1306_SET_ENTIRE_DISP_ON);
return ssd1306_command(SSD1306_SET_ENTIRE_DISP_OFF);
} }
/* one byte of xbm - 8 dots in line of picture source /* one byte of xbm - 8 dots in line of picture source
* one byte of fb - 8 rows for 1 column of screen * one byte of fb - 8 rows for 1 column of screen
*/ */
int ssd1306_load_xbm(uint8_t *xbm, uint8_t *fb) int ssd1306_load_xbm(const ssd1306_t *dev, uint8_t *xbm, uint8_t *fb)
{ {
uint8_t bit = 0; uint8_t bit = 0;
int row = 0; int row = 0;
int column = 0; int column = 0;
for (row = 0; row < SSD1306_ROWS; row ++) { for (row = 0; row < dev->height; row ++) {
for (column = 0; column < SSD1306_COLS/8; column++) { for (column = 0; column < dev->width / 8; column++) {
uint16_t xbm_offset = row * 16 + column; uint16_t xbm_offset = row * 16 + column;
for (bit = 0; bit < 8; bit++) { for (bit = 0; bit < 8; bit++) {
if (*(xbm + xbm_offset) & 1 << bit) { if (*(xbm + xbm_offset) & 1 << bit) {
*(fb + SSD1306_COLS*(row/8)+column*8+bit) |= 1 << row%8; *(fb + dev->width * (row / 8) + column * 8 + bit) |= 1 << row % 8;
} }
} }
} }
} }
return ssd1306_load_frame_buffer(fb, SSD1306_ROWS*SSD1306_COLS/8); return ssd1306_load_frame_buffer(dev, fb);
} }

View file

@ -1,3 +1,11 @@
/**
* SSD1306 OLED display driver for esp-open-rtos.
*
* Copyright (c) 2016 urx (https://github.com/urx),
* Ruslan V. Uss (https://github.com/UncleRus)
*
* MIT Licensed as described in the file LICENSE
*/
#ifndef _SSD1306__H_ #ifndef _SSD1306__H_
#define _SSD1306__H_ #define _SSD1306__H_
@ -5,46 +13,271 @@
#include <stdbool.h> #include <stdbool.h>
#include <errno.h> #include <errno.h>
#include "config.h"
// shifted // shifted
#define SSD1306_I2C_ADDR (0x3C) #if (SSD1306_I2C_SUPPORT)
#define SSD1306_I2C_ADDR_0 (0x3C)
#define SSD1306_I2C_ADDR_1 (0x3D)
#endif
#define SSD1306_ROWS (64) #ifdef __cplusplus
#define SSD1306_COLS (128) extern "C"
{
#endif
/* Issue single command on SSD1306 */ /**
int ssd1306_command(uint8_t cmd); * I/O protocols
/* Default init for SSD1306 */
int ssd1306_init();
/* Load picture in xbm format into SSD1306 RAM
* xbm - pointer to xbm picture array
* fb - pointer fo local buffer for storing converted xbm image
*/ */
int ssd1306_load_xbm(uint8_t *xbm, uint8_t *fb); typedef enum
{
SSD1306_PROTO_I2C = 0, //!< I2C
SSD1306_PROTO_SPI4, //!< SPI 8 bits + D/C pin
SSD1306_PROTO_SPI3 //!< SPI 9 bits, currently not supported
} ssd1306_protocol_t;
/* Load local framebuffer into SSD1306 RAM */ /**
int ssd1306_load_frame_buffer(uint8_t buf[], uint16_t len); * Device descriptor
*/
typedef struct
{
ssd1306_protocol_t protocol;
#if (SSD1306_I2C_SUPPORT)
uint8_t addr; //!< I2C address, used by SSD1306_PROTO_I2C
#endif
#if (SSD1306_SPI4_SUPPORT)
uint8_t cs_pin; //!< Chip Select GPIO pin, used by SSD1306_PROTO_SPI3, SSD1306_PROTO_SPI4
uint8_t dc_pin; //!< Data/Command GPIO pin, used by SSD1306_PROTO_SPI4
#endif
uint8_t width; //!< Screen width, currently supported 128px, 96px
uint8_t height; //!< Screen height, currently supported 16px, 32px, 64px
} ssd1306_t;
/* Clears SSD1306 ram */ /**
int ssd1306_clear_screen(); * Addressing mode, see datasheet
*/
typedef enum
{
SSD1306_ADDR_MODE_HORIZONTAL = 0,
SSD1306_ADDR_MODE_VERTICAL,
SSD1306_ADDR_MODE_PAGE
} ssd1306_mem_addr_mode_t;
int ssd1306_display_on(bool on); /**
int ssd1306_set_display_start_line(uint8_t start); * Issue a single command on SSD1306.
int ssd1306_set_display_offset(uint8_t offset); * @param dev Pointer to device descriptor
int ssd1306_set_charge_pump_enabled(bool enabled); * @param cmd Command
int ssd1306_set_mem_addr_mode(uint8_t mode); * @return Non-zero if error occured
int ssd1306_set_segment_remapping_enabled(bool on); */
int ssd1306_set_scan_direction_fwd(bool fwd); int ssd1306_command(const ssd1306_t *dev, uint8_t cmd);
int ssd1306_set_com_pin_hw_config(uint8_t config);
int ssd1306_set_contrast(uint8_t contrast); /**
int ssd1306_set_inversion(bool on); * Default init for SSD1306
int ssd1306_set_osc_freq(uint8_t osc_freq); * @param dev Pointer to device descriptor
int ssd1306_set_mux_ratio(uint8_t ratio); * @return Non-zero if error occured
int ssd1306_set_column_addr(uint8_t start, uint8_t stop); */
int ssd1306_set_page_addr(uint8_t start, uint8_t stop); int ssd1306_init(const ssd1306_t *dev);
int ssd1306_set_precharge_period(uint8_t prchrg);
int ssd1306_set_deseltct_lvl(uint8_t lvl); /**
int ssd1306_set_whole_display_lighting(bool light); * Load picture in xbm format into the SSD1306 RAM.
* @param dev Pointer to device descriptor
* @param xbm Pointer to xbm picture array
* @param fb Pointer to local buffer for storing converted xbm image
* @return Non-zero if error occured
*/
int ssd1306_load_xbm(const ssd1306_t *dev, uint8_t *xbm, uint8_t *fb);
/**
* Load local framebuffer into the SSD1306 RAM.
* @param dev Pointer to device descriptor
* @param buf Pointer to framebuffer or NULL for clear RAM. Framebuffer size = width * height / 8
* @return Non-zero if error occured
*/
int ssd1306_load_frame_buffer(const ssd1306_t *dev, uint8_t buf[]);
/**
* Clear SSD1306 RAM.
* @param dev Pointer to device descriptor
* @return Non-zero if error occured
*/
inline int ssd1306_clear_screen(const ssd1306_t *dev)
{
return ssd1306_load_frame_buffer(dev, NULL);
}
/**
* Turn display on or off.
* @param dev Pointer to device descriptor
* @param on Turn on if true
* @return Non-zero if error occured
*/
int ssd1306_display_on(const ssd1306_t *dev, bool on);
/**
* Set the Display Start Line register to determine starting address of
* display RAM, by selecting a value from 0 to 63. With value equal to 0,
* RAM row 0 is mapped to COM0. With value equal to 1, RAM row 1 is mapped
* to COM0 and so on.
* @param dev Pointer to device descriptor
* @param start Start line, 0..63
* @return Non-zero if error occured
*/
int ssd1306_set_display_start_line(const ssd1306_t *dev, uint8_t start);
/**
* Set display offset (see datasheet).
* @param dev Pointer to device descriptor
* @param offset Offset, 0..63
* @return Non-zero if error occured
*/
int ssd1306_set_display_offset(const ssd1306_t *dev, uint8_t offset);
/**
* Enable or disable the charge pump. See application note in datasheet.
* @param dev Pointer to device descriptor
* @param enabled Enable charge pump if true
* @return Non-zero if error occured
*/
int ssd1306_set_charge_pump_enabled(const ssd1306_t *dev, bool enabled);
/**
* Set memory addressing mode. See datasheet.
* @param dev Pointer to device descriptor
* @param mode Addressing mode
* @return Non-zero if error occured
*/
int ssd1306_set_mem_addr_mode(const ssd1306_t *dev, ssd1306_mem_addr_mode_t mode);
/**
* Change the mapping between the display data column address and the
* segment driver. See datasheet.
* @param dev Pointer to device descriptor
* @param on Enable segment remapping if true
* @return Non-zero if error occured
*/
int ssd1306_set_segment_remapping_enabled(const ssd1306_t *dev, bool on);
/**
* Set the scan direction of the COM output, allowing layout flexibility
* in the OLED module design. Additionally, the display will show once
* this command is issued. For example, if this command is sent during
* normal display then the graphic display will be vertically flipped
* immediately.
* @param dev Pointer to device descriptor
* @param fwd Forward direction if true, backward otherwise
* @return Non-zero if error occured
*/
int ssd1306_set_scan_direction_fwd(const ssd1306_t *dev, bool fwd);
/**
* Set the COM signals pin configuration to match the OLED panel
* hardware layout. See datasheet.
* @param dev Pointer to device descriptor
* @param config Sequential COM pin configuration
* @return Non-zero if error occured
*/
int ssd1306_set_com_pin_hw_config(const ssd1306_t *dev, uint8_t config);
/**
* Set the display contrast.
* @param dev Pointer to device descriptor
* @param contrast Contrast increases as the value increases.
* @return Non-zero if error occured
*/
int ssd1306_set_contrast(const ssd1306_t *dev, uint8_t contrast);
/**
* Set the display to be either normal or inverse. In normal display
* a RAM data of 1 indicates an ON pixel while in inverse display a
* RAM data of 0 indicates an ON pixel.
* @param dev Pointer to device descriptor
* @param on Inverse display if true
* @return Non-zero if error occured
*/
int ssd1306_set_inversion(const ssd1306_t *dev, bool on);
/**
* Set the divide ratio of display clock and oscillator frequency.
* See datasheet.
* @param dev Pointer to device descriptor
* @param osc_freq Lower nibble - DCLK divide ratio, high
* nibble - oscillator frequency
* @return Non-zero if error occured
*/
int ssd1306_set_osc_freq(const ssd1306_t *dev, uint8_t osc_freq);
/**
* Switch the default 63 multiplex mode to any multiplex ratio,
* ranging from 16 to 63. The output pads COM0~COM63 will be switched
* to the corresponding COM signal.
* @param dev Pointer to device descriptor
* @param ratio Multiplex ratio, 16..63
* @return Non-zero if error occured
*/
int ssd1306_set_mux_ratio(const ssd1306_t *dev, uint8_t ratio);
/**
* Specify column start address and end address of the display data RAM.
* This command also sets the column address pointer to column start
* address. This pointer is used to define the current read/write column
* address in graphic display data RAM. If horizontal address increment mode
* is enabled by ssd1306_set_mem_addr_mode(), after finishing read/write
* one column data, it is incremented automatically to the next column
* address. Whenever the column address pointer finishes accessing the
* end column address, it is reset back to start column address and the
* row address is incremented to the next row.
* @param dev Pointer to device descriptor
* @param start Start RAM address
* @param stop End RAM address
* @return Non-zero if error occured
*/
int ssd1306_set_column_addr(const ssd1306_t *dev, uint8_t start, uint8_t stop);
/**
* Specify page start address and end address of the display data RAM.
* This command also sets the page address pointer to page start address.
* This pointer is used to define the current read/write page address in
* graphic display data RAM. If vertical address increment mode is enabled by
* ssd1306_set_mem_addr_mode(), after finishing read/write one page data,
* it is incremented automatically to the next page address. Whenever the page
* address pointer finishes accessing the end page address, it is reset back
* to start page address.
* @param dev Pointer to device descriptor
* @param start Start RAM address
* @param stop End RAM address
* @return Non-zero if error occured
*/
int ssd1306_set_page_addr(const ssd1306_t *dev, uint8_t start, uint8_t stop);
/**
* Set the duration of the pre-charge period. The interval is counted in
* number of DCLK, where RESET equals 2 DCLKs.
* @param dev Pointer to device descriptor
* @param prchrg Pre-charge period
* @return Non-zero if error occured
*/
int ssd1306_set_precharge_period(const ssd1306_t *dev, uint8_t prchrg);
/**
* Adjust the VCOMH regulator output. See datasheet.
* @param dev Pointer to device descriptor
* @param lvl Deselect level
* @return Non-zero if error occured
*/
int ssd1306_set_deseltct_lvl(const ssd1306_t *dev, uint8_t lvl);
/**
* Force the entire display to be ON, regardless of the contents of
* the display data RAM.
* @param dev Pointer to device descriptor
* @param light Force the entire display to be ON if true
* @return Non-zero if error occured
*/
int ssd1306_set_whole_display_lighting(const ssd1306_t *dev, bool light);
#ifdef __cplusplus
extern "C"
}
#endif
#endif // _SSD1306__H_ #endif // _SSD1306__H_