Files
esp-st7290/st7920.c
2026-06-27 20:04:13 +02:00

156 lines
5.2 KiB
C

#include "st7920.h"
#include <string.h>
#include "esp_log.h"
#include "driver/gpio.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "rom/ets_sys.h"
/* ST7920 display driver lib
* regretfully a clanker wrote most of this. first try too, good shit claude
* i did make make the code cleaner, less ass in geneneral and optimized it a bit */
static const char *TAG = "ST7920";
// ST7920 SPI protocol uses specific byte sequences
// Format: 11111ABC where ABC identifies sync, R/W, RS bits
#define ST7920_SYNC_CMD 0xF8 // 11111000 - Sync + Command
#define ST7920_SYNC_DATA 0xFA // 11111010 - Sync + Data
// Timing delays (in microseconds)
#define ST7920_DELAY_CMD 72
#define ST7920_DELAY_DATA 72
#define ST7920_DELAY_RESET 100000 // 100ms
static esp_err_t st7920_spi_send(const st7920_device_t *dev, const uint8_t sync_byte, const uint8_t data) {
spi_transaction_t trans = {
.flags = 0,
.length = 24, // 3 bytes = 24 bits
.tx_data = {sync_byte, data & 0xF0, (data << 4) & 0xF0},
.rx_buffer = NULL,
};
trans.flags = SPI_TRANS_USE_TXDATA;
const esp_err_t ret = spi_device_polling_transmit(dev->spi, &trans);
return ret;
}
esp_err_t st7920_send_cmd(const st7920_device_t *dev, const uint8_t cmd) {
const esp_err_t ret = st7920_spi_send(dev, ST7920_SYNC_CMD, cmd);
ets_delay_us(ST7920_DELAY_CMD);
return ret;
}
esp_err_t st7920_send_data(const st7920_device_t *dev, const uint8_t data) {
const esp_err_t ret = st7920_spi_send(dev, ST7920_SYNC_DATA, data);
ets_delay_us(ST7920_DELAY_DATA);
return ret;
}
esp_err_t st7920_init(const st7920_config_t *config, st7920_device_t *device) {
memset(device, 0, sizeof(st7920_device_t)); // clears framebuffer too
device->pin_cs = config->pin_cs;
device->pin_bl = config->pin_bl;
// Configure CS pin
gpio_config_t io_conf = {
.pin_bit_mask = (1ULL << config->pin_cs),
.mode = GPIO_MODE_OUTPUT,
.pull_up_en = GPIO_PULLUP_ENABLE,
.pull_down_en = GPIO_PULLDOWN_DISABLE,
.intr_type = GPIO_INTR_DISABLE,
};
gpio_config(&io_conf);
gpio_set_level(config->pin_cs, 1);
// Configure BL pin if provided
if (config->pin_bl >= 0) {
io_conf.pin_bit_mask = (1ULL << config->pin_bl); // reuse part of ocnfig from CS pin
gpio_config(&io_conf);
st7920_set_backlight(device, 0);
}
// Configure SPI device
const spi_device_interface_config_t dev_cfg = {
.clock_speed_hz = config->spi_clock_speed_hz,
.mode = 3, // CPOL=1, CPHA=1 (required by ST7920)
.spics_io_num = -1, // We manage CS manually
.queue_size = 1,
.flags = SPI_DEVICE_NO_DUMMY,
};
const esp_err_t ret = spi_bus_add_device(config->spi_host, &dev_cfg, &device->spi);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "Failed to add SPI device: %s", esp_err_to_name(ret));
return ret;
}
// start talking to the thing
vTaskDelay(pdMS_TO_TICKS(50)); // Wait after reset
st7920_send_cmd(device, ST7920_CMD_BASIC_FUNC);
vTaskDelay(pdMS_TO_TICKS(1));
st7920_send_cmd(device, ST7920_CMD_BASIC_FUNC);
ets_delay_us(100);
st7920_send_cmd(device, ST7920_CMD_CLEAR);
vTaskDelay(pdMS_TO_TICKS(10)); // Clear needs longer time
st7920_send_cmd(device, ST7920_CMD_ENTRY_MODE);
ets_delay_us(100);
st7920_send_cmd(device, ST7920_CMD_GRAPHIC_ON);
ets_delay_us(100);
st7920_send_cmd(device, ST7920_CMD_DISPLAY_ON);
ets_delay_us(100);
ESP_LOGI(TAG, "display init done");
return ESP_OK;
}
esp_err_t st7920_clear(st7920_device_t *dev) {
memset(dev->framebuffer, 0, sizeof(dev->framebuffer));
// Clear display RAM
st7920_send_cmd(dev, ST7920_CMD_BASIC_FUNC);
st7920_send_cmd(dev, ST7920_CMD_CLEAR);
vTaskDelay(pdMS_TO_TICKS(10));
st7920_send_cmd(dev, ST7920_CMD_GRAPHIC_ON);
return ESP_OK;
}
esp_err_t st7920_set_backlight(const st7920_device_t *dev, const bool backlight) {
return gpio_set_level(dev->pin_bl, backlight);
}
esp_err_t st7920_update_screen(const st7920_device_t *dev) {
// ST7920 is actually two 128x32 disps, we send row by row to each of them
for (uint8_t y = 0; y < 64; y++) {
const bool upper_half = y < 32;
st7920_send_cmd(dev, ST7920_CMD_SET_GRAPHIC_ADDR | (upper_half ? y : y - 32)); // adjust y pos if we're on 2nd screen
st7920_send_cmd(dev, ST7920_CMD_SET_GRAPHIC_ADDR | (upper_half ? 0x00 : 0x08)); // 2nd screen has a different addr
for (uint8_t x = 0; x < 16; x++)
st7920_send_data(dev, dev->framebuffer[y * 16 + x]);
}
return ESP_OK;
}
static void st7920_set_pixel_color(st7920_device_t *dev, const uint8_t x, const uint8_t y, const uint8_t color) {
if (x >= ST7920_WIDTH || y >= ST7920_HEIGHT)
return;
const uint16_t byte_index = (y * (ST7920_WIDTH / 8)) + (x / 8);
const uint8_t bit_index = 7 - (x % 8);
if (color)
dev->framebuffer[byte_index] |= (1 << bit_index);
else
dev->framebuffer[byte_index] &= ~(1 << bit_index);
}
void st7920_draw_pixel(void *dev, const uint8_t x, const uint8_t y) {
st7920_set_pixel_color(dev, x, y, 1);
}
void st7920_clear_pixel(void *dev, const uint8_t x, const uint8_t y) {
st7920_set_pixel_color(dev, x, y, 0);
}