spi/i2c ver

This commit is contained in:
2026-08-21 00:00:10 +02:00
parent 073bcd0d25
commit df7d135577
6 changed files with 227 additions and 270 deletions

View File

@@ -8,7 +8,7 @@ For both SPI and I2C displays
I2C version I2C version
```c ```c
#include <st7567/st7567-i2c.h> #include <st7567/st7567.h>
i2c_master_bus_config_t bus_cfg = { i2c_master_bus_config_t bus_cfg = {
.i2c_port = I2C_NUM_0, .i2c_port = I2C_NUM_0,
@@ -21,15 +21,15 @@ i2c_master_bus_config_t bus_cfg = {
i2c_master_bus_handle_t bus; i2c_master_bus_handle_t bus;
i2c_new_master_bus(&bus_cfg, &bus); i2c_new_master_bus(&bus_cfg, &bus);
st7567_config_t cfg = { st7567_config_i2c_t cfg = {
.i2c_bus = bus, .i2c_bus = bus,
.i2c_addr = ST7567_I2C_ADDR_DEFAULT, // 0x3F, .i2c_addr = ST7567_I2C_ADDR_DEFAULT, // 0x3F,
.i2c_clock_speed_hz = 400000, .i2c_clock_speed_hz = 400000,
.contrast = 0x38, // good for generic modules at 3.3v .contrast = 0x38, // good for generic modules at 3.3v
}; };
st7567_device_t dev; st7567_device_t dev;
st7567_init(&cfg, &dev); st7567_init_i2c(&cfg, &dev);
st7567_set_backlight(&dev, true);
st7567_draw_pixel(&dev, 10, 10); st7567_draw_pixel(&dev, 10, 10);
st7567_update_screen(&dev); st7567_update_screen(&dev);
@@ -38,11 +38,29 @@ st7567_update_screen(&dev);
SPI version SPI version
```c ```c
#include <st7567/st7567-spi.h> #include <st7567/st7567.h>
const spi_bus_config_t buscfg = {
.mosi_io_num = GPIO_NUM_18,
.sclk_io_num = GPIO_NUM_19,
.miso_io_num = GPIO_NUM_20, // default c6 spi pins?
.max_transfer_sz = 0,
.quadhd_io_num = -1,
.quadwp_io_num = -1,
};
spi_bus_initialize(SPI2_HOST, &buscfg, SPI_DMA_CH_AUTO);
const st7567_config_spi_t cfg = {
.contrast = 0x35,
.cs_gpio = GPIO_NUM_16,
.dc_gpio = GPIO_NUM_17,
.rst_gpio = GPIO_NUM_21,
.spi_clock_speed_hz = 0,
.spi_host = SPI2_HOST,
};
st7567_device_t dev; st7567_device_t dev;
st7567_init(&cfg, &dev); st7567_init_spi(&cfg, &dev);
st7567_set_backlight(&dev, true);
st7567_draw_pixel(&dev, 10, 10); st7567_draw_pixel(&dev, 10, 10);
st7567_update_screen(&dev); st7567_update_screen(&dev);

View File

@@ -1,4 +1,4 @@
version: "1.1.1" version: "2.0.0"
description: "ST7567 128x64 display driver" description: "ST7567 128x64 display driver"
url: "https://git.drath.cc/drath/esp-st7567" url: "https://git.drath.cc/drath/esp-st7567"
dependencies: dependencies:

View File

@@ -1,31 +0,0 @@
#ifndef ST7567_I2C_H
#define ST7567_I2C_H
#include "esp_err.h"
#include "driver/i2c_master.h"
#include "st7567-common.h"
// Display dimensions
#define ST7567_WIDTH 128
#define ST7567_HEIGHT 64
#define ST7567_PAGES (ST7567_HEIGHT / 8)
// Configuration structure
typedef struct {
i2c_master_bus_handle_t i2c_bus; // already-initialized I2C bus
uint16_t i2c_addr; // 7-bit device address, 0 = use ST7567_I2C_ADDR_DEFAULT
uint32_t i2c_clock_speed_hz; // 0 = use 400kHz default
uint8_t contrast; // 0-63, 0 = use a sane default (0x20)
} st7567_i2c_config_t;
/**
* @brief Initialize ST7567 display
*
* @param config Configuration structure
* @param device Pointer to store device state (framebuffer etc, caller-owned)
* @return esp_err_t ESP_OK on success
*/
esp_err_t st7567_init(const st7567_i2c_config_t *config, st7567_device_t *device);
#endif // ST7567_I2C_H

View File

@@ -1,29 +0,0 @@
#ifndef ST7567_SPI_H
#define ST7567_SPI_H
#include "st7567-common.h"
#include "esp_err.h"
#include "driver/spi_master.h"
#include "driver/gpio.h"
// Configuration structure
typedef struct {
spi_host_device_t spi_host; // already-initialized SPI bus (spi_bus_initialize called by caller)
gpio_num_t cs_gpio; // chip select
gpio_num_t dc_gpio; // data/command select (required, no in-band control byte over SPI)
gpio_num_t rst_gpio; // hardware reset, GPIO_NUM_NC if not wired
uint32_t spi_clock_speed_hz; // 0 = use 8MHz default
uint8_t contrast; // 0-63, 0 = use a sane default (0x20)
} st7567_spi_config_t;
/**
* @brief Initialize ST7567 display
*
* @param config Configuration structure
* @param device Pointer to store device state (framebuffer etc, caller-owned)
* @return esp_err_t ESP_OK on success
*/
esp_err_t st7567_init(const st7567_spi_config_t *config, st7567_device_t *device);
#endif // ST7567_SPI_H

View File

@@ -1,238 +1,201 @@
#include "st7567-common.h" #include "st7567.h"
#include <string.h> #include <string.h>
#include "esp_log.h" #include "esp_log.h"
#include "freertos/FreeRTOS.h" #include "freertos/FreeRTOS.h"
#include "freertos/task.h" #include "freertos/task.h"
#include <driver/i2c_master.h>
static const char *TAG = "ST7567"; static const char *TAG = "ST7567";
#ifdef ST7567_I2C_H
#define ST7567_I2C_CTRL_CMD 0x00 static esp_err_t st7567_write_i2c(const st7567_device_t *dev, uint8_t ctrl, const uint8_t *data, size_t len) {
#define ST7567_I2C_CTRL_DATA 0x40 // small stack buffer, framebuffer flush is chunked by page (128B) so this is plenty
uint8_t buf[ST7567_WIDTH + 1];
if (len + 1 > sizeof(buf))
return ESP_ERR_INVALID_SIZE;
#define ST7567_I2C_TIMEOUT_MS 100 buf[0] = ctrl;
#define ST7567_DEFAULT_I2C_CLOCK_HZ 400000 memcpy(&buf[1], data, len);
return i2c_master_transmit(dev->bus, buf, len + 1, ST7567_I2C_TIMEOUT_MS);
static esp_err_t st7567_write(const st7567_device_t *dev, uint8_t ctrl, const uint8_t *data, size_t len) {
// small stack buffer, framebuffer flush is chunked by page (128B) so this is plenty
uint8_t buf[ST7567_WIDTH + 1];
if (len + 1 > sizeof(buf))
return ESP_ERR_INVALID_SIZE;
buf[0] = ctrl;
memcpy(&buf[1], data, len);
return i2c_master_transmit(dev->bus, buf, len + 1, ST7567_I2C_TIMEOUT_MS);
} }
esp_err_t st7567_send_cmd(const st7567_device_t *dev, const uint8_t cmd) { static esp_err_t st7567_write_spi(const st7567_device_t *dev, int dc_level, const uint8_t *data, size_t len) {
return st7567_write(dev, ST7567_I2C_CTRL_CMD, &cmd, 1); if (len == 0)
} return ESP_OK;
esp_err_t st7567_send_data(const st7567_device_t *dev, const uint8_t *data, size_t len) { gpio_set_level(dev->dc_gpio, dc_level);
return st7567_write(dev, ST7567_I2C_CTRL_DATA, data, len);
spi_transaction_t t = {
.length = len * 8, // bits, not bytes
.tx_buffer = data,
};
// blocking polling transmit: this driver is only ever called from one task
// (ui_task), so there's no need for the queued/async SPI API here
return spi_device_polling_transmit(dev->bus, &t);
} }
esp_err_t st7567_init(const st7567_i2c_config_t *config, st7567_device_t *device) { inline static esp_err_t st7567_send_cmd(const st7567_device_t *dev, const uint8_t cmd) {
memset(device, 0, sizeof(st7567_device_t)); // clears framebuffer too if (dev->bus_type == ST7567_BUS_TYPE_SPI)
return st7567_write_spi(dev, ST7567_DC_CMD, &cmd, 1);
return st7567_write_i2c(dev, ST7567_I2C_CTRL_CMD, &cmd, 1);
}
// Add device on the (already-initialized) I2C bus inline static esp_err_t st7567_send_data(const st7567_device_t *dev, const uint8_t *data, size_t len) {
const i2c_device_config_t dev_cfg = { if (dev->bus_type == ST7567_BUS_TYPE_SPI)
.dev_addr_length = I2C_ADDR_BIT_LEN_7, return st7567_write_spi(dev, ST7567_DC_DATA, data, len);
.device_address = config->i2c_addr ? config->i2c_addr : ST7567_I2C_ADDR_DEFAULT, return st7567_write_i2c(dev, ST7567_I2C_CTRL_DATA, data, len);
.scl_speed_hz = config->i2c_clock_speed_hz ? config->i2c_clock_speed_hz : ST7567_DEFAULT_CLOCK_HZ, }
};
const esp_err_t ret = i2c_master_bus_add_device(config->i2c_bus, &dev_cfg, &device->i2c_dev); static void st7567_send_init(const st7567_device_t *device, const uint8_t contrast) {
if (ret != ESP_OK) { // internal software reset + init sequence (standard ST7567 sequence)
ESP_LOGE(TAG, "Failed to add I2C device: %s", esp_err_to_name(ret)); st7567_send_cmd(device, ST7567_CMD_RESET);
return ret; vTaskDelay(pdMS_TO_TICKS(10));
} st7567_send_cmd(device, ST7567_CMD_BIAS_1_9);
st7567_send_cmd(device, ST7567_CMD_ADC_NORMAL);
st7567_send_cmd(device, ST7567_CMD_COM_REVERSE);
st7567_send_cmd(device, ST7567_CMD_RESISTOR_RATIO | 0x03);
st7567_send_cmd(device, ST7567_CMD_POWER_CTRL | 0x07); // booster, regulator, follower all on
vTaskDelay(pdMS_TO_TICKS(10)); // let the charge pump stabilize before touching contrast
// internal software reset + init sequence (standard ST7567 sequence) st7567_set_contrast(device, contrast ? contrast : ST7567_DEFAULT_CONTRAST);
st7567_send_cmd(device, ST7567_CMD_RESET);
vTaskDelay(pdMS_TO_TICKS(10));
st7567_send_cmd(device, ST7567_CMD_BIAS_1_9);
st7567_send_cmd(device, ST7567_CMD_ADC_NORMAL);
st7567_send_cmd(device, ST7567_CMD_COM_REVERSE);
st7567_send_cmd(device, ST7567_CMD_RESISTOR_RATIO | 0x03);
st7567_send_cmd(device, ST7567_CMD_POWER_CTRL | 0x07); // booster, regulator, follower all on
vTaskDelay(pdMS_TO_TICKS(10)); // let the charge pump stabilize before touching contrast
st7567_set_contrast(device, config->contrast ? config->contrast : ST7567_DEFAULT_CONTRAST); st7567_send_cmd(device, ST7567_CMD_START_LINE | 0x00);
st7567_send_cmd(device, ST7567_CMD_ALL_PIXEL_NORMAL);
st7567_send_cmd(device, ST7567_CMD_DISPLAY_NORMAL);
st7567_send_cmd(device, ST7567_CMD_DISPLAY_ON);
}
st7567_send_cmd(device, ST7567_CMD_START_LINE | 0x00); esp_err_t st7567_init_i2c(const st7567_config_i2c_t *config, st7567_device_t *device) {
st7567_send_cmd(device, ST7567_CMD_ALL_PIXEL_NORMAL); memset(device, 0, sizeof(st7567_device_t)); // clears framebuffer too
st7567_send_cmd(device, ST7567_CMD_DISPLAY_NORMAL); device->bus_type = ST7567_BUS_TYPE_I2C;
st7567_send_cmd(device, ST7567_CMD_DISPLAY_ON);
ESP_LOGI(TAG, "display init done"); // Add device on the (already-initialized) I2C bus
return ESP_OK; const i2c_device_config_t dev_cfg = {
.dev_addr_length = I2C_ADDR_BIT_LEN_7,
.device_address = config->i2c_addr ? config->i2c_addr : ST7567_I2C_ADDR_DEFAULT,
.scl_speed_hz = config->i2c_clock_speed_hz ? config->i2c_clock_speed_hz : ST7567_DEFAULT_I2C_CLOCK_HZ,
};
const esp_err_t ret = i2c_master_bus_add_device(config->i2c_bus, &dev_cfg, (i2c_master_dev_handle_t *)&device->bus);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "Failed to add I2C device: %s", esp_err_to_name(ret));
return ret;
}
st7567_send_init(device, config->contrast);
ESP_LOGI(TAG, "display init done");
return ESP_OK;
} }
#endif esp_err_t st7567_init_spi(const st7567_config_spi_t *config, st7567_device_t *device) {
memset(device, 0, sizeof(st7567_device_t)); // clears framebuffer too
device->bus_type = ST7567_BUS_TYPE_SPI;
device->dc_gpio = config->dc_gpio;
#ifdef ST7567_SPI_H // DC is toggled every single command/data write, so drive it directly
// rather than routing it through the SPI peripheral
const gpio_config_t dc_cfg = {
.pin_bit_mask = 1ULL << config->dc_gpio,
.mode = GPIO_MODE_OUTPUT,
};
esp_err_t ret = gpio_config(&dc_cfg);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "Failed to configure DC gpio: %s", esp_err_to_name(ret));
return ret;
}
#define ST7567_SPI_TIMEOUT_MS 100 if (config->rst_gpio != GPIO_NUM_NC) {
#define ST7567_DEFAULT_SPI_CLOCK_HZ 8000000 const gpio_config_t rst_cfg = {
.pin_bit_mask = 1ULL << config->rst_gpio,
.mode = GPIO_MODE_OUTPUT,
};
ret = gpio_config(&rst_cfg);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "Failed to configure RST gpio: %s", esp_err_to_name(ret));
return ret;
}
#define ST7567_DC_CMD 0 gpio_set_level(config->rst_gpio, 0);
#define ST7567_DC_DATA 1 vTaskDelay(pdMS_TO_TICKS(10));
gpio_set_level(config->rst_gpio, 1);
vTaskDelay(pdMS_TO_TICKS(10));
}
static esp_err_t st7567_write(const st7567_device_t *dev, int dc_level, const uint8_t *data, size_t len) { // Add device on the (already-initialized) SPI bus
if (len == 0) const spi_device_interface_config_t dev_cfg = {
return ESP_OK; .clock_speed_hz = config->spi_clock_speed_hz ? (int)config->spi_clock_speed_hz : ST7567_DEFAULT_SPI_CLOCK_HZ,
.mode = 0, // CPOL=0, CPHA=0 - standard for ST7567, check your panel if init garbles
.spics_io_num = config->cs_gpio,
.queue_size = 1,
};
gpio_set_level(dev->dc_gpio, dc_level); ret = spi_bus_add_device(config->spi_host, &dev_cfg, (spi_device_handle_t*)&device->bus);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "Failed to add SPI device: %s", esp_err_to_name(ret));
return ret;
}
spi_transaction_t t = { st7567_send_init(device, config->contrast);
.length = len * 8, // bits, not bytes ESP_LOGI(TAG, "display init done");
.tx_buffer = data, return ESP_OK;
};
// blocking polling transmit: this driver is only ever called from one task
// (ui_task), so there's no need for the queued/async SPI API here
return spi_device_polling_transmit(dev->bus, &t);
} }
esp_err_t st7567_send_cmd(const st7567_device_t *dev, const uint8_t cmd) {
return st7567_write(dev, ST7567_DC_CMD, &cmd, 1);
}
esp_err_t st7567_send_data(const st7567_device_t *dev, const uint8_t *data, size_t len) {
return st7567_write(dev, ST7567_DC_DATA, data, len);
}
esp_err_t st7567_init(const st7567_spi_config_t *config, st7567_device_t *device) {
memset(device, 0, sizeof(st7567_device_t)); // clears framebuffer too
device->dc_gpio = config->dc_gpio;
// DC is toggled every single command/data write, so drive it directly
// rather than routing it through the SPI peripheral
gpio_config_t dc_cfg = {
.pin_bit_mask = 1ULL << config->dc_gpio,
.mode = GPIO_MODE_OUTPUT,
};
esp_err_t ret = gpio_config(&dc_cfg);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "Failed to configure DC gpio: %s", esp_err_to_name(ret));
return ret;
}
if (config->rst_gpio != GPIO_NUM_NC) {
gpio_config_t rst_cfg = {
.pin_bit_mask = 1ULL << config->rst_gpio,
.mode = GPIO_MODE_OUTPUT,
};
ret = gpio_config(&rst_cfg);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "Failed to configure RST gpio: %s", esp_err_to_name(ret));
return ret;
}
// hardware reset pulse, active low - panel datasheets typically want
// >=1us low, we hold well past that
gpio_set_level(config->rst_gpio, 0);
vTaskDelay(pdMS_TO_TICKS(10));
gpio_set_level(config->rst_gpio, 1);
vTaskDelay(pdMS_TO_TICKS(10));
}
// Add device on the (already-initialized) SPI bus
const spi_device_interface_config_t dev_cfg = {
.clock_speed_hz = config->spi_clock_speed_hz ? config->spi_clock_speed_hz : ST7567_DEFAULT_CLOCK_HZ,
.mode = 0, // CPOL=0, CPHA=0 - standard for ST7567, check your panel if init garbles
.spics_io_num = config->cs_gpio,
.queue_size = 1,
};
ret = spi_bus_add_device(config->spi_host, &dev_cfg, &device->spi_dev);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "Failed to add SPI device: %s", esp_err_to_name(ret));
return ret;
}
// internal software reset + init sequence (standard ST7567 sequence)
st7567_send_cmd(device, ST7567_CMD_RESET);
vTaskDelay(pdMS_TO_TICKS(10));
st7567_send_cmd(device, ST7567_CMD_BIAS_1_9);
st7567_send_cmd(device, ST7567_CMD_ADC_NORMAL);
st7567_send_cmd(device, ST7567_CMD_COM_REVERSE);
st7567_send_cmd(device, ST7567_CMD_RESISTOR_RATIO | 0x03);
st7567_send_cmd(device, ST7567_CMD_POWER_CTRL | 0x07); // booster, regulator, follower all on
vTaskDelay(pdMS_TO_TICKS(10)); // let the charge pump stabilize before touching contrast
st7567_set_contrast(device, config->contrast ? config->contrast : ST7567_DEFAULT_CONTRAST);
st7567_send_cmd(device, ST7567_CMD_START_LINE | 0x00);
st7567_send_cmd(device, ST7567_CMD_ALL_PIXEL_NORMAL);
st7567_send_cmd(device, ST7567_CMD_DISPLAY_NORMAL);
st7567_send_cmd(device, ST7567_CMD_DISPLAY_ON);
ESP_LOGI(TAG, "display init done");
return ESP_OK;
}
#endif
esp_err_t st7567_set_contrast(const st7567_device_t *dev, uint8_t contrast) { esp_err_t st7567_set_contrast(const st7567_device_t *dev, uint8_t contrast) {
if (contrast > 63) if (contrast > 63)
contrast = 63; contrast = 63;
esp_err_t ret = st7567_send_cmd(dev, ST7567_CMD_CONTRAST_MODE); const esp_err_t ret = st7567_send_cmd(dev, ST7567_CMD_CONTRAST_MODE);
if (ret != ESP_OK) if (ret != ESP_OK)
return ret; return ret;
return st7567_send_cmd(dev, contrast); return st7567_send_cmd(dev, contrast);
} }
void st7567_clear_buffer(st7567_device_t *dev) { void st7567_clear_buffer(st7567_device_t *dev) {
memset(dev->framebuffer, 0, sizeof(dev->framebuffer)); memset(dev->framebuffer, 0, sizeof(dev->framebuffer));
} }
esp_err_t st7567_clear(st7567_device_t *dev) { esp_err_t st7567_clear(st7567_device_t *dev) {
st7567_clear_buffer(dev); st7567_clear_buffer(dev);
return st7567_update_screen(dev); return st7567_update_screen(dev);
} }
esp_err_t st7567_update_screen(const st7567_device_t *dev) { esp_err_t st7567_update_screen(const st7567_device_t *dev) {
// ST7567 is page-addressed: 8 pages of 8 vertical px each, byte = one column for (uint8_t page = 0; page < ST7567_PAGES; page++) {
// within a page (LSB = top row of the page). Same layout as SSD1306. esp_err_t ret;
for (uint8_t page = 0; page < ST7567_PAGES; page++) { if ((ret = st7567_send_cmd(dev, ST7567_CMD_PAGE_ADDR | page)) != ESP_OK)
esp_err_t ret; return ret;
if ((ret = st7567_send_cmd(dev, ST7567_CMD_PAGE_ADDR | page)) != ESP_OK) return ret; if ((ret = st7567_send_cmd(dev, ST7567_CMD_COL_ADDR_HIGH | 0x00)) != ESP_OK)
if ((ret = st7567_send_cmd(dev, ST7567_CMD_COL_ADDR_HIGH | 0x00)) != ESP_OK) return ret; return ret;
if ((ret = st7567_send_cmd(dev, ST7567_CMD_COL_ADDR_LOW | 0x00)) != ESP_OK) return ret; if ((ret = st7567_send_cmd(dev, ST7567_CMD_COL_ADDR_LOW | 0x00)) != ESP_OK)
if ((ret = st7567_send_data(dev, &dev->framebuffer[page * ST7567_WIDTH], ST7567_WIDTH)) != ESP_OK) return ret; return ret;
} if ((ret = st7567_send_data(dev, &dev->framebuffer[page * ST7567_WIDTH], ST7567_WIDTH)) != ESP_OK)
return ret;
}
return ESP_OK; return ESP_OK;
} }
static void st7567_set_pixel_color(st7567_device_t *dev, const uint8_t x, const uint8_t y, const uint8_t color) { static void st7567_set_pixel_color(st7567_device_t *dev, const uint8_t x, const uint8_t y, const uint8_t color) {
if (x >= ST7567_WIDTH || y >= ST7567_HEIGHT) if (x >= ST7567_WIDTH || y >= ST7567_HEIGHT)
return; return;
const uint16_t byte_index = (y / 8) * ST7567_WIDTH + x; const uint16_t byte_index = (y / 8) * ST7567_WIDTH + x;
const uint8_t bit_index = y % 8; const uint8_t bit_index = y % 8;
if (color) if (color)
dev->framebuffer[byte_index] |= (1 << bit_index); dev->framebuffer[byte_index] |= (1 << bit_index);
else else
dev->framebuffer[byte_index] &= ~(1 << bit_index); dev->framebuffer[byte_index] &= ~(1 << bit_index);
} }
void st7567_draw_pixel(void *dev, const uint8_t x, const uint8_t y) { void st7567_draw_pixel(void *dev, const uint8_t x, const uint8_t y) {
st7567_set_pixel_color(dev, x, y, 1); st7567_set_pixel_color(dev, x, y, 1);
} }
void st7567_clear_pixel(void *dev, const uint8_t x, const uint8_t y) { void st7567_clear_pixel(void *dev, const uint8_t x, const uint8_t y) {
st7567_set_pixel_color(dev, x, y, 0); st7567_set_pixel_color(dev, x, y, 0);
} }

