#include "st7920.h" #include #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(void *p) { st7920_device_t* dev = (st7920_device_t*)p; 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 void *p) { const st7920_device_t* dev = (st7920_device_t*)p; // 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); } esp_err_t st7920_draw_pixel(void *dev, const uint8_t x, const uint8_t y) { st7920_set_pixel_color(dev, x, y, 1); return ESP_OK; } esp_err_t st7920_clear_pixel(void *dev, const uint8_t x, const uint8_t y) { st7920_set_pixel_color(dev, x, y, 0); return ESP_OK; }