text/plain
•
9.48 KB
•
339 lines
#include "st7735.h"
#include "../src/moon.h"
#include "buttons.h"
#include "pinout.h"
#include "utils.h"
#include <string.h>
#include "driver/gpio.h"
#include "driver/ledc.h"
#include "driver/spi_master.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
// ST7735 commands
#define ST7735_SWRESET 0x01
#define ST7735_SLPIN 0x10
#define ST7735_SLPOUT 0x11
#define ST7735_INVON 0x21
#define ST7735_DISPOFF 0x28
#define ST7735_DISPON 0x29
#define ST7735_CASET 0x2A
#define ST7735_RASET 0x2B
#define ST7735_RAMWR 0x2C
#define ST7735_MADCTL 0x36
#define ST7735_COLMOD 0x3A
// Display offsets (adjust for your specific module)
#define X_OFFSET 0
#define Y_OFFSET 0
static spi_device_handle_t spi_dev;
static bool display_sleeping = false;
#define LEDC_TIMER LEDC_TIMER_0
#define LEDC_MODE LEDC_LOW_SPEED_MODE
#define LEDC_OUTPUT_IO (PIN_LED) // Pin 15
#define LEDC_CHANNEL LEDC_CHANNEL_0
#define LEDC_DUTY_RES LEDC_TIMER_13_BIT // Resolution: 2^13 = 8192 levels
#define LEDC_FREQUENCY (8192) // Frequency in Hertz
#define BRIGHTNESS_UPDATE_INTERVAL 12
uint32_t backlight_last_update = 0;
uint8_t backlight_goal = 0;
uint8_t backlight_current = 0;
static void init_backlight() {
// 1. Prepare and then apply the LEDC PWM timer configuration
ledc_timer_config_t ledc_timer = {.speed_mode = LEDC_MODE,
.timer_num = LEDC_TIMER,
.duty_resolution = LEDC_DUTY_RES,
.freq_hz = LEDC_FREQUENCY,
.clk_cfg = LEDC_AUTO_CLK};
ledc_timer_config(&ledc_timer);
// 2. Prepare and then apply the LEDC PWM channel configuration
ledc_channel_config_t ledc_channel = {.speed_mode = LEDC_MODE,
.channel = LEDC_CHANNEL,
.timer_sel = LEDC_TIMER,
.intr_type = LEDC_INTR_DISABLE,
.gpio_num = LEDC_OUTPUT_IO,
.duty = 0, // Start at 0% brightness
.hpoint = 0};
ledc_channel_config(&ledc_channel);
}
static void st7735_write_cmd(uint8_t cmd);
void st7735_sleep(void) {
if (display_sleeping) {
return;
}
st7735_write_cmd(ST7735_DISPOFF);
st7735_write_cmd(ST7735_SLPIN);
vTaskDelay(pdMS_TO_TICKS(120));
display_sleeping = true;
}
void st7735_wake(void) {
if (!display_sleeping) {
return;
}
st7735_write_cmd(ST7735_SLPOUT);
vTaskDelay(pdMS_TO_TICKS(120));
st7735_write_cmd(ST7735_DISPON);
display_sleeping = false;
}
void set_backlight_brightness(uint8_t level) {
if (level == 0 && !display_sleeping) {
st7735_sleep();
} else if (level > 0 && display_sleeping) {
st7735_wake();
}
uint32_t duty = (uint32_t)level << 5;
ledc_set_duty(LEDC_MODE, LEDC_CHANNEL, duty);
ledc_update_duty(LEDC_MODE, LEDC_CHANNEL);
}
void update_backlight_brightness() {
while (backlight_goal > DISPLAY_DIM_BRIGHTNESS &&
buttons_duration_released > DISPLAY_DIM_AFTER_MS_INACTIVITY) {
buttons_duration_released -= DISPLAY_DIM_AFTER_MS_STEP;
backlight_goal--;
}
while (backlight_goal > DISPLAY_SLEEP_BRIGHTNESS &&
buttons_duration_released > DISPLAY_SLEEP_AFTER_MS_INACTIVITY) {
buttons_duration_released -= DISPLAY_SLEEP_AFTER_MS_STEP;
backlight_goal--;
}
if (backlight_goal == backlight_current) {
return;
}
const uint32_t now = get_millis();
const uint8_t dir = (backlight_goal - backlight_current) > 0 ? 1 : -1;
while ((backlight_last_update + BRIGHTNESS_UPDATE_INTERVAL) < now) {
backlight_last_update += BRIGHTNESS_UPDATE_INTERVAL;
backlight_current += dir;
if (backlight_goal == backlight_current) {
break;
}
}
set_backlight_brightness(backlight_current);
}
void set_display_brightness(uint8_t level) {
backlight_last_update = get_millis();
backlight_goal = level;
}
// Pre-transfer callback to set DC pin
static void IRAM_ATTR spi_pre_transfer_cb(spi_transaction_t* t) {
int dc = (int)t->user;
gpio_set_level(PIN_DC, dc);
}
static void st7735_write_cmd(uint8_t cmd) {
spi_transaction_t t = {
.length = 8,
.tx_buffer = &cmd,
.user = (void*)0, // DC=0 for command
};
spi_device_polling_transmit(spi_dev, &t);
}
static void st7735_write_data(const uint8_t* data, size_t len) {
if (len == 0) {
return;
}
spi_transaction_t t = {
.length = len * 8,
.tx_buffer = data,
.user = (void*)1, // DC=1 for data
};
spi_device_polling_transmit(spi_dev, &t);
}
static void st7735_write_data_byte(uint8_t data) {
st7735_write_data(&data, 1);
}
void st7735_init(void) {
// Configure GPIO pins
gpio_config_t io_conf = {
.pin_bit_mask = (1ULL << PIN_DC) | (1ULL << PIN_RST),
.mode = GPIO_MODE_OUTPUT,
.pull_up_en = GPIO_PULLUP_DISABLE,
.pull_down_en = GPIO_PULLDOWN_DISABLE,
.intr_type = GPIO_INTR_DISABLE,
};
gpio_config(&io_conf);
init_backlight();
// Hardware reset
gpio_set_level(PIN_RST, 0);
vTaskDelay(pdMS_TO_TICKS(1));
gpio_set_level(PIN_RST, 1);
vTaskDelay(pdMS_TO_TICKS(5));
// Configure SPI bus
spi_bus_config_t buscfg = {
.mosi_io_num = PIN_MOSI,
.miso_io_num = -1,
.sclk_io_num = PIN_SCK,
.quadwp_io_num = -1,
.quadhd_io_num = -1,
.max_transfer_sz = 160 * 128 * 2, // Full frame at 2 bytes/pixel (RGB565)
};
spi_bus_initialize(SPI2_HOST, &buscfg, SPI_DMA_CH_AUTO);
// Configure SPI device
spi_device_interface_config_t devcfg = {
.clock_speed_hz = 20 * 1000 * 1000, // 20 MHz (APB_CLK/4)
.mode = 0,
.spics_io_num = PIN_CS,
.queue_size = 7,
.pre_cb = spi_pre_transfer_cb,
};
spi_bus_add_device(SPI2_HOST, &devcfg, &spi_dev);
// ST7735 initialization sequence
st7735_write_cmd(ST7735_SWRESET);
vTaskDelay(pdMS_TO_TICKS(5));
st7735_write_cmd(ST7735_SLPOUT);
vTaskDelay(pdMS_TO_TICKS(10));
// Set color mode to RGB565 (16-bit, 2 bytes per pixel)
st7735_write_cmd(ST7735_COLMOD);
st7735_write_data_byte(0x05); // 16-bit RGB565
// Set display orientation (MV + MX for landscape 160x128)
st7735_write_cmd(ST7735_MADCTL);
st7735_write_data_byte(0x60);
// Enable display
st7735_write_cmd(ST7735_DISPON);
}
void st7735_set_window(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1) {
// Column address set
st7735_write_cmd(ST7735_CASET);
uint8_t caset_data[] = {0, (uint8_t)(x0 + X_OFFSET), 0,
(uint8_t)(x1 + X_OFFSET)};
st7735_write_data(caset_data, 4);
// Row address set
st7735_write_cmd(ST7735_RASET);
uint8_t raset_data[] = {0, (uint8_t)(y0 + Y_OFFSET), 0,
(uint8_t)(y1 + Y_OFFSET)};
st7735_write_data(raset_data, 4);
// Write to RAM
st7735_write_cmd(ST7735_RAMWR);
}
void st7735_write_pixels(const uint16_t* data, size_t len) {
st7735_write_data((const uint8_t*)data, len * 2);
}
// Static transaction pool and buffers for fully-async flush pipeline
// ESP32-S3 SPI hardware max is 2^18 bits = 32768 bytes per transaction
#define SPI_MAX_DMA_BYTES 32768
static spi_transaction_t win_trans[5];
static spi_transaction_t px_trans[2];
static int px_trans_count;
static uint8_t cmd_caset = ST7735_CASET;
static uint8_t cmd_raset = ST7735_RASET;
static uint8_t cmd_ramwr = ST7735_RAMWR;
static uint8_t caset_data[4];
static uint8_t raset_data[4];
void st7735_flush_async(uint16_t x0,
uint16_t y0,
uint16_t x1,
uint16_t y1,
const uint16_t* pixels,
size_t pixel_count) {
// Fill coordinate buffers
caset_data[0] = 0;
caset_data[1] = (uint8_t)(x0 + X_OFFSET);
caset_data[2] = 0;
caset_data[3] = (uint8_t)(x1 + X_OFFSET);
raset_data[0] = 0;
raset_data[1] = (uint8_t)(y0 + Y_OFFSET);
raset_data[2] = 0;
raset_data[3] = (uint8_t)(y1 + Y_OFFSET);
// CASET command
memset(&win_trans[0], 0, sizeof(spi_transaction_t));
win_trans[0].length = 8;
win_trans[0].tx_buffer = &cmd_caset;
win_trans[0].user = (void*)0; // DC=0
// CASET data
memset(&win_trans[1], 0, sizeof(spi_transaction_t));
win_trans[1].length = 32;
win_trans[1].tx_buffer = caset_data;
win_trans[1].user = (void*)1; // DC=1
// RASET command
memset(&win_trans[2], 0, sizeof(spi_transaction_t));
win_trans[2].length = 8;
win_trans[2].tx_buffer = &cmd_raset;
win_trans[2].user = (void*)0; // DC=0
// RASET data
memset(&win_trans[3], 0, sizeof(spi_transaction_t));
win_trans[3].length = 32;
win_trans[3].tx_buffer = raset_data;
win_trans[3].user = (void*)1; // DC=1
// RAMWR command
memset(&win_trans[4], 0, sizeof(spi_transaction_t));
win_trans[4].length = 8;
win_trans[4].tx_buffer = &cmd_ramwr;
win_trans[4].user = (void*)0; // DC=0
// Split pixel data into chunks that fit the SPI hardware limit
size_t total_bytes = pixel_count * 2;
size_t offset = 0;
px_trans_count = 0;
while (offset < total_bytes) {
size_t chunk = total_bytes - offset;
if (chunk > SPI_MAX_DMA_BYTES)
chunk = SPI_MAX_DMA_BYTES;
memset(&px_trans[px_trans_count], 0, sizeof(spi_transaction_t));
px_trans[px_trans_count].length = chunk * 8;
px_trans[px_trans_count].tx_buffer = (const uint8_t*)pixels + offset;
px_trans[px_trans_count].user = (void*)1; // DC=1
px_trans_count++;
offset += chunk;
}
// Queue window setup + pixel transactions
for (int i = 0; i < 5; i++) {
spi_device_queue_trans(spi_dev, &win_trans[i], portMAX_DELAY);
}
for (int i = 0; i < px_trans_count; i++) {
spi_device_queue_trans(spi_dev, &px_trans[i], portMAX_DELAY);
}
}
void st7735_flush_wait(void) {
spi_transaction_t* result;
for (int i = 0; i < 5 + px_trans_count; i++) {
spi_device_get_trans_result(spi_dev, &result, portMAX_DELAY);
}
}