drivers: add SSD1306 display controller driver

Add SSD1306 OLED display controller driver.

Signed-off-by: Johann Fischer <j.fischer@phytec.de>
This commit is contained in:
Johann Fischer 2018-07-19 18:11:50 +02:00 committed by Anas Nashif
commit 419f4b7801
6 changed files with 696 additions and 0 deletions

View file

@ -6,3 +6,4 @@ zephyr_sources_ifdef(CONFIG_ILI9340 display_ili9340.c)
zephyr_sources_ifdef(CONFIG_ILI9340_LCD_ADAFRUIT_1480
display_ili9340_adafruit_1480.c
)
zephyr_sources_ifdef(CONFIG_SSD1306 ssd1306.c)

View file

@ -21,4 +21,6 @@ source "drivers/display/Kconfig.microbit"
source "drivers/display/Kconfig.ili9340"
source "drivers/display/Kconfig.ssd1306"
endif # DISPLAY

View file

@ -0,0 +1,38 @@
# Kconfig - SSD1306 display controller configuration options
#
# Copyright (c) 2018 Phytec Messtechnik GmbH
#
# SPDX-License-Identifier: Apache-2.0
#
menuconfig SSD1306
bool "SSD1306 display driver"
depends on I2C
help
Enable driver for SSD1306 display driver.
if SSD1306
config SSD1306_DEFAULT_CONTRAST
int "SSD1306 default contrast"
default 128
range 0 255
help
SSD1673 default contrast.
choice
prompt "Display controller type"
default SSD1306_DEFAULT
help
Specify the type of the controller.
config SSD1306_DEFAULT
bool "Default SSD1306 controller"
config SSD1306_SH1106_COMPATIBLE
bool "Enable SH1106 compatible mode"
endchoice
endif #SSD1306

478
drivers/display/ssd1306.c Normal file
View file

