diff --git a/examples/ssd1306_i2c/Makefile b/examples/ssd1306_example/Makefile similarity index 100% rename from examples/ssd1306_i2c/Makefile rename to examples/ssd1306_example/Makefile diff --git a/examples/ssd1306_example/README.md b/examples/ssd1306_example/README.md new file mode 100644 index 0000000..9ffd1d5 --- /dev/null +++ b/examples/ssd1306_example/README.md @@ -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. diff --git a/examples/ssd1306_i2c/image.xbm b/examples/ssd1306_example/image.xbm similarity index 100% rename from examples/ssd1306_i2c/image.xbm rename to examples/ssd1306_example/image.xbm diff --git a/examples/ssd1306_example/main.c b/examples/ssd1306_example/main.c new file mode 100644 index 0000000..0238408 --- /dev/null +++ b/examples/ssd1306_example/main.c @@ -0,0 +1,96 @@ +#include +#include +#include +#include +#include +#include +#include + +/* Remove this line if your display connected by SPI */ +#define I2C_CONNECTION + +#ifdef I2C_CONNECTION + #include +#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); +} diff --git a/examples/ssd1306_i2c/README.md b/examples/ssd1306_i2c/README.md deleted file mode 100644 index 9c75bb3..0000000 --- a/examples/ssd1306_i2c/README.md +++ /dev/null @@ -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. diff --git a/examples/ssd1306_i2c/ssd1306_i2c.c b/examples/ssd1306_i2c/ssd1306_i2c.c deleted file mode 100644 index 7dadfb4..0000000 --- a/examples/ssd1306_i2c/ssd1306_i2c.c +++ /dev/null @@ -1,65 +0,0 @@ -#include "espressif/esp_common.h" -#include "esp/uart.h" - -#include "FreeRTOS.h" -#include "task.h" -#include "queue.h" - -#include - -#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); -} diff --git a/extras/ssd1306/README.md b/extras/ssd1306/README.md index cb283b9..f3a06e9 100644 --- a/extras/ssd1306/README.md +++ b/extras/ssd1306/README.md @@ -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)). -### 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 -``` -#define SCL_PIN GPIO_ID_PIN(0) -#define SDA_PIN GPIO_ID_PIN(2) +```C +#define SCL_PIN 5 +#define SDA_PIN 4 +... + +static const ssd1306_t device = { + .protocol = SSD1306_PROTO_I2C, + .width = 128, + .height = 64 +}; + ... i2c_init(SCL_PIN, SDA_PIN); -if (ssd1306_init()) { -// An error occured, while performing SSD1306 init init (E.g device not found etc.) +if (ssd1306_init(&device)) { +// An error occured, while performing SSD1306 init (E.g device not found etc.) } // 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 +``` diff --git a/extras/ssd1306/component.mk b/extras/ssd1306/component.mk index 7f93338..f12a292 100644 --- a/extras/ssd1306/component.mk +++ b/extras/ssd1306/component.mk @@ -3,7 +3,15 @@ # expected anyone using ssd1306 driver includes it as 'ssd1306/ssd1306.h' 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 -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)) diff --git a/extras/ssd1306/config.h b/extras/ssd1306/config.h new file mode 100644 index 0000000..bd622c3 --- /dev/null +++ b/extras/ssd1306/config.h @@ -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_ */ diff --git a/extras/ssd1306/ssd1306.c b/extras/ssd1306/ssd1306.c index 85f641f..06370a8 100644 --- a/extras/ssd1306/ssd1306.c +++ b/extras/ssd1306/ssd1306.c @@ -1,376 +1,406 @@ +/** + * 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 -#include +#if (SSD1306_I2C_SUPPORT) + #include +#endif +#if (SSD1306_SPI4_SUPPORT) + #include +#endif +#include #include #include -#include "ssd1306.h" + +#define SPI_BUS 1 + +//#define SSD1306_DEBUG /* SSD1306 commands */ -#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_MEM_ADDR_MODE (0x20) -#define SSD1306_SET_COL_ADDR (0x21) -#define SSD1306_SET_PAGE_ADDR (0x22) -#define SSD1306_SET_DISP_START_LINE (0x40) -#define SSD1306_SET_CONTRAST (0x81) -#define SSD1306_SET_SEGMENT_REMAP0 (0xA0) -#define SSD1306_SET_SEGMENT_REMAP1 (0xA1) -#define SSD1306_SET_ENTIRE_DISP_ON (0xA5) -#define SSD1306_SET_ENTIRE_DISP_OFF (0xA4) -#define SSD1306_SET_INVERSION_OFF (0xA6) -#define SSD1306_SET_INVERSION_ON (0xA7) +#define SSD1306_SET_COL_ADDR (0x21) +#define SSD1306_SET_PAGE_ADDR (0x22) +#define SSD1306_SET_DISP_START_LINE (0x40) +#define SSD1306_SET_CONTRAST (0x81) +#define SSD1306_SET_SEGMENT_REMAP0 (0xA0) +#define SSD1306_SET_SEGMENT_REMAP1 (0xA1) +#define SSD1306_SET_ENTIRE_DISP_ON (0xA5) +#define SSD1306_SET_ENTIRE_DISP_OFF (0xA4) +#define SSD1306_SET_INVERSION_OFF (0xA6) +#define SSD1306_SET_INVERSION_ON (0xA7) -#define SSD1306_SET_MUX_RATIO (0xA8) -#define SSD1306_MUX_RATIO_MASK (0x3F) -#define SSD1306_SET_DISPLAY_OFF (0xAE) -#define SSD1306_SET_DISPLAY_ON (0xAF) -#define SSD1306_SET_SCAN_DIR_FWD (0xC0) -#define SSD1306_SET_SCAN_DIR_BWD (0xC8) -#define SSD1306_SET_DISPLAY_OFFSET (0xD3) -#define SSD1306_SET_OSC_FREQ (0xD5) -#define SSD1306_SET_PRE_CHRG_PER (0xD9) +#define SSD1306_SET_MUX_RATIO (0xA8) +#define SSD1306_MUX_RATIO_MASK (0x3F) +#define SSD1306_SET_DISPLAY_OFF (0xAE) +#define SSD1306_SET_DISPLAY_ON (0xAF) +#define SSD1306_SET_SCAN_DIR_FWD (0xC0) +#define SSD1306_SET_SCAN_DIR_BWD (0xC8) +#define SSD1306_SET_DISPLAY_OFFSET (0xD3) +#define SSD1306_SET_OSC_FREQ (0xD5) +#define SSD1306_SET_PRE_CHRG_PER (0xD9) -#define SSD1306_SET_COM_PINS_HW_CFG (0xDA) -#define SSD1306_COM_PINS_HW_CFG_MASK (0x32) -#define SSD1306_SEQ_COM_PINS_CFG (0x02) -#define SSD1306_ALT_COM_PINS_CFG (0x12) -#define SSD1306_COM_LR_REMAP_OFF (0x02) -#define SSD1306_COM_LR_REMAP_ON (0x22) +#define SSD1306_SET_COM_PINS_HW_CFG (0xDA) +#define SSD1306_COM_PINS_HW_CFG_MASK (0x32) +#define SSD1306_SEQ_COM_PINS_CFG (0x02) +#define SSD1306_ALT_COM_PINS_CFG (0x12) +#define SSD1306_COM_LR_REMAP_OFF (0x02) +#define SSD1306_COM_LR_REMAP_ON (0x22) -#define SSD1306_SET_DESEL_LVL (0xDB) -#define SSD1306_SET_NOP (0xE3) +#define SSD1306_SET_DESEL_LVL (0xDB) +#define SSD1306_SET_NOP (0xE3) -#define SSD1306_SET_CHARGE_PUMP (0x8D) -#define SSD1306_CHARGE_PUMP_EN (0x14) -#define SSD1306_CHARGE_PUMP_DIS (0x10) +#define SSD1306_SET_CHARGE_PUMP (0x8D) +#define SSD1306_CHARGE_PUMP_EN (0x14) +#define SSD1306_CHARGE_PUMP_DIS (0x10) #ifdef SSD1306_DEBUG -#define debug(fmt, ...) printf("%s" fmt "\n", "SSD1306", ## __VA_ARGS__); +#define debug(fmt, ...) printf("%s: " fmt "\n", "SSD1306", ## __VA_ARGS__) #else #define debug(fmt, ...) #endif /* Issue a command to SSD1306 device - * format such follows: + * I2C proto format: * |S|Slave Address|W|ACK|0x00|Command|Ack|P| * * 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) { - i2c_start(); - if (!i2c_write(SSD1306_I2C_ADDR << 1)) { - debug("Error while xmitting I2C slave address\n"); - i2c_stop(); - return -EIO; + debug("Command: 0x%02x", cmd); + switch (dev->protocol) { +#if (SSD1306_I2C_SUPPORT) + case SSD1306_PROTO_I2C: + i2c_start(); + if (!i2c_write(dev->addr << 1)) { + debug("Error while xmitting I2C slave address\n"); + i2c_stop(); + return -EIO; + } + if (!i2c_write(0x00)) { + debug("Error while xmitting transmission type\n"); + i2c_stop(); + return -EIO; + } + if (!i2c_write(cmd)) { + debug("Error while xmitting command: 0x%02X\n", cmd); + i2c_stop(); + return -EIO; + } + 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; } - if (!i2c_write(0x00)) { - debug("Error while xmitting transmission type\n"); - i2c_stop(); - return -EIO; - } - - if (!i2c_write(cmd)) { - debug("Error while xmitting command: 0x%02X\n", cmd); - i2c_stop(); - return -EIO; - } - - i2c_stop(); return 0; } /* Perform default init routine according * to SSD1306 datasheet from adafruit.com */ -int ssd1306_init() +int ssd1306_init(const ssd1306_t *dev) { - if (!ssd1306_display_on(false) && - !ssd1306_set_osc_freq(0x80) && - !ssd1306_set_mux_ratio(SSD1306_ROWS-1) && - !ssd1306_set_display_offset(0x0) && - !ssd1306_set_display_start_line(0x0) && - !ssd1306_set_charge_pump_enabled(true) && - !ssd1306_set_mem_addr_mode(SSD1306_ADDR_MODE_HORIZ) && - !ssd1306_set_segment_remapping_enabled(false) && - !ssd1306_set_scan_direction_fwd(true) && - !ssd1306_set_com_pin_hw_config(SSD1306_ALT_COM_PINS_CFG) && - !ssd1306_set_contrast(0x9f) && - !ssd1306_set_precharge_period(0xf1) && - !ssd1306_set_deseltct_lvl(0x40) && - !ssd1306_set_whole_display_lighting(true) && - !ssd1306_set_inversion(false) && - !ssd1306_display_on(true)) { + uint8_t pin_cfg; + switch (dev->height) { + case 16: + case 32: + pin_cfg = 0x02; + break; + case 64: + pin_cfg = 0x12; + break; + default: + debug("Unsupported screen height"); + return -ENOTSUP; + } + + switch (dev->protocol) { +#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 -EIO; } -/* - * frame buffer of SSD1306 consists of 8 pages of 128 bits each - * -*/ -int ssd1306_load_frame_buffer(uint8_t buf[], uint16_t len) +int ssd1306_load_frame_buffer(const ssd1306_t *dev, uint8_t buf[]) { uint16_t i; uint8_t j; - ssd1306_set_column_addr(0, 127); - ssd1306_set_page_addr(0, 7); + ssd1306_set_column_addr(dev, 0, dev->width - 1); + ssd1306_set_page_addr(dev, 0, dev->height / 8 - 1); - for (i=0; iwidth * dev->height / 8; - for (j=0;j<16;j++) { - if (!i2c_write(buf[i])) { - debug("Error while writing to GDDRAM\n"); + switch (dev->protocol) { +#if (SSD1306_I2C_SUPPORT) + case SSD1306_PROTO_I2C: + for (i = 0; i < len; i++) { + i2c_start(); + if (!i2c_write(dev->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; + } + + for (j = 0; j < 16; j++) { + if (!i2c_write(buf ? buf[i] : 0)) { + debug("Error while writing to GDDRAM\n"); + i2c_stop(); + return -EIO; + } + i++; + } + i--; i2c_stop(); - return -EIO; + taskYIELD(); } - i++; - } - i--; - i2c_stop(); - taskYIELD(); + break; +#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); + break; +#endif + default: + debug("Unsupported protocol"); + return -EPROTONOSUPPORT; } return 0; } -int ssd1306_clear_screen() +int ssd1306_display_on(const ssd1306_t *dev, bool on) { - uint16_t i = 0; - uint8_t j = 0; - - 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 ssd1306_command(dev, on ? SSD1306_SET_DISPLAY_ON : SSD1306_SET_DISPLAY_OFF); } -int ssd1306_display_on(bool on) +int ssd1306_set_display_start_line(const ssd1306_t *dev, uint8_t start) { - if (on) - return ssd1306_command(SSD1306_SET_DISPLAY_ON); + if (start > 63) + return -EINVAL; - return ssd1306_command(SSD1306_SET_DISPLAY_OFF); + return ssd1306_command(dev, SSD1306_SET_DISP_START_LINE | start); } -int ssd1306_set_display_start_line(uint8_t start) +int ssd1306_set_display_offset(const ssd1306_t *dev, uint8_t offset) { - return ssd1306_command(SSD1306_SET_DISP_START_LINE | start); -} - -int ssd1306_set_display_offset(uint8_t offset) -{ - int err = 0; - 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) + if (offset > 63) return -EINVAL; int err = 0; - if ((err = ssd1306_command(SSD1306_SET_MEM_ADDR_MODE))) + if ((err = ssd1306_command(dev, SSD1306_SET_DISPLAY_OFFSET))) return err; - return ssd1306_command(mode); + return ssd1306_command(dev, offset); } -int ssd1306_set_segment_remapping_enabled(bool on) -{ - 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 ssd1306_set_charge_pump_enabled(const ssd1306_t *dev, bool enabled) { 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 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; - if ((err = ssd1306_command(SSD1306_SET_CONTRAST))) + if ((err = ssd1306_command(dev, SSD1306_SET_MEM_ADDR_MODE))) 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(SSD1306_SET_INVERSION_ON); - - return ssd1306_command(SSD1306_SET_INVERSION_OFF); + return ssd1306_command(dev, on ? SSD1306_SET_SEGMENT_REMAP1 : SSD1306_SET_SEGMENT_REMAP0); } -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; - if ((err = ssd1306_command(SSD1306_SET_OSC_FREQ))) + if ((err = ssd1306_command(dev, SSD1306_SET_COM_PINS_HW_CFG))) 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; int err = 0; - if ((err = ssd1306_command(SSD1306_SET_MUX_RATIO))) + if ((err = ssd1306_command(dev, SSD1306_SET_MUX_RATIO))) 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; - if ((err = ssd1306_command(SSD1306_SET_COL_ADDR))) + if ((err = ssd1306_command(dev, SSD1306_SET_COL_ADDR))) return err; - if ((err = ssd1306_command(start))) + if ((err = ssd1306_command(dev, start))) 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; - if ((err = ssd1306_command(SSD1306_SET_PAGE_ADDR))) + if ((err = ssd1306_command(dev, SSD1306_SET_PAGE_ADDR))) return err; - if ((err = ssd1306_command(start))) + if ((err = ssd1306_command(dev, start))) 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; - if ((err = ssd1306_command(SSD1306_SET_PRE_CHRG_PER))) + if ((err = ssd1306_command(dev, SSD1306_SET_PRE_CHRG_PER))) 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; - if ((err = ssd1306_command(SSD1306_SET_DESEL_LVL))) + if ((err = ssd1306_command(dev, SSD1306_SET_DESEL_LVL))) 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(SSD1306_SET_ENTIRE_DISP_ON); - - return ssd1306_command(SSD1306_SET_ENTIRE_DISP_OFF); + return ssd1306_command(dev, light ? SSD1306_SET_ENTIRE_DISP_ON : SSD1306_SET_ENTIRE_DISP_OFF); } /* one byte of xbm - 8 dots in line of picture source * 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; int row = 0; int column = 0; - for (row = 0; row < SSD1306_ROWS; row ++) { - for (column = 0; column < SSD1306_COLS/8; column++) { + for (row = 0; row < dev->height; row ++) { + for (column = 0; column < dev->width / 8; column++) { uint16_t xbm_offset = row * 16 + column; for (bit = 0; bit < 8; 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); } diff --git a/extras/ssd1306/ssd1306.h b/extras/ssd1306/ssd1306.h index 933ceae..b4309f9 100644 --- a/extras/ssd1306/ssd1306.h +++ b/extras/ssd1306/ssd1306.h @@ -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_ #define _SSD1306__H_ @@ -5,46 +13,271 @@ #include #include +#include "config.h" + // 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) -#define SSD1306_COLS (128) +#ifdef __cplusplus +extern "C" +{ +#endif -/* Issue single command on SSD1306 */ -int ssd1306_command(uint8_t cmd); - -/* 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 +/** + * I/O protocols */ -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); -int ssd1306_set_display_offset(uint8_t offset); -int ssd1306_set_charge_pump_enabled(bool enabled); -int ssd1306_set_mem_addr_mode(uint8_t mode); -int ssd1306_set_segment_remapping_enabled(bool on); -int ssd1306_set_scan_direction_fwd(bool fwd); -int ssd1306_set_com_pin_hw_config(uint8_t config); -int ssd1306_set_contrast(uint8_t contrast); -int ssd1306_set_inversion(bool on); -int ssd1306_set_osc_freq(uint8_t osc_freq); -int ssd1306_set_mux_ratio(uint8_t ratio); -int ssd1306_set_column_addr(uint8_t start, uint8_t stop); -int ssd1306_set_page_addr(uint8_t start, uint8_t stop); -int ssd1306_set_precharge_period(uint8_t prchrg); -int ssd1306_set_deseltct_lvl(uint8_t lvl); -int ssd1306_set_whole_display_lighting(bool light); +/** + * Issue a single command on SSD1306. + * @param dev Pointer to device descriptor + * @param cmd Command + * @return Non-zero if error occured + */ +int ssd1306_command(const ssd1306_t *dev, uint8_t cmd); + +/** + * Default init for SSD1306 + * @param dev Pointer to device descriptor + * @return Non-zero if error occured + */ +int ssd1306_init(const ssd1306_t *dev); + +/** + * 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_