diff --git a/README.md b/README.md index 34c3c41..ef7cfcf 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ For both SPI and I2C displays I2C version ```c -#include +#include i2c_master_bus_config_t bus_cfg = { .i2c_port = I2C_NUM_0, @@ -21,15 +21,15 @@ i2c_master_bus_config_t bus_cfg = { i2c_master_bus_handle_t bus; i2c_new_master_bus(&bus_cfg, &bus); -st7567_config_t cfg = { +st7567_config_i2c_t cfg = { .i2c_bus = bus, .i2c_addr = ST7567_I2C_ADDR_DEFAULT, // 0x3F, .i2c_clock_speed_hz = 400000, .contrast = 0x38, // good for generic modules at 3.3v }; + st7567_device_t dev; -st7567_init(&cfg, &dev); -st7567_set_backlight(&dev, true); +st7567_init_i2c(&cfg, &dev); st7567_draw_pixel(&dev, 10, 10); st7567_update_screen(&dev); @@ -38,11 +38,29 @@ st7567_update_screen(&dev); SPI version ```c -#include +#include + +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_init(&cfg, &dev); -st7567_set_backlight(&dev, true); +st7567_init_spi(&cfg, &dev); st7567_draw_pixel(&dev, 10, 10); st7567_update_screen(&dev); diff --git a/idf_component.yml b/idf_component.yml index 48367af..cb6f83d 100644 --- a/idf_component.yml +++ b/idf_component.yml @@ -1,4 +1,4 @@ -version: "1.1.1" +version: "2.0.0" description: "ST7567 128x64 display driver" url: "https://git.drath.cc/drath/esp-st7567" dependencies: diff --git a/st7567/st7567-i2c.h b/st7567/st7567-i2c.h deleted file mode 100644 index 5e2608e..0000000 --- a/st7567/st7567-i2c.h +++ /dev/null @@ -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 diff --git a/st7567/st7567-spi.h b/st7567/st7567-spi.h deleted file mode 100644 index 4181e69..0000000 --- a/st7567/st7567-spi.h +++ /dev/null @@ -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 diff --git a/st7567/st7567.c b/st7567/st7567.c index 009fec9..b18b994 100644 --- a/st7567/st7567.c +++ b/st7567/st7567.c @@ -1,238 +1,201 @@ -#include "st7567-common.h" +#include "st7567.h" #include #include "esp_log.h" #include "freertos/FreeRTOS.h" #include "freertos/task.h" +#include static const char *TAG = "ST7567"; -#ifdef ST7567_I2C_H -#define ST7567_I2C_CTRL_CMD 0x00 -#define ST7567_I2C_CTRL_DATA 0x40 +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; -#define ST7567_I2C_TIMEOUT_MS 100 -#define ST7567_DEFAULT_I2C_CLOCK_HZ 400000 - -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); + 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) { - return st7567_write(dev, ST7567_I2C_CTRL_CMD, &cmd, 1); -} +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; -esp_err_t st7567_send_data(const st7567_device_t *dev, const uint8_t *data, size_t len) { - return st7567_write(dev, ST7567_I2C_CTRL_DATA, data, len); + 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); } -esp_err_t st7567_init(const st7567_i2c_config_t *config, st7567_device_t *device) { - memset(device, 0, sizeof(st7567_device_t)); // clears framebuffer too +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); +} - // 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, - }; +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); +} - 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; - } +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 - // 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_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); - 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; - ESP_LOGI(TAG, "display init done"); - return ESP_OK; + // 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; } -#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 -#define ST7567_DEFAULT_SPI_CLOCK_HZ 8000000 + 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; + } -#define ST7567_DC_CMD 0 -#define ST7567_DC_DATA 1 + gpio_set_level(config->rst_gpio, 0); + 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) { - if (len == 0) - return ESP_OK; + // 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, + }; - 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 = { - .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); + st7567_send_init(device, config->contrast); + ESP_LOGI(TAG, "display init done"); + return ESP_OK; } - -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) { - if (contrast > 63) - contrast = 63; + if (contrast > 63) + contrast = 63; - esp_err_t ret = st7567_send_cmd(dev, ST7567_CMD_CONTRAST_MODE); - if (ret != ESP_OK) - return ret; + 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); + return st7567_send_cmd(dev, contrast); } 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) { - st7567_clear_buffer(dev); - return st7567_update_screen(dev); + st7567_clear_buffer(dev); + 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; - } + 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; + 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; + 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; + 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); + 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); + 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); -} + st7567_set_pixel_color(dev, x, y, 0); +} \ No newline at end of file diff --git a/st7567/st7567-common.h b/st7567/st7567.h similarity index 58% rename from st7567/st7567-common.h rename to st7567/st7567.h index 32a5710..d48454b 100644 --- a/st7567/st7567-common.h +++ b/st7567/st7567.h @@ -3,6 +3,16 @@ #include #include #include +#include +#include +#include + +// Display dimensions +#define ST7567_WIDTH 128 +#define ST7567_HEIGHT 64 +#define ST7567_PAGES (ST7567_HEIGHT / 8) + +#define ST7567_DEFAULT_CONTRAST 0x35 // ST7567 command set #define ST7567_CMD_DISPLAY_OFF 0xAE @@ -28,36 +38,62 @@ #define ST7567_CMD_STATIC_INDICATOR_OFF 0xAC #define ST7567_CMD_NOP 0xE3 -// Display dimensions -#define ST7567_WIDTH 128 -#define ST7567_HEIGHT 64 -#define ST7567_PAGES (ST7567_HEIGHT / 8) +#define ST7567_I2C_ADDR_DEFAULT 0x3f +#define ST7567_I2C_CTRL_CMD 0x00 +#define ST7567_I2C_CTRL_DATA 0x40 +#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 { - void* bus; - gpio_num_t dc_gpio; - uint8_t framebuffer[ST7567_WIDTH * ST7567_PAGES]; + void *bus; + st7567_bus_type_t bus_type; + gpio_num_t dc_gpio; + uint8_t framebuffer[ST7567_WIDTH * ST7567_PAGES]; } st7567_device_t; -/** - * @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); +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_i2c_t; + +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 data Pointer to data buffer - * @param len Length of data buffer + * @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_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)