@ -0,0 +1,478 @@
/*
* Copyright (c) 2018 PHYTEC Messtechnik GmbH
*
* SPDX-License-Identifier: Apache-2.0
*/
#define LOG_LEVEL CONFIG_DISPLAY_LOG_LEVEL
#include <logging/log.h>
LOG_MODULE_REGISTER(ssd1306);
#include <string.h>
#include <device.h>
#include <init.h>
#include <gpio.h>
#include <i2c.h>
#include "ssd1306_regs.h"
#include <display/cfb.h>
#if !defined(CONFIG_SSD1306_I2C_MASTER_DEV_NAME)
#if defined(I2C_0_SSD1306_BUS_NAME)
#define CONFIG_SSD1306_I2C_MASTER_DEV_NAME I2C_0_SSD1306_BUS_NAME
#elif defined(I2C_1_SSD1306_BUS_NAME)
#define CONFIG_SSD1306_I2C_MASTER_DEV_NAME I2C_1_SSD1306_BUS_NAME
#endif
#endif
#if !defined(CONFIG_SSD1306_I2C_ADDR)
#if defined(I2C_0_SSD1306_BASE_ADDRESS)
#define CONFIG_SSD1306_I2C_ADDR I2C_0_SSD1306_BASE_ADDRESS
#elif defined(I2C_1_SSD1306_BASE_ADDRESS)
#define CONFIG_SSD1306_I2C_ADDR I2C_1_SSD1306_BASE_ADDRESS
#endif
#endif
#if !defined(CONFIG_SSD1306_DEV_NAME)
#if defined(I2C_0_SSD1306_LABEL)
#define CONFIG_SSD1306_DEV_NAME I2C_0_SSD1306_LABEL
#elif defined(I2C_1_SSD1306_LABEL)
#define CONFIG_SSD1306_DEV_NAME I2C_1_SSD1306_LABEL
#endif
#endif
#if defined(I2C_0_SSD1306_HEIGHT)
#define SSD1306_PANEL_WIDTH I2C_0_SSD1306_WIDTH
#define SSD1306_PANEL_HEIGHT I2C_0_SSD1306_HEIGHT
#define SSD1306_PANEL_FIRST_SEG I2C_0_SSD1306_SEGMENT_OFFSET
#define SSD1306_PANEL_DISPLAY_OFFSET I2C_0_SSD1306_DISPLAY_OFFSET
#define SSD1306_PANEL_PAGE_OFFSET I2C_0_SSD1306_PAGE_OFFSET
#define SSD1306_PANEL_PRECHARGE_PERIOD I2C_0_SSD1306_PRECHARGEP
#endif
#if defined(I2C_1_SSD1306_HEIGHT)
#define SSD1306_PANEL_WIDTH I2C_1_SSD1306_WIDTH
#define SSD1306_PANEL_HEIGHT I2C_1_SSD1306_HEIGHT
#define SSD1306_PANEL_FIRST_SEG I2C_1_SSD1306_SEGMENT_OFFSET
#define SSD1306_PANEL_DISPLAY_OFFSET I2C_1_SSD1306_DISPLAY_OFFSET
#define SSD1306_PANEL_PAGE_OFFSET I2C_1_SSD1306_PAGE_OFFSET
#define SSD1306_PANEL_PRECHARGE_PERIOD I2C_1_SSD1306_PRECHARGEP
#endif
#if defined(I2C_0_SSD1306_SEGMENT_REMAP)
#define SSD1306_PANEL_SEGMENT_REMAP true
#elif defined(I2C_1_SSD1306_SEGMENT_REMAP)
#define SSD1306_PANEL_SEGMENT_REMAP true
#else
#define SSD1306_PANEL_SEGMENT_REMAP false
#endif
#if defined(I2C_0_SSD1306_COM_INVDIR)
#define SSD1306_PANEL_COM_INVDIR true
#elif defined(I2C_1_SSD1306_COM_INVDIR)
#define SSD1306_PANEL_COM_INVDIR true
#else
#define SSD1306_PANEL_COM_INVDIR false
#endif
#define SSD1306_PANEL_NUMOF_PAGES (SSD1306_PANEL_HEIGHT / 8)
#define SSD1306_CLOCK_DIV_RATIO 0x0
#define SSD1306_CLOCK_FREQUENCY 0x8
#define SSD1306_PANEL_MUX_RATIO 63
#define SSD1306_PANEL_VCOM_DESEL_LEVEL 0x20
#define SSD1306_PANEL_PUMP_VOLTAGE SSD1306_SET_PUMP_VOLTAGE_90
#if defined(CONFIG_SSD1306_SH1106_COMPATIBLE)
#define SSD1306_PANEL_NUMOF_COLUMS 132
#else
#define SSD1306_PANEL_NUMOF_COLUMS 128
#endif
#ifndef SSD1306_ADDRESSING_MODE
#define SSD1306_ADDRESSING_MODE (SSD1306_SET_MEM_ADDRESSING_HORIZONTAL)
#endif
struct ssd1306_data {
struct device *i2c;
u8_t contrast;
u8_t scan_mode;
};
static inline int ssd1306_reg_read(struct ssd1306_data *driver,
u8_t reg, u8_t * const val)
{
return i2c_reg_read_byte(driver->i2c, CONFIG_SSD1306_I2C_ADDR,
reg, val);
}
static inline int ssd1306_reg_write(struct ssd1306_data *driver,
u8_t reg, u8_t val)
{
return i2c_reg_write_byte(driver->i2c, CONFIG_SSD1306_I2C_ADDR,
reg, val);
}
static inline int ssd1306_reg_update(struct ssd1306_data *driver, u8_t reg,
u8_t mask, u8_t val)
{
return i2c_reg_update_byte(driver->i2c, CONFIG_SSD1306_I2C_ADDR,
reg, mask, val);
}
static inline int ssd1306_set_panel_orientation(struct device *dev)
{
struct ssd1306_data *driver = dev->driver_data;
u8_t cmd_buf[] = {
SSD1306_CONTROL_BYTE_CMD,
(SSD1306_PANEL_SEGMENT_REMAP ?
SSD1306_SET_SEGMENT_MAP_REMAPED :
SSD1306_SET_SEGMENT_MAP_NORMAL),
SSD1306_CONTROL_LAST_BYTE_CMD,
(SSD1306_PANEL_COM_INVDIR ?
SSD1306_SET_COM_OUTPUT_SCAN_FLIPPED :
SSD1306_SET_COM_OUTPUT_SCAN_NORMAL)
};
return i2c_write(driver->i2c, cmd_buf, sizeof(cmd_buf),
CONFIG_SSD1306_I2C_ADDR);
}
static inline int ssd1306_set_timing_setting(struct device *dev)
{
struct ssd1306_data *driver = dev->driver_data;
u8_t cmd_buf[] = {
SSD1306_CONTROL_BYTE_CMD,
SSD1306_SET_CLOCK_DIV_RATIO,
SSD1306_CONTROL_BYTE_CMD,
(SSD1306_CLOCK_FREQUENCY << 4) | SSD1306_CLOCK_DIV_RATIO,
SSD1306_CONTROL_BYTE_CMD,
SSD1306_SET_CHARGE_PERIOD,
SSD1306_CONTROL_BYTE_CMD,
SSD1306_PANEL_PRECHARGE_PERIOD,
SSD1306_CONTROL_BYTE_CMD,
SSD1306_SET_VCOM_DESELECT_LEVEL,
SSD1306_CONTROL_LAST_BYTE_CMD,
SSD1306_PANEL_VCOM_DESEL_LEVEL
};
return i2c_write(driver->i2c, cmd_buf, sizeof(cmd_buf),
CONFIG_SSD1306_I2C_ADDR);
}
static inline int ssd1306_set_hardware_config(struct device *dev)
{
struct ssd1306_data *driver = dev->driver_data;
u8_t cmd_buf[] = {
SSD1306_CONTROL_BYTE_CMD,
SSD1306_SET_START_LINE,
SSD1306_CONTROL_BYTE_CMD,
SSD1306_SET_DISPLAY_OFFSET,
SSD1306_CONTROL_BYTE_CMD,
SSD1306_PANEL_DISPLAY_OFFSET,
SSD1306_CONTROL_BYTE_CMD,
SSD1306_SET_PADS_HW_CONFIG,
SSD1306_CONTROL_BYTE_CMD,
SSD1306_SET_PADS_HW_ALTERNATIVE,
SSD1306_CONTROL_BYTE_CMD,
SSD1306_SET_MULTIPLEX_RATIO,
SSD1306_CONTROL_LAST_BYTE_CMD,
SSD1306_PANEL_MUX_RATIO
};
return i2c_write(driver->i2c, cmd_buf, sizeof(cmd_buf),
CONFIG_SSD1306_I2C_ADDR);
}
static inline int ssd1306_set_charge_pump(const struct device *dev)
{
struct ssd1306_data *driver = dev->driver_data;
u8_t cmd_buf[] = {
#if defined(CONFIG_SSD1306_DEFAULT)
SSD1306_CONTROL_BYTE_CMD,
SSD1306_SET_CHARGE_PUMP_ON,
SSD1306_CONTROL_BYTE_CMD,
SSD1306_SET_CHARGE_PUMP_ON_ENABLED,
#endif
#if defined(CONFIG_SSD1306_SH1106_COMPATIBLE)
SSD1306_CONTROL_BYTE_CMD,
SH1106_SET_DCDC_MODE,
SSD1306_CONTROL_BYTE_CMD,
SH1106_SET_DCDC_ENABLED,
#endif
SSD1306_CONTROL_LAST_BYTE_CMD,
SSD1306_PANEL_PUMP_VOLTAGE,
};
return i2c_write(driver->i2c, cmd_buf, sizeof(cmd_buf),
CONFIG_SSD1306_I2C_ADDR);
}
int ssd1306_resume(const struct device *dev)
{
struct ssd1306_data *driver = dev->driver_data;
/* set display on */
return ssd1306_reg_write(driver, SSD1306_CONTROL_LAST_BYTE_CMD,
SSD1306_DISPLAY_ON);
}
int ssd1306_suspend(const struct device *dev)
{
struct ssd1306_data *driver = dev->driver_data;
/* set display on */
return ssd1306_reg_write(driver, SSD1306_CONTROL_LAST_BYTE_CMD,
SSD1306_DISPLAY_OFF);
}
int ssd1306_write_page(struct device *dev, u8_t page, void * const data,
size_t length)
{
struct ssd1306_data *driver = dev->driver_data;
u8_t cmd_buf[] = {
#ifdef OLED_PANEL_CONTROLLER_SSD1306
SSD1306_CONTROL_BYTE_CMD,
SSD1306_SET_MEM_ADDRESSING_MODE,
SSD1306_CONTROL_BYTE_CMD,
SSD1306_SET_MEM_ADDRESSING_PAGE,
#endif
SSD1306_CONTROL_BYTE_CMD,
SSD1306_SET_LOWER_COL_ADDRESS |
(SSD1306_PANEL_FIRST_SEG &
SSD1306_SET_LOWER_COL_ADDRESS_MASK),
SSD1306_CONTROL_BYTE_CMD,
SSD1306_SET_HIGHER_COL_ADDRESS |
((SSD1306_PANEL_FIRST_SEG >> 4) &
SSD1306_SET_LOWER_COL_ADDRESS_MASK),
SSD1306_CONTROL_LAST_BYTE_CMD,
SSD1306_SET_PAGE_START_ADDRESS | page
};
if (page >= SSD1306_PANEL_NUMOF_PAGES) {
return -1;
}
if (length > SSD1306_PANEL_NUMOF_COLUMS) {
return -1;
}
if (i2c_write(driver->i2c, cmd_buf, sizeof(cmd_buf),
CONFIG_SSD1306_I2C_ADDR)) {
return -1;
}
return i2c_burst_write(driver->i2c, CONFIG_SSD1306_I2C_ADDR,
SSD1306_CONTROL_LAST_BYTE_DATA,
data, length);
}
int ssd1306_write(const struct device *dev, const u16_t x, const u16_t y,
const struct display_buffer_descriptor *desc,
const void *buf)
{
if (desc->pitch < desc->width) {
LOG_ERR("Pitch is smaller then width");
return -1;
}
if (buf == NULL || desc->buf_size == 0) {
LOG_ERR("Display buffer is not available");
return -1;
}
if (desc->pitch > desc->width) {
LOG_ERR("Unsupported mode");
return -1;
}
if (x != 0 && y != 0) {
LOG_ERR("Unsupported origin");
return -1;
}
#if defined(CONFIG_SSD1306_DEFAULT)
struct ssd1306_data *driver = dev->driver_data;
u8_t cmd_buf[] = {
SSD1306_CONTROL_BYTE_CMD,
SSD1306_SET_MEM_ADDRESSING_MODE,
SSD1306_CONTROL_BYTE_CMD,
SSD1306_ADDRESSING_MODE,
SSD1306_CONTROL_BYTE_CMD,
SSD1306_SET_COLUMN_ADDRESS,
SSD1306_CONTROL_BYTE_CMD,
0,
SSD1306_CONTROL_BYTE_CMD,
(SSD1306_PANEL_NUMOF_COLUMS - 1),
SSD1306_CONTROL_BYTE_CMD,
SSD1306_SET_PAGE_ADDRESS,
SSD1306_CONTROL_BYTE_CMD,
0,
SSD1306_CONTROL_LAST_BYTE_CMD,
(SSD1306_PANEL_NUMOF_PAGES - 1)
};
if (i2c_write(driver->i2c, cmd_buf, sizeof(cmd_buf),
CONFIG_SSD1306_I2C_ADDR)) {
LOG_ERR("Failed to write command");
return -1;
}
return i2c_burst_write(driver->i2c, CONFIG_SSD1306_I2C_ADDR,
SSD1306_CONTROL_LAST_BYTE_DATA,
(u8_t *)buf, desc->buf_size);
#elif defined(CONFIG_SSD1306_SH1106_COMPATIBLE)
if (len != SSD1306_PANEL_NUMOF_PAGES * SSD1306_PANEL_WIDTH) {
return -1;
}
for (size_t pidx = 0; pidx < SSD1306_PANEL_NUMOF_PAGES; pidx++) {
if (ssd1306_write_page(dev, pidx, buf, SSD1306_PANEL_WIDTH)) {
return -1;
}
buf = (u8_t *)buf + SSD1306_PANEL_WIDTH;
}
#endif
return 0;
}
static int ssd1306_read(const struct device *dev, const u16_t x,
const u16_t y,
const struct display_buffer_descriptor *desc,
void *buf)
{
LOG_ERR("Unsupported");
return -ENOTSUP;
}
static void *ssd1306_get_framebuffer(const struct device *dev)
{
LOG_ERR("Unsupported");
return NULL;
}
static int ssd1306_set_brightness(const struct device *dev,
const u8_t brightness)
{
LOG_WRN("Unsupported");
return -ENOTSUP;
}
int ssd1306_set_contrast(const struct device *dev, const u8_t contrast)
{
struct ssd1306_data *driver = dev->driver_data;
u8_t cmd_buf[] = {
SSD1306_CONTROL_BYTE_CMD,
SSD1306_SET_CONTRAST_CTRL,
SSD1306_CONTROL_LAST_BYTE_CMD,
contrast,
};
return i2c_write(driver->i2c, cmd_buf, sizeof(cmd_buf),
CONFIG_SSD1306_I2C_ADDR);
}
static void ssd1306_get_capabilities(const struct device *dev,
struct display_capabilities *caps)
{
memset(caps, 0, sizeof(struct display_capabilities));
caps->x_resolution = SSD1306_PANEL_WIDTH;
caps->y_resolution = SSD1306_PANEL_HEIGHT;
caps->supported_pixel_formats = PIXEL_FORMAT_MONO10;
caps->current_pixel_format = PIXEL_FORMAT_MONO10;
caps->screen_info = SCREEN_INFO_MONO_VTILED;
}
static int ssd1306_set_pixel_format(const struct device *dev,
const enum display_pixel_format pf)
{
LOG_ERR("Unsupported");
return -ENOTSUP;
}
static int ssd1306_init_device(struct device *dev)
{
struct ssd1306_data *driver = dev->driver_data;
u8_t cmd_buf[] = {
SSD1306_CONTROL_BYTE_CMD,
SSD1306_SET_ENTIRE_DISPLAY_OFF,
SSD1306_CONTROL_LAST_BYTE_CMD,
SSD1306_SET_NORMAL_DISPLAY,
};
/* Turn display off */
if (ssd1306_reg_write(driver, SSD1306_CONTROL_LAST_BYTE_CMD,
SSD1306_DISPLAY_OFF)) {
return -EIO;
}
if (ssd1306_set_timing_setting(dev)) {
return -EIO;
}
if (ssd1306_set_hardware_config(dev)) {
return -EIO;
}
if (ssd1306_set_panel_orientation(dev)) {
return -EIO;
}
if (ssd1306_set_charge_pump(dev)) {
return -EIO;
}
if (i2c_write(driver->i2c, cmd_buf, sizeof(cmd_buf),
CONFIG_SSD1306_I2C_ADDR)) {
return -EIO;
}
if (ssd1306_set_contrast(dev, CONFIG_SSD1306_DEFAULT_CONTRAST)) {
return -EIO;
}
ssd1306_resume(dev);
return 0;
}
static int ssd1306_init(struct device *dev)
{
struct ssd1306_data *driver = dev->driver_data;
LOG_DBG("");
driver->i2c = device_get_binding(CONFIG_SSD1306_I2C_MASTER_DEV_NAME);
if (driver->i2c == NULL) {
LOG_ERR("Failed to get pointer to %s device!",
CONFIG_SSD1306_I2C_MASTER_DEV_NAME);
return -EINVAL;
}
if (ssd1306_init_device(dev)) {
LOG_ERR("Failed to initialize device!");
return -EIO;
}
return 0;
}
static struct ssd1306_data ssd1306_driver;
static struct display_driver_api ssd1306_driver_api = {
.blanking_on = ssd1306_resume,
.blanking_off = ssd1306_suspend,
.write = ssd1306_write,
.read = ssd1306_read,
.get_framebuffer = ssd1306_get_framebuffer,
.set_brightness = ssd1306_set_brightness,
.set_contrast = ssd1306_set_contrast,
.get_capabilities = ssd1306_get_capabilities,
.set_pixel_format = ssd1306_set_pixel_format,
};
DEVICE_AND_API_INIT(ssd1306, CONFIG_SSD1306_DEV_NAME, ssd1306_init,
&ssd1306_driver, NULL,
POST_KERNEL, CONFIG_APPLICATION_INIT_PRIORITY,
&ssd1306_driver_api);

