244 lines
5.9 KiB
C
244 lines
5.9 KiB
C
#include "../wokwi-api.h"
|
|
|
|
#include <stdbool.h>
|
|
#include <stdint.h>
|
|
#include <stdlib.h>
|
|
|
|
typedef struct {
|
|
buffer_t framebuffer;
|
|
uint32_t width;
|
|
uint32_t height;
|
|
spi_dev_t spi;
|
|
pin_t cs_pin;
|
|
pin_t dc_pin;
|
|
pin_t rst_pin;
|
|
pin_t led_pin;
|
|
uint8_t spi_buffer[128];
|
|
uint8_t current_cmd;
|
|
uint8_t expected_params;
|
|
uint8_t param_index;
|
|
uint8_t params[4];
|
|
bool memory_write;
|
|
bool color_high_pending;
|
|
uint8_t color_high;
|
|
uint16_t col_start;
|
|
uint16_t col_end;
|
|
uint16_t row_start;
|
|
uint16_t row_end;
|
|
uint16_t cursor_col;
|
|
uint16_t cursor_row;
|
|
} main_display_state_t;
|
|
|
|
static main_display_state_t *chip;
|
|
|
|
static uint16_t clamp_u16(uint16_t value, uint16_t limit) {
|
|
return value > limit ? limit : value;
|
|
}
|
|
|
|
static void draw_pixel(uint16_t x, uint16_t y, uint16_t color565) {
|
|
if (x >= chip->width || y >= chip->height) {
|
|
return;
|
|
}
|
|
|
|
uint8_t pixel[4];
|
|
pixel[0] = (uint8_t)(((color565 >> 11) & 0x1F) << 3);
|
|
pixel[1] = (uint8_t)(((color565 >> 5) & 0x3F) << 2);
|
|
pixel[2] = (uint8_t)((color565 & 0x1F) << 3);
|
|
pixel[3] = 255;
|
|
buffer_write(chip->framebuffer, ((y * chip->width) + x) * 4, pixel, 4);
|
|
}
|
|
|
|
static void fill_screen(uint16_t color565) {
|
|
for (uint32_t y = 0; y < chip->height; ++y) {
|
|
for (uint32_t x = 0; x < chip->width; ++x) {
|
|
draw_pixel((uint16_t)x, (uint16_t)y, color565);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void reset_state(void) {
|
|
chip->current_cmd = 0;
|
|
chip->expected_params = 0;
|
|
chip->param_index = 0;
|
|
chip->memory_write = false;
|
|
chip->color_high_pending = false;
|
|
chip->col_start = 0;
|
|
chip->col_end = (uint16_t)(chip->width - 1);
|
|
chip->row_start = 0;
|
|
chip->row_end = (uint16_t)(chip->height - 1);
|
|
chip->cursor_col = 0;
|
|
chip->cursor_row = 0;
|
|
fill_screen(0x0000);
|
|
}
|
|
|
|
static void advance_pixel_cursor(void) {
|
|
if (chip->cursor_col < chip->col_end) {
|
|
++chip->cursor_col;
|
|
return;
|
|
}
|
|
|
|
chip->cursor_col = chip->col_start;
|
|
if (chip->cursor_row < chip->row_end) {
|
|
++chip->cursor_row;
|
|
return;
|
|
}
|
|
|
|
chip->cursor_row = chip->row_start;
|
|
}
|
|
|
|
static void apply_params(void) {
|
|
switch (chip->current_cmd) {
|
|
case 0x2A:
|
|
chip->col_start = clamp_u16((uint16_t)((chip->params[0] << 8) | chip->params[1]), (uint16_t)(chip->width - 1));
|
|
chip->col_end = clamp_u16((uint16_t)((chip->params[2] << 8) | chip->params[3]), (uint16_t)(chip->width - 1));
|
|
if (chip->col_end < chip->col_start) {
|
|
chip->col_end = chip->col_start;
|
|
}
|
|
chip->cursor_col = chip->col_start;
|
|
break;
|
|
case 0x2B:
|
|
chip->row_start = clamp_u16((uint16_t)((chip->params[0] << 8) | chip->params[1]), (uint16_t)(chip->height - 1));
|
|
chip->row_end = clamp_u16((uint16_t)((chip->params[2] << 8) | chip->params[3]), (uint16_t)(chip->height - 1));
|
|
if (chip->row_end < chip->row_start) {
|
|
chip->row_end = chip->row_start;
|
|
}
|
|
chip->cursor_row = chip->row_start;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
chip->current_cmd = 0;
|
|
chip->expected_params = 0;
|
|
chip->param_index = 0;
|
|
}
|
|
|
|
static void process_command(uint8_t command) {
|
|
chip->current_cmd = command;
|
|
chip->param_index = 0;
|
|
chip->expected_params = 0;
|
|
chip->memory_write = false;
|
|
|
|
switch (command) {
|
|
case 0x01:
|
|
reset_state();
|
|
break;
|
|
case 0x2A:
|
|
case 0x2B:
|
|
chip->expected_params = 4;
|
|
break;
|
|
case 0x2C:
|
|
chip->memory_write = true;
|
|
chip->cursor_col = chip->col_start;
|
|
chip->cursor_row = chip->row_start;
|
|
chip->color_high_pending = false;
|
|
break;
|
|
case 0x36:
|
|
case 0x3A:
|
|
chip->expected_params = 1;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void handle_data_byte(uint8_t data) {
|
|
if (chip->current_cmd != 0 && chip->expected_params > 0) {
|
|
chip->params[chip->param_index++] = data;
|
|
if (chip->param_index >= chip->expected_params) {
|
|
apply_params();
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (!chip->memory_write) {
|
|
return;
|
|
}
|
|
|
|
if (!chip->color_high_pending) {
|
|
chip->color_high = data;
|
|
chip->color_high_pending = true;
|
|
return;
|
|
}
|
|
|
|
uint16_t color565 = (uint16_t)((chip->color_high << 8) | data);
|
|
draw_pixel(chip->cursor_col, chip->cursor_row, color565);
|
|
advance_pixel_cursor();
|
|
chip->color_high_pending = false;
|
|
}
|
|
|
|
static void chip_spi_done(void *user_data, uint8_t *buffer, uint32_t count) {
|
|
(void)user_data;
|
|
|
|
for (uint32_t i = 0; i < count; ++i) {
|
|
if (pin_read(chip->dc_pin) == 0) {
|
|
process_command(buffer[i]);
|
|
} else {
|
|
handle_data_byte(buffer[i]);
|
|
}
|
|
}
|
|
|
|
if (pin_read(chip->cs_pin) == 0) {
|
|
spi_start(chip->spi, chip->spi_buffer, sizeof(chip->spi_buffer));
|
|
}
|
|
}
|
|
|
|
static void chip_cs_changed(void *user_data, pin_t pin, uint32_t value) {
|
|
(void)user_data;
|
|
(void)pin;
|
|
|
|
if (value == 0) {
|
|
spi_start(chip->spi, chip->spi_buffer, sizeof(chip->spi_buffer));
|
|
} else {
|
|
spi_stop(chip->spi);
|
|
chip->memory_write = false;
|
|
chip->color_high_pending = false;
|
|
}
|
|
}
|
|
|
|
static void chip_reset_changed(void *user_data, pin_t pin, uint32_t value) {
|
|
(void)user_data;
|
|
(void)pin;
|
|
|
|
if (value == 0) {
|
|
reset_state();
|
|
}
|
|
}
|
|
|
|
void chip_init(void) {
|
|
chip = (main_display_state_t *)malloc(sizeof(main_display_state_t));
|
|
chip->framebuffer = framebuffer_init(&chip->width, &chip->height);
|
|
chip->cs_pin = pin_init("CS", INPUT);
|
|
chip->dc_pin = pin_init("DC", INPUT);
|
|
chip->rst_pin = pin_init("RESET", INPUT_PULLUP);
|
|
chip->led_pin = pin_init("LED", INPUT);
|
|
|
|
const spi_config_t spi_config = {
|
|
.user_data = chip,
|
|
.sck = pin_init("SCK", INPUT),
|
|
.mosi = pin_init("MOSI", INPUT),
|
|
.miso = NO_PIN,
|
|
.mode = 0,
|
|
.done = chip_spi_done,
|
|
};
|
|
|
|
chip->spi = spi_init(&spi_config);
|
|
|
|
const pin_watch_config_t cs_watch = {
|
|
.user_data = chip,
|
|
.edge = BOTH,
|
|
.pin_change = chip_cs_changed,
|
|
};
|
|
pin_watch(chip->cs_pin, &cs_watch);
|
|
|
|
const pin_watch_config_t reset_watch = {
|
|
.user_data = chip,
|
|
.edge = BOTH,
|
|
.pin_change = chip_reset_changed,
|
|
};
|
|
pin_watch(chip->rst_pin, &reset_watch);
|
|
|
|
pin_mode(chip->led_pin, OUTPUT_HIGH);
|
|
reset_state();
|
|
}
|