View File

@@ -3,6 +3,16 @@
#include <esp_err.h> #include <esp_err.h>
#include <stdint.h> #include <stdint.h>
#include <soc/gpio_num.h> #include <soc/gpio_num.h>
#include <driver/spi_master.h>
#include <driver/i2c_master.h>
#include <driver/gpio.h>
// Display dimensions
#define ST7567_WIDTH 128
#define ST7567_HEIGHT 64
#define ST7567_PAGES (ST7567_HEIGHT / 8)
#define ST7567_DEFAULT_CONTRAST 0x35
// ST7567 command set // ST7567 command set
#define ST7567_CMD_DISPLAY_OFF 0xAE #define ST7567_CMD_DISPLAY_OFF 0xAE
@@ -28,36 +38,62 @@
#define ST7567_CMD_STATIC_INDICATOR_OFF 0xAC #define ST7567_CMD_STATIC_INDICATOR_OFF 0xAC
#define ST7567_CMD_NOP 0xE3 #define ST7567_CMD_NOP 0xE3
// Display dimensions #define ST7567_I2C_ADDR_DEFAULT 0x3f
#define ST7567_WIDTH 128 #define ST7567_I2C_CTRL_CMD 0x00
#define ST7567_HEIGHT 64 #define ST7567_I2C_CTRL_DATA 0x40
#define ST7567_PAGES (ST7567_HEIGHT / 8) #define ST7567_I2C_TIMEOUT_MS 100
#define ST7567_DEFAULT_I2C_CLOCK_HZ 400000
#define ST7567_SPI_TIMEOUT_MS 100
#define ST7567_DEFAULT_SPI_CLOCK_HZ 8000000
#define ST7567_DC_CMD 0
#define ST7567_DC_DATA 1
typedef enum {
ST7567_BUS_TYPE_I2C,
ST7567_BUS_TYPE_SPI,
} st7567_bus_type_t;
typedef struct { typedef struct {
void* bus; void *bus;
gpio_num_t dc_gpio; st7567_bus_type_t bus_type;
uint8_t framebuffer[ST7567_WIDTH * ST7567_PAGES]; gpio_num_t dc_gpio;
uint8_t framebuffer[ST7567_WIDTH * ST7567_PAGES];
} st7567_device_t; } st7567_device_t;
/** typedef struct {
* @brief Send a single command byte to the ST7567 i2c_master_bus_handle_t i2c_bus; // already-initialized I2C bus
* uint16_t i2c_addr; // 7-bit device address, 0 = use ST7567_I2C_ADDR_DEFAULT
* @param dev Device handle uint32_t i2c_clock_speed_hz; // 0 = use 400kHz default
* @param cmd Command byte uint8_t contrast; // 0-63, 0 = use a sane default (0x20)
* @return esp_err_t ESP_OK on success } st7567_config_i2c_t;
*/
esp_err_t st7567_send_cmd(const st7567_device_t *dev, uint8_t cmd); typedef struct {
spi_host_device_t spi_host; // already-initialized SPI bus (spi_bus_initialize called by caller)
gpio_num_t cs_gpio; // chip select
gpio_num_t dc_gpio; // data/command select (required, no in-band control byte over SPI)
gpio_num_t rst_gpio; // hardware reset, GPIO_NUM_NC if not wired
uint32_t spi_clock_speed_hz; // 0 = use 8MHz default
uint8_t contrast; // 0-63, 0 = use a sane default (0x20)
} st7567_config_spi_t;
/** /**
* @brief Send a data buffer to the ST7567 * @brief Initialize ST7567 display on an I2C bus
* *
* @param dev Device handle * @param config Configuration structure
* @param data Pointer to data buffer * @param device Pointer to store device state (framebuffer etc, caller-owned)
* @param len Length of data buffer
* @return esp_err_t ESP_OK on success * @return esp_err_t ESP_OK on success
*/ */
esp_err_t st7567_send_data(const st7567_device_t *dev, const uint8_t *data, size_t len); esp_err_t st7567_init_i2c(const st7567_config_i2c_t *config, st7567_device_t *device);
/**
* @brief Initialize ST7567 display on an SPI bus
*
* @param config Configuration structure
* @param device Pointer to store device state (framebuffer etc, caller-owned)
* @return esp_err_t ESP_OK on success
*/
esp_err_t st7567_init_spi(const st7567_config_spi_t *config, st7567_device_t *device);
/** /**
* @brief Clear the display (just wipe framebuffer, don't send any commands) * @brief Clear the display (just wipe framebuffer, don't send any commands)