156 lines
5.2 KiB
C
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);
|
|
} |