201 lines
6.6 KiB
C
201 lines
6.6 KiB
C
#include "st7567.h"
|
|
#include <string.h>
|
|
#include "esp_log.h"
|
|
#include "freertos/FreeRTOS.h"
|
|
#include "freertos/task.h"
|
|
#include <driver/i2c_master.h>
|
|
|
|
static const char *TAG = "ST7567";
|
|
|
|
|
|
static esp_err_t st7567_write_i2c(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);
|
|
}
|
|
|
|
static esp_err_t st7567_write_spi(const st7567_device_t *dev, int dc_level, const uint8_t *data, size_t len) {
|
|
if (len == 0)
|
|
return ESP_OK;
|
|
|
|
gpio_set_level(dev->dc_gpio, dc_level);
|
|
|
|
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);
|
|
}
|
|
|
|
|
|
inline static esp_err_t st7567_send_cmd(const st7567_device_t *dev, const uint8_t cmd) {
|
|
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);
|
|
}
|
|
|
|
inline static esp_err_t st7567_send_data(const st7567_device_t *dev, const uint8_t *data, size_t len) {
|
|
if (dev->bus_type == ST7567_BUS_TYPE_SPI)
|
|
return st7567_write_spi(dev, ST7567_DC_DATA, data, len);
|
|
return st7567_write_i2c(dev, ST7567_I2C_CTRL_DATA, data, len);
|
|
}
|
|
|
|
static void st7567_send_init(const st7567_device_t *device, const uint8_t contrast) {
|
|
// 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, contrast ? 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_err_t st7567_init_i2c(const st7567_config_i2c_t *config, st7567_device_t *device) {
|
|
memset(device, 0, sizeof(st7567_device_t)); // clears framebuffer too
|
|
device->bus_type = ST7567_BUS_TYPE_I2C;
|
|
|
|
// 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_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;
|
|
}
|
|
|
|
|
|
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;
|
|
|
|
// 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;
|
|
}
|
|
|
|
if (config->rst_gpio != GPIO_NUM_NC) {
|
|
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;
|
|
}
|
|
|
|
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 ? (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,
|
|
};
|
|
|
|
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;
|
|
}
|
|
|
|
st7567_send_init(device, config->contrast);
|
|
ESP_LOGI(TAG, "display init done");
|
|
return ESP_OK;
|
|
}
|
|
|
|
esp_err_t st7567_set_contrast(const st7567_device_t *dev, uint8_t contrast) {
|
|
if (contrast > 63)
|
|
contrast = 63;
|
|
|
|
const esp_err_t ret = st7567_send_cmd(dev, ST7567_CMD_CONTRAST_MODE);
|
|
if (ret != ESP_OK)
|
|
return ret;
|
|
|
|
return st7567_send_cmd(dev, contrast);
|
|
}
|
|
|
|
void st7567_clear_buffer(st7567_device_t *dev) {
|
|
memset(dev->framebuffer, 0, sizeof(dev->framebuffer));
|
|
}
|
|
|
|
esp_err_t st7567_clear(st7567_device_t *dev) {
|
|
st7567_clear_buffer(dev);
|
|
return st7567_update_screen(dev);
|
|
}
|
|
|
|
esp_err_t st7567_update_screen(const st7567_device_t *dev) {
|
|
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);
|
|
} |