View file

@ -0,0 +1,109 @@
/*
* Copyright (c) 2018 Phytec Messtechnik GmbH
*
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef __SSD1306_REGS_H__
#define __SSD1306_REGS_H__
#define SSD1306_CONTROL_LAST_BYTE_CMD 0x00
#define SSD1306_CONTROL_LAST_BYTE_DATA 0x40
#define SSD1306_CONTROL_BYTE_CMD 0x80
#define SSD1306_CONTROL_BYTE_DATA 0xc0
#define SSD1306_READ_STATUS_MASK 0xc0
#define SSD1306_READ_STATUS_BUSY 0x80
#define SSD1306_READ_STATUS_ON 0x40
/*
* Fundamental Command Table
*/
#define SSD1306_SET_CONTRAST_CTRL 0x81 /* double byte command */
#define SSD1306_SET_ENTIRE_DISPLAY_OFF 0xa4
#define SSD1306_SET_ENTIRE_DISPLAY_ON 0xa5
#define SSD1306_SET_NORMAL_DISPLAY 0xa6
#define SSD1306_SET_REVERSE_DISPLAY 0xa7
#define SSD1306_DISPLAY_OFF 0xae
#define SSD1306_DISPLAY_ON 0xaf
/*
* Addressing Setting Command Table
*/
#define SSD1306_SET_LOWER_COL_ADDRESS 0x00
#define SSD1306_SET_LOWER_COL_ADDRESS_MASK 0x0f
#define SSD1306_SET_HIGHER_COL_ADDRESS 0x10
#define SSD1306_SET_HIGHER_COL_ADDRESS_MASK 0x0f
#define SSD1306_SET_MEM_ADDRESSING_MODE 0x20 /* double byte command */
#define SSD1306_SET_MEM_ADDRESSING_HORIZONTAL 0x00
#define SSD1306_SET_MEM_ADDRESSING_VERTICAL 0x01
#define SSD1306_SET_MEM_ADDRESSING_PAGE 0x02
#define SSD1306_SET_COLUMN_ADDRESS 0x21 /* triple byte command */
#define SSD1306_SET_PAGE_ADDRESS 0x22 /* triple byte command */
#define SSD1306_SET_PAGE_START_ADDRESS 0xb0
#define SSD1306_SET_PAGE_START_ADDRESS_MASK 0x07
/*
* Hardware Configuration Command Table
*/
#define SSD1306_SET_START_LINE 0x40
#define SSD1306_SET_START_LINE_MASK 0x3f
#define SSD1306_SET_SEGMENT_MAP_NORMAL 0xa0
#define SSD1306_SET_SEGMENT_MAP_REMAPED 0xa1
#define SSD1306_SET_MULTIPLEX_RATIO 0xa8 /* double byte command */
#define SSD1306_SET_COM_OUTPUT_SCAN_NORMAL 0xc0
#define SSD1306_SET_COM_OUTPUT_SCAN_FLIPPED 0xc8
#define SSD1306_SET_DISPLAY_OFFSET 0xd3 /* double byte command */
#define SSD1306_SET_PADS_HW_CONFIG 0xda /* double byte command */
#define SSD1306_SET_PADS_HW_SEQUENTIAL 0x02
#define SSD1306_SET_PADS_HW_ALTERNATIVE 0x12
/*
* Timming and Driving Scheme Setting Command Table
*/
#define SSD1306_SET_CLOCK_DIV_RATIO 0xd5 /* double byte command */
#define SSD1306_SET_CHARGE_PERIOD 0xd9 /* double byte command */
#define SSD1306_SET_VCOM_DESELECT_LEVEL 0xdb /* double byte command */
#define SSD1306_NOP 0xe3
/*
* Charge Pump Command Table
*/
#define SSD1306_SET_CHARGE_PUMP_ON 0x8d /* double byte command */
#define SSD1306_SET_CHARGE_PUMP_ON_DISABLED 0x10
#define SSD1306_SET_CHARGE_PUMP_ON_ENABLED 0x14
#define SH1106_SET_DCDC_MODE 0xad /* double byte command */
#define SH1106_SET_DCDC_DISABLED 0x8a
#define SH1106_SET_DCDC_ENABLED 0x8b
#define SSD1306_SET_PUMP_VOLTAGE_64 0x30
#define SSD1306_SET_PUMP_VOLTAGE_74 0x31
#define SSD1306_SET_PUMP_VOLTAGE_80 0x32
#define SSD1306_SET_PUMP_VOLTAGE_90 0x33
/*
* Read modify write
*/
#define SSD1306_READ_MODIFY_WRITE_START 0xe0
#define SSD1306_READ_MODIFY_WRITE_END 0xee
#endif