first commit

This commit is contained in:
2026-08-06 21:23:40 +02:00
commit a91786b25a
5 changed files with 285 additions and 0 deletions

5
CMakeLists.txt Normal file
View File

@@ -0,0 +1,5 @@
idf_component_register(
SRCS "st7567.c"
INCLUDE_DIRS "."
REQUIRES esp_driver_i2c
)

30
README.md Normal file
View File

@@ -0,0 +1,30 @@
# ST7567 I2C display driver
For esp-idf, same idea as the st7920 driver
## Usage
```c
i2c_master_bus_config_t bus_cfg = {
.i2c_port = I2C_NUM_0,
.sda_io_num = GPIO_NUM_21,
.scl_io_num = GPIO_NUM_22,
.clk_source = I2C_CLK_SRC_DEFAULT,
.glitch_ignore_cnt = 7,
.flags.enable_internal_pullup = true,
};
i2c_master_bus_handle_t bus;
i2c_new_master_bus(&bus_cfg, &bus);
st7567_config_t cfg = {
.i2c_bus = bus,
.i2c_addr = ST7567_I2C_ADDR_DEFAULT, // 0x3F,
.i2c_clock_speed_hz = 400000,
.contrast = 0x39, // good for generic modules at 3.3v
};
st7567_device_t dev;
st7567_init(&cfg, &dev);
st7567_set_backlight(&dev, true);
st7567_draw_pixel(&dev, 10, 10);
st7567_update_screen(&dev);
```

5
idf_component.yml Normal file
View File

@@ -0,0 +1,5 @@
version: "1.0.0"
description: "ST7567 I2C 128x64 display driver"
url: "https://git.drath.cc/you/esp-st7567"
dependencies:
idf: ">=5.2"

123
st7567.c Normal file
View File

@@ -0,0 +1,123 @@
#include "st7567.h"
#include <string.h>
#include "esp_log.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
static const char *TAG = "ST7567";
// I2C control byte: bit7 = Co (0 = stream, no further control bytes needed),
// bit6 = D/C# (0 = command, 1 = data). Same convention as SSD1306-family displays.
#define ST7567_I2C_CTRL_CMD 0x00
#define ST7567_I2C_CTRL_DATA 0x40
#define ST7567_I2C_TIMEOUT_MS 100
#define ST7567_DEFAULT_CLOCK_HZ 400000
#define ST7567_DEFAULT_CONTRAST 0x20
static esp_err_t st7567_i2c_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->i2c_dev, buf, len + 1, ST7567_I2C_TIMEOUT_MS);
}
esp_err_t st7567_send_cmd(const st7567_device_t *dev, const uint8_t cmd) {
return st7567_i2c_write(dev, ST7567_I2C_CTRL_CMD, &cmd, 1);
}
esp_err_t st7567_send_data(const st7567_device_t *dev, const uint8_t *data, size_t len) {
return st7567_i2c_write(dev, ST7567_I2C_CTRL_DATA, data, len);
}
esp_err_t st7567_set_contrast(const st7567_device_t *dev, uint8_t contrast) {
if (contrast > 63)
contrast = 63;
esp_err_t ret = st7567_send_cmd(dev, ST7567_CMD_CONTRAST_MODE);
if (ret != ESP_OK)
return ret;
return st7567_send_cmd(dev, contrast);
}
esp_err_t st7567_init(const st7567_config_t *config, st7567_device_t *device) {
memset(device, 0, sizeof(st7567_device_t)); // clears framebuffer too
// Add device on the (already-initialized) I2C bus
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_CLOCK_HZ,
};
const esp_err_t ret = i2c_master_bus_add_device(config->i2c_bus, &dev_cfg, &device->i2c_dev);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "Failed to add I2C 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;
}
esp_err_t st7567_clear(st7567_device_t *dev) {
memset(dev->framebuffer, 0, sizeof(dev->framebuffer));
return st7567_update_screen(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
// within a page (LSB = top row of the page). Same layout as SSD1306.
for (uint8_t page = 0; page < ST7567_PAGES; page++) {
esp_err_t 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) return ret;
if ((ret = st7567_send_cmd(dev, ST7567_CMD_COL_ADDR_LOW | 0x00)) != ESP_OK) return ret;
if ((ret = st7567_send_data(dev, &dev->framebuffer[page * ST7567_WIDTH], ST7567_WIDTH)) != ESP_OK) return ret;
}
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) {
if (x >= ST7567_WIDTH || y >= ST7567_HEIGHT)
return;
const uint16_t byte_index = (y / 8) * ST7567_WIDTH + x;
const uint8_t bit_index = y % 8;
if (color)
dev->framebuffer[byte_index] |= (1 << bit_index);
else
dev->framebuffer[byte_index] &= ~(1 << bit_index);
}
void st7567_draw_pixel(void *dev, const uint8_t x, const uint8_t y) {
st7567_set_pixel_color(dev, x, y, 1);
}
void st7567_clear_pixel(void *dev, const uint8_t x, const uint8_t y) {
st7567_set_pixel_color(dev, x, y, 0);
}

122
st7567.h Normal file
View File

@@ -0,0 +1,122 @@
#ifndef ST7567_H
#define ST7567_H
#include "esp_err.h"
#include "driver/i2c_master.h"
// Display dimensions
#define ST7567_WIDTH 128
#define ST7567_HEIGHT 64
#define ST7567_PAGES (ST7567_HEIGHT / 8)
// Default I2C 7-bit address for most ST7567 breakout boards (some use 0x3F)
#define ST7567_I2C_ADDR_DEFAULT 0x3F
// ST7567 command set
#define ST7567_CMD_DISPLAY_OFF 0xAE
#define ST7567_CMD_DISPLAY_ON 0xAF
#define ST7567_CMD_START_LINE 0x40 // OR with 0-63
#define ST7567_CMD_PAGE_ADDR 0xB0 // OR with 0-8
#define ST7567_CMD_COL_ADDR_HIGH 0x10 // OR with high nibble
#define ST7567_CMD_COL_ADDR_LOW 0x00 // OR with low nibble
#define ST7567_CMD_ADC_NORMAL 0xA0 // column addr 0 = SEG0
#define ST7567_CMD_ADC_REVERSE 0xA1 // column addr 131 = SEG0
#define ST7567_CMD_ALL_PIXEL_NORMAL 0xA4
#define ST7567_CMD_ALL_PIXEL_ON 0xA5
#define ST7567_CMD_DISPLAY_NORMAL 0xA6
#define ST7567_CMD_DISPLAY_INVERSE 0xA7
#define ST7567_CMD_BIAS_1_9 0xA2
#define ST7567_CMD_BIAS_1_7 0xA3
#define ST7567_CMD_RESET 0xE2
#define ST7567_CMD_COM_NORMAL 0xC0
#define ST7567_CMD_COM_REVERSE 0xC8
#define ST7567_CMD_POWER_CTRL 0x28 // OR with 0-7 (booster/regulator/follower bits)
#define ST7567_CMD_RESISTOR_RATIO 0x20 // OR with 0-7
#define ST7567_CMD_CONTRAST_MODE 0x81 // followed by a second byte, 0-63
#define ST7567_CMD_STATIC_INDICATOR_OFF 0xAC
#define ST7567_CMD_NOP 0xE3
// 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_config_t;
// Handle for ST7567 device
typedef struct {
i2c_master_dev_handle_t i2c_dev;
uint8_t framebuffer[ST7567_WIDTH * ST7567_PAGES];
} st7567_device_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_config_t *config, st7567_device_t *device);
/**
* @brief Send a single command byte to the ST7567
*
* @param dev Device handle
* @param cmd Command byte
* @return esp_err_t ESP_OK on success
*/
esp_err_t st7567_send_cmd(const st7567_device_t *dev, uint8_t cmd);
/**
* @brief Send a data buffer to the ST7567
*
* @param dev Device handle
* @param data Pointer to data buffer
* @param len Length of data buffer
* @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);
/**
* @brief Clear the display (both GDRAM and local framebuffer)
*
* @param dev Device handle
* @return esp_err_t ESP_OK on success
*/
esp_err_t st7567_clear(st7567_device_t *dev);
/**
* @brief Set contrast (electronic volume)
*
* @param dev Device handle
* @param contrast 0-63
* @return esp_err_t ESP_OK on success
*/
esp_err_t st7567_set_contrast(const st7567_device_t *dev, uint8_t contrast);
/**
* @brief Push the local framebuffer contents to the display
*
* @param dev Device handle
* @return esp_err_t ESP_OK on success
*/
esp_err_t st7567_update_screen(const st7567_device_t *dev);
/**
* @brief Turn a pixel ON at given coordinates
* @param dev Device handle
* @param x x coordinate
* @param y y coordinate
*/
void st7567_draw_pixel(void *dev, uint8_t x, uint8_t y);
/**
* @brief Turn a pixel OFF at given coordinates
* @param dev Device handle
* @param x x coordinate
* @param y y coordinate
*/
void st7567_clear_pixel(void *dev, uint8_t x, uint8_t y);
#endif // ST7567_H