diff --git a/src/chips/common/wokwi-api.h b/src/chips/common/wokwi-api.h new file mode 100644 index 0000000..bd7f5c6 --- /dev/null +++ b/src/chips/common/wokwi-api.h @@ -0,0 +1,30 @@ +#ifndef WOKWI_API_H +#define WOKWI_API_H + +#include +#include + +typedef int32_t pin_t; + +#define NO_PIN ((pin_t)-1) + +enum pin_mode { + INPUT = 0, + OUTPUT = 1, + INPUT_PULLUP = 2, + INPUT_PULLDOWN = 3, + ANALOG = 4, +}; + +typedef struct { + void *user_data; + uint32_t edge; + void (*pin_change)(void *user_data, pin_t pin, uint32_t value); +} pin_watch_config_t; + +extern __attribute__((import_name("pinInit"))) pin_t pin_init(const char *name, uint32_t mode); +extern __attribute__((import_name("pinWatch"))) bool pin_watch(pin_t pin, const pin_watch_config_t *config); +extern __attribute__((import_name("pinWatchStop"))) void pin_watch_stop(pin_t pin); +extern __attribute__((export_name("chipInit"))) void chip_init(void); + +#endif \ No newline at end of file diff --git a/src/chips/main-display/Makefile b/src/chips/main-display/Makefile new file mode 100644 index 0000000..adfe690 --- /dev/null +++ b/src/chips/main-display/Makefile @@ -0,0 +1,19 @@ +SOURCES = src/main.c +TARGET = dist/chip.wasm + +.PHONY: all clean + +all: $(TARGET) dist/chip.json + +clean: + rm -rf dist + +dist: + mkdir -p dist + +$(TARGET): dist $(SOURCES) wokwi-api.h + podman run --rm -u root -v "$(CURDIR)":/src:Z -w /src docker.io/wokwi/builder-clang-wasm:latest \ + clang --target=wasm32-unknown-wasi --sysroot /opt/wasi-libc -nostartfiles -Wl,--import-memory -Wl,--export-table -Wl,--no-entry -Werror -o $(TARGET) $(SOURCES) + +dist/chip.json: dist chip.json + cp chip.json dist diff --git a/src/chips/main-display/chip.json b/src/chips/main-display/chip.json new file mode 100644 index 0000000..2fdb1d8 --- /dev/null +++ b/src/chips/main-display/chip.json @@ -0,0 +1,6 @@ +{ + "name": "Main Display", + "author": "GitHub Copilot", + "display": { "width": 240, "height": 320 }, + "pins": ["VCC", "GND", "SCK", "MOSI", "CS", "DC", "RESET", "LED"] +} \ No newline at end of file diff --git a/src/chips/main-display/dist/chip.json b/src/chips/main-display/dist/chip.json new file mode 100644 index 0000000..2fdb1d8 --- /dev/null +++ b/src/chips/main-display/dist/chip.json @@ -0,0 +1,6 @@ +{ + "name": "Main Display", + "author": "GitHub Copilot", + "display": { "width": 240, "height": 320 }, + "pins": ["VCC", "GND", "SCK", "MOSI", "CS", "DC", "RESET", "LED"] +} \ No newline at end of file diff --git a/src/chips/main-display/dist/chip.wasm b/src/chips/main-display/dist/chip.wasm new file mode 100755 index 0000000..1aaa994 Binary files /dev/null and b/src/chips/main-display/dist/chip.wasm differ diff --git a/src/chips/main-display/src/main.c b/src/chips/main-display/src/main.c new file mode 100644 index 0000000..93b4640 --- /dev/null +++ b/src/chips/main-display/src/main.c @@ -0,0 +1,243 @@ +#include "../wokwi-api.h" + +#include +#include +#include + +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(); +} diff --git a/src/chips/main-display/wokwi-api.h b/src/chips/main-display/wokwi-api.h new file mode 100644 index 0000000..1b4a7c1 --- /dev/null +++ b/src/chips/main-display/wokwi-api.h @@ -0,0 +1,74 @@ +#ifndef WOKWI_API_H +#define WOKWI_API_H + +#include +#include + +typedef int32_t pin_t; +#define NO_PIN ((pin_t)-1) + +enum pin_mode { + INPUT = 0, + OUTPUT = 1, + INPUT_PULLUP = 2, + INPUT_PULLDOWN = 3, + ANALOG = 4, + OUTPUT_LOW = 16, + OUTPUT_HIGH = 17, +}; + +enum edge { + RISING = 1, + FALLING = 2, + BOTH = 3, +}; + +typedef struct { + void *user_data; + uint32_t edge; + void (*pin_change)(void *user_data, pin_t pin, uint32_t value); +} pin_watch_config_t; + +typedef struct { + void *user_data; + pin_t sck; + pin_t mosi; + pin_t miso; + uint32_t mode; + void (*done)(void *user_data, uint8_t *buffer, uint32_t count); + uint32_t reserved[8]; +} spi_config_t; + +typedef uint32_t spi_dev_t; + +typedef struct { + void *user_data; + uint32_t address; + pin_t scl; + pin_t sda; + bool (*connect)(void *user_data, uint32_t address, bool read); + uint8_t (*read)(void *user_data); + bool (*write)(void *user_data, uint8_t data); + void (*disconnect)(void *user_data); + uint32_t reserved[8]; +} i2c_config_t; + +typedef uint32_t i2c_dev_t; + +typedef uint32_t buffer_t; + +extern __attribute__((export_name("chipInit"))) void chip_init(void); +extern __attribute__((import_name("pinInit"))) pin_t pin_init(const char *name, uint32_t mode); +extern __attribute__((import_name("pinWatch"))) bool pin_watch(pin_t pin, const pin_watch_config_t *config); +extern __attribute__((import_name("pinWatchStop"))) void pin_watch_stop(pin_t pin); +extern __attribute__((import_name("pinMode"))) void pin_mode(pin_t pin, uint32_t value); +extern __attribute__((import_name("pinRead"))) uint32_t pin_read(pin_t pin); +extern __attribute__((import_name("pinWrite"))) void pin_write(pin_t pin, uint32_t value); +extern __attribute__((import_name("spiInit"))) spi_dev_t spi_init(const spi_config_t *config); +extern __attribute__((import_name("spiStart"))) void spi_start(const spi_dev_t spi, uint8_t *buffer, uint32_t count); +extern __attribute__((import_name("spiStop"))) void spi_stop(const spi_dev_t spi); +extern __attribute__((import_name("i2cInit"))) i2c_dev_t i2c_init(const i2c_config_t *config); +extern __attribute__((import_name("framebufferInit"))) buffer_t framebuffer_init(uint32_t *pixel_width, uint32_t *pixel_height); +extern __attribute__((import_name("bufferWrite"))) void buffer_write(buffer_t buffer, uint32_t offset, void *data, uint32_t data_len); + +#endif \ No newline at end of file diff --git a/src/chips/nfc-reader/Makefile b/src/chips/nfc-reader/Makefile new file mode 100644 index 0000000..adfe690 --- /dev/null +++ b/src/chips/nfc-reader/Makefile @@ -0,0 +1,19 @@ +SOURCES = src/main.c +TARGET = dist/chip.wasm + +.PHONY: all clean + +all: $(TARGET) dist/chip.json + +clean: + rm -rf dist + +dist: + mkdir -p dist + +$(TARGET): dist $(SOURCES) wokwi-api.h + podman run --rm -u root -v "$(CURDIR)":/src:Z -w /src docker.io/wokwi/builder-clang-wasm:latest \ + clang --target=wasm32-unknown-wasi --sysroot /opt/wasi-libc -nostartfiles -Wl,--import-memory -Wl,--export-table -Wl,--no-entry -Werror -o $(TARGET) $(SOURCES) + +dist/chip.json: dist chip.json + cp chip.json dist diff --git a/src/chips/nfc-reader/chip.json b/src/chips/nfc-reader/chip.json new file mode 100644 index 0000000..e38cca7 --- /dev/null +++ b/src/chips/nfc-reader/chip.json @@ -0,0 +1,5 @@ +{ + "name": "PN532 NFC Reader", + "author": "GitHub Copilot", + "pins": ["VCC", "GND", "SCL", "SDA", "IRQ", "RSTPDN", "SS", "MOSI", "MISO", "SCK"] +} \ No newline at end of file diff --git a/src/chips/nfc-reader/dist/chip.json b/src/chips/nfc-reader/dist/chip.json new file mode 100644 index 0000000..e38cca7 --- /dev/null +++ b/src/chips/nfc-reader/dist/chip.json @@ -0,0 +1,5 @@ +{ + "name": "PN532 NFC Reader", + "author": "GitHub Copilot", + "pins": ["VCC", "GND", "SCL", "SDA", "IRQ", "RSTPDN", "SS", "MOSI", "MISO", "SCK"] +} \ No newline at end of file diff --git a/src/chips/nfc-reader/dist/chip.wasm b/src/chips/nfc-reader/dist/chip.wasm new file mode 100755 index 0000000..85a67f6 Binary files /dev/null and b/src/chips/nfc-reader/dist/chip.wasm differ diff --git a/src/chips/nfc-reader/src/main.c b/src/chips/nfc-reader/src/main.c new file mode 100644 index 0000000..e8e3419 --- /dev/null +++ b/src/chips/nfc-reader/src/main.c @@ -0,0 +1,127 @@ +#include "../wokwi-api.h" + +#include +#include +#include + +typedef struct { + i2c_dev_t i2c; + spi_dev_t spi; + pin_t irq_pin; + pin_t rst_pin; + pin_t ss_pin; + uint8_t spi_buffer[16]; + uint8_t ack_frame[6]; + bool ready; +} nfc_reader_state_t; + +static nfc_reader_state_t *chip; + +static void update_irq(void) { + pin_write(chip->irq_pin, chip->ready ? 0 : 1); +} + +static void load_ack_frame(void) { + chip->ack_frame[0] = 0x00; + chip->ack_frame[1] = 0x00; + chip->ack_frame[2] = 0xFF; + chip->ack_frame[3] = 0x00; + chip->ack_frame[4] = 0xFF; + chip->ack_frame[5] = 0x00; +} + +static void on_spi_done(void *user_data, uint8_t *buffer, uint32_t count) { + (void)user_data; + (void)buffer; + (void)count; + load_ack_frame(); + if (pin_read(chip->ss_pin) == 0) { + spi_start(chip->spi, chip->spi_buffer, sizeof(chip->spi_buffer)); + } +} + +static void on_ss_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); + } +} + +static void on_rst_changed(void *user_data, pin_t pin, uint32_t value) { + (void)user_data; + (void)pin; + chip->ready = value != 0; + update_irq(); +} + +static bool on_i2c_connect(void *user_data, uint32_t address, bool read) { + (void)user_data; + (void)address; + (void)read; + return true; +} + +static uint8_t on_i2c_read(void *user_data) { + (void)user_data; + return 0; +} + +static bool on_i2c_write(void *user_data, uint8_t data) { + (void)user_data; + (void)data; + return true; +} + +static void on_i2c_disconnect(void *user_data) { + (void)user_data; +} + +void chip_init(void) { + chip = (nfc_reader_state_t *)malloc(sizeof(nfc_reader_state_t)); + chip->irq_pin = pin_init("IRQ", INPUT); + chip->rst_pin = pin_init("RSTPDN", INPUT_PULLUP); + chip->ss_pin = pin_init("SS", INPUT_PULLUP); + + const spi_config_t spi_config = { + .user_data = chip, + .sck = pin_init("SCK", INPUT), + .mosi = pin_init("MOSI", INPUT), + .miso = pin_init("MISO", INPUT), + .mode = 0, + .done = on_spi_done, + }; + chip->spi = spi_init(&spi_config); + + const pin_watch_config_t ss_watch = { + .user_data = chip, + .edge = BOTH, + .pin_change = on_ss_changed, + }; + pin_watch(chip->ss_pin, &ss_watch); + + const pin_watch_config_t rst_watch = { + .user_data = chip, + .edge = BOTH, + .pin_change = on_rst_changed, + }; + pin_watch(chip->rst_pin, &rst_watch); + + const i2c_config_t i2c_config = { + .user_data = chip, + .address = 0x24, + .scl = pin_init("SCL", INPUT_PULLUP), + .sda = pin_init("SDA", INPUT_PULLUP), + .connect = on_i2c_connect, + .read = on_i2c_read, + .write = on_i2c_write, + .disconnect = on_i2c_disconnect, + }; + chip->i2c = i2c_init(&i2c_config); + + chip->ready = true; + load_ack_frame(); + update_irq(); +} \ No newline at end of file diff --git a/src/chips/nfc-reader/wokwi-api.h b/src/chips/nfc-reader/wokwi-api.h new file mode 100644 index 0000000..1b4a7c1 --- /dev/null +++ b/src/chips/nfc-reader/wokwi-api.h @@ -0,0 +1,74 @@ +#ifndef WOKWI_API_H +#define WOKWI_API_H + +#include +#include + +typedef int32_t pin_t; +#define NO_PIN ((pin_t)-1) + +enum pin_mode { + INPUT = 0, + OUTPUT = 1, + INPUT_PULLUP = 2, + INPUT_PULLDOWN = 3, + ANALOG = 4, + OUTPUT_LOW = 16, + OUTPUT_HIGH = 17, +}; + +enum edge { + RISING = 1, + FALLING = 2, + BOTH = 3, +}; + +typedef struct { + void *user_data; + uint32_t edge; + void (*pin_change)(void *user_data, pin_t pin, uint32_t value); +} pin_watch_config_t; + +typedef struct { + void *user_data; + pin_t sck; + pin_t mosi; + pin_t miso; + uint32_t mode; + void (*done)(void *user_data, uint8_t *buffer, uint32_t count); + uint32_t reserved[8]; +} spi_config_t; + +typedef uint32_t spi_dev_t; + +typedef struct { + void *user_data; + uint32_t address; + pin_t scl; + pin_t sda; + bool (*connect)(void *user_data, uint32_t address, bool read); + uint8_t (*read)(void *user_data); + bool (*write)(void *user_data, uint8_t data); + void (*disconnect)(void *user_data); + uint32_t reserved[8]; +} i2c_config_t; + +typedef uint32_t i2c_dev_t; + +typedef uint32_t buffer_t; + +extern __attribute__((export_name("chipInit"))) void chip_init(void); +extern __attribute__((import_name("pinInit"))) pin_t pin_init(const char *name, uint32_t mode); +extern __attribute__((import_name("pinWatch"))) bool pin_watch(pin_t pin, const pin_watch_config_t *config); +extern __attribute__((import_name("pinWatchStop"))) void pin_watch_stop(pin_t pin); +extern __attribute__((import_name("pinMode"))) void pin_mode(pin_t pin, uint32_t value); +extern __attribute__((import_name("pinRead"))) uint32_t pin_read(pin_t pin); +extern __attribute__((import_name("pinWrite"))) void pin_write(pin_t pin, uint32_t value); +extern __attribute__((import_name("spiInit"))) spi_dev_t spi_init(const spi_config_t *config); +extern __attribute__((import_name("spiStart"))) void spi_start(const spi_dev_t spi, uint8_t *buffer, uint32_t count); +extern __attribute__((import_name("spiStop"))) void spi_stop(const spi_dev_t spi); +extern __attribute__((import_name("i2cInit"))) i2c_dev_t i2c_init(const i2c_config_t *config); +extern __attribute__((import_name("framebufferInit"))) buffer_t framebuffer_init(uint32_t *pixel_width, uint32_t *pixel_height); +extern __attribute__((import_name("bufferWrite"))) void buffer_write(buffer_t buffer, uint32_t offset, void *data, uint32_t data_len); + +#endif \ No newline at end of file diff --git a/src/chips/secondary-display/Makefile b/src/chips/secondary-display/Makefile new file mode 100644 index 0000000..adfe690 --- /dev/null +++ b/src/chips/secondary-display/Makefile @@ -0,0 +1,19 @@ +SOURCES = src/main.c +TARGET = dist/chip.wasm + +.PHONY: all clean + +all: $(TARGET) dist/chip.json + +clean: + rm -rf dist + +dist: + mkdir -p dist + +$(TARGET): dist $(SOURCES) wokwi-api.h + podman run --rm -u root -v "$(CURDIR)":/src:Z -w /src docker.io/wokwi/builder-clang-wasm:latest \ + clang --target=wasm32-unknown-wasi --sysroot /opt/wasi-libc -nostartfiles -Wl,--import-memory -Wl,--export-table -Wl,--no-entry -Werror -o $(TARGET) $(SOURCES) + +dist/chip.json: dist chip.json + cp chip.json dist diff --git a/src/chips/secondary-display/chip.json b/src/chips/secondary-display/chip.json new file mode 100644 index 0000000..0b1fd16 --- /dev/null +++ b/src/chips/secondary-display/chip.json @@ -0,0 +1,6 @@ +{ + "name": "Secondary Display", + "author": "GitHub Copilot", + "display": { "width": 84, "height": 48 }, + "pins": ["VCC", "LIGHT", "GND", "CLK", "DIN", "CE", "DC", "RST"] +} \ No newline at end of file diff --git a/src/chips/secondary-display/dist/chip.json b/src/chips/secondary-display/dist/chip.json new file mode 100644 index 0000000..0b1fd16 --- /dev/null +++ b/src/chips/secondary-display/dist/chip.json @@ -0,0 +1,6 @@ +{ + "name": "Secondary Display", + "author": "GitHub Copilot", + "display": { "width": 84, "height": 48 }, + "pins": ["VCC", "LIGHT", "GND", "CLK", "DIN", "CE", "DC", "RST"] +} \ No newline at end of file diff --git a/src/chips/secondary-display/dist/chip.wasm b/src/chips/secondary-display/dist/chip.wasm new file mode 100755 index 0000000..4e49010 Binary files /dev/null and b/src/chips/secondary-display/dist/chip.wasm differ diff --git a/src/chips/secondary-display/src/main.c b/src/chips/secondary-display/src/main.c new file mode 100644 index 0000000..8142a54 --- /dev/null +++ b/src/chips/secondary-display/src/main.c @@ -0,0 +1,171 @@ +#include "../wokwi-api.h" + +#include +#include +#include + +typedef struct { + buffer_t framebuffer; + uint32_t width; + uint32_t height; + spi_dev_t spi; + pin_t ce_pin; + pin_t dc_pin; + pin_t rst_pin; + uint8_t spi_buffer[64]; + uint8_t x; + uint8_t y; + bool extended_commands; +} secondary_display_state_t; + +static secondary_display_state_t *chip; + +static void set_pixel(uint8_t x, uint8_t y, bool on) { + if (x >= chip->width || y >= chip->height) { + return; + } + + uint8_t pixel[4] = {0, 0, 0, 255}; + if (on) { + pixel[0] = 0x20; + pixel[1] = 0x90; + pixel[2] = 0x20; + } + + buffer_write(chip->framebuffer, ((uint32_t)y * chip->width + x) * 4, pixel, 4); +} + +static void clear_display(void) { + for (uint32_t y = 0; y < chip->height; ++y) { + for (uint32_t x = 0; x < chip->width; ++x) { + set_pixel((uint8_t)x, (uint8_t)y, false); + } + } + chip->x = 0; + chip->y = 0; +} + +static void write_data(uint8_t data) { + for (uint8_t bit = 0; bit < 8; ++bit) { + set_pixel(chip->x, (uint8_t)(chip->y * 8 + bit), (data >> bit) & 1); + } + + ++chip->x; + if (chip->x >= chip->width) { + chip->x = 0; + ++chip->y; + if (chip->y >= (chip->height / 8)) { + chip->y = 0; + } + } +} + +static void write_command(uint8_t command) { + if (command == 0x20) { + chip->extended_commands = false; + return; + } + + if (command == 0x21) { + chip->extended_commands = true; + return; + } + + if (command == 0x0C) { + return; + } + + if (command == 0x08) { + clear_display(); + return; + } + + if (command == 0x80 || (command & 0x80) == 0x80) { + chip->x = (uint8_t)(command & 0x7F); + if (chip->x >= chip->width) { + chip->x = 0; + } + return; + } + + if (command == 0x40 || (command & 0x40) == 0x40) { + chip->y = (uint8_t)(command & 0x07); + if (chip->y >= (chip->height / 8)) { + chip->y = 0; + } + return; + } + + if (command == 0x01) { + clear_display(); + } +} + +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) { + write_command(buffer[i]); + } else { + write_data(buffer[i]); + } + } + + if (pin_read(chip->ce_pin) == 0) { + spi_start(chip->spi, chip->spi_buffer, sizeof(chip->spi_buffer)); + } +} + +static void chip_ce_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); + } +} + +static void chip_rst_changed(void *user_data, pin_t pin, uint32_t value) { + (void)user_data; + (void)pin; + + if (value == 0) { + clear_display(); + } +} + +void chip_init(void) { + chip = (secondary_display_state_t *)malloc(sizeof(secondary_display_state_t)); + chip->framebuffer = framebuffer_init(&chip->width, &chip->height); + chip->ce_pin = pin_init("CE", INPUT); + chip->dc_pin = pin_init("DC", INPUT); + chip->rst_pin = pin_init("RST", INPUT_PULLUP); + + const spi_config_t spi_config = { + .user_data = chip, + .sck = pin_init("CLK", INPUT), + .mosi = pin_init("DIN", INPUT), + .miso = NO_PIN, + .mode = 0, + .done = chip_spi_done, + }; + chip->spi = spi_init(&spi_config); + + const pin_watch_config_t ce_watch = { + .user_data = chip, + .edge = BOTH, + .pin_change = chip_ce_changed, + }; + pin_watch(chip->ce_pin, &ce_watch); + + const pin_watch_config_t rst_watch = { + .user_data = chip, + .edge = BOTH, + .pin_change = chip_rst_changed, + }; + pin_watch(chip->rst_pin, &rst_watch); + + clear_display(); +} \ No newline at end of file diff --git a/src/chips/secondary-display/wokwi-api.h b/src/chips/secondary-display/wokwi-api.h new file mode 100644 index 0000000..1b4a7c1 --- /dev/null +++ b/src/chips/secondary-display/wokwi-api.h @@ -0,0 +1,74 @@ +#ifndef WOKWI_API_H +#define WOKWI_API_H + +#include +#include + +typedef int32_t pin_t; +#define NO_PIN ((pin_t)-1) + +enum pin_mode { + INPUT = 0, + OUTPUT = 1, + INPUT_PULLUP = 2, + INPUT_PULLDOWN = 3, + ANALOG = 4, + OUTPUT_LOW = 16, + OUTPUT_HIGH = 17, +}; + +enum edge { + RISING = 1, + FALLING = 2, + BOTH = 3, +}; + +typedef struct { + void *user_data; + uint32_t edge; + void (*pin_change)(void *user_data, pin_t pin, uint32_t value); +} pin_watch_config_t; + +typedef struct { + void *user_data; + pin_t sck; + pin_t mosi; + pin_t miso; + uint32_t mode; + void (*done)(void *user_data, uint8_t *buffer, uint32_t count); + uint32_t reserved[8]; +} spi_config_t; + +typedef uint32_t spi_dev_t; + +typedef struct { + void *user_data; + uint32_t address; + pin_t scl; + pin_t sda; + bool (*connect)(void *user_data, uint32_t address, bool read); + uint8_t (*read)(void *user_data); + bool (*write)(void *user_data, uint8_t data); + void (*disconnect)(void *user_data); + uint32_t reserved[8]; +} i2c_config_t; + +typedef uint32_t i2c_dev_t; + +typedef uint32_t buffer_t; + +extern __attribute__((export_name("chipInit"))) void chip_init(void); +extern __attribute__((import_name("pinInit"))) pin_t pin_init(const char *name, uint32_t mode); +extern __attribute__((import_name("pinWatch"))) bool pin_watch(pin_t pin, const pin_watch_config_t *config); +extern __attribute__((import_name("pinWatchStop"))) void pin_watch_stop(pin_t pin); +extern __attribute__((import_name("pinMode"))) void pin_mode(pin_t pin, uint32_t value); +extern __attribute__((import_name("pinRead"))) uint32_t pin_read(pin_t pin); +extern __attribute__((import_name("pinWrite"))) void pin_write(pin_t pin, uint32_t value); +extern __attribute__((import_name("spiInit"))) spi_dev_t spi_init(const spi_config_t *config); +extern __attribute__((import_name("spiStart"))) void spi_start(const spi_dev_t spi, uint8_t *buffer, uint32_t count); +extern __attribute__((import_name("spiStop"))) void spi_stop(const spi_dev_t spi); +extern __attribute__((import_name("i2cInit"))) i2c_dev_t i2c_init(const i2c_config_t *config); +extern __attribute__((import_name("framebufferInit"))) buffer_t framebuffer_init(uint32_t *pixel_width, uint32_t *pixel_height); +extern __attribute__((import_name("bufferWrite"))) void buffer_write(buffer_t buffer, uint32_t offset, void *data, uint32_t data_len); + +#endif \ No newline at end of file diff --git a/src/diagram.json b/src/diagram.json new file mode 100644 index 0000000..9f5f0cb --- /dev/null +++ b/src/diagram.json @@ -0,0 +1,103 @@ +{ + "version": 1, + "author": "CreatureDex Builder", + "editor": "wokwi", + "parts": [ + { + "type": "board-esp32-s3-devkitc-1", + "id": "esp", + "top": -144.18, + "left": -168.23, + "attrs": {} + }, + { + "type": "wokwi-lcd1602", + "id": "lcd1", + "top": -310.4, + "left": -253.6, + "attrs": { "pins": "i2c" } + }, + { + "type": "chip-main-display", + "id": "lcd2", + "top": -162.4, + "left": 105.3, + "attrs": {} + }, + { + "type": "chip-secondary-display", + "id": "oled_spi_1", + "top": 210, + "left": -291.78, + "attrs": {} + }, + { + "type": "chip-secondary-display", + "id": "oled_spi_2", + "top": 210, + "left": -109.38, + "attrs": {} + }, + { + "type": "chip-secondary-display", + "id": "oled_spi_3", + "top": 210, + "left": 73.02, + "attrs": {} + }, + { + "type": "chip-nfc-reader", + "id": "nfc1", + "top": 390, + "left": -24, + "attrs": {}, + "hide": true + } + ], + "connections": [ + [ "esp:TX", "$serialMonitor:RX", "", [] ], + [ "esp:RX", "$serialMonitor:TX", "", [] ], + + [ "esp:3V3", "lcd1:VCC", "", [] ], + [ "esp:GND", "lcd1:GND", "", [] ], + [ "esp:8", "lcd1:SDA", "", [] ], + [ "esp:9", "lcd1:SCL", "", [] ], + + [ "esp:3V3", "lcd2:VCC", "", [] ], + [ "esp:3V3", "lcd2:LED", "", [] ], + [ "esp:GND", "lcd2:GND", "", [] ], + [ "esp:12", "lcd2:SCK", "", [] ], + [ "esp:11", "lcd2:MOSI", "", [] ], + [ "esp:10", "lcd2:CS", "", [] ], + [ "esp:7", "lcd2:D/C", "", [] ], + [ "esp:5", "lcd2:RESET", "", [] ], + + [ "esp:3V3", "oled_spi_1:VCC", "", [] ], + [ "esp:3V3", "oled_spi_1:LIGHT", "", [] ], + [ "esp:GND", "oled_spi_1:GND", "", [] ], + [ "esp:12", "oled_spi_1:CLK", "", [] ], + [ "esp:11", "oled_spi_1:DIN", "", [] ], + [ "esp:14", "oled_spi_1:CE", "", [] ], + [ "esp:6", "oled_spi_1:DC", "", [] ], + [ "esp:5", "oled_spi_1:RST", "", [] ], + + [ "esp:3V3", "oled_spi_2:VCC", "", [] ], + [ "esp:3V3", "oled_spi_2:LIGHT", "", [] ], + [ "esp:GND", "oled_spi_2:GND", "", [] ], + [ "esp:12", "oled_spi_2:CLK", "", [] ], + [ "esp:11", "oled_spi_2:DIN", "", [] ], + [ "esp:15", "oled_spi_2:CE", "", [] ], + [ "esp:6", "oled_spi_2:DC", "", [] ], + [ "esp:5", "oled_spi_2:RST", "", [] ], + + [ "esp:3V3", "oled_spi_3:VCC", "", [] ], + [ "esp:3V3", "oled_spi_3:LIGHT", "", [] ], + [ "esp:GND", "oled_spi_3:GND", "", [] ], + [ "esp:12", "oled_spi_3:CLK", "", [] ], + [ "esp:11", "oled_spi_3:DIN", "", [] ], + [ "esp:16", "oled_spi_3:CE", "", [] ], + [ "esp:6", "oled_spi_3:DC", "", [] ], + [ "esp:5", "oled_spi_3:RST", "", [] ] + ], + "dependencies": {} +} \ No newline at end of file diff --git a/src/wokwi.toml b/src/wokwi.toml new file mode 100644 index 0000000..db54d99 --- /dev/null +++ b/src/wokwi.toml @@ -0,0 +1,16 @@ +[wokwi] +version = 1 +firmware = "../build/firmware.bin" +elf = "../build/firmware.elf" + +[[chip]] +name = "main-display" +binary = "chips/main-display/dist/chip.wasm" + +[[chip]] +name = "secondary-display" +binary = "chips/secondary-display/dist/chip.wasm" + +[[chip]] +name = "nfc-reader" +binary = "chips/nfc-reader/dist/chip.wasm" \ No newline at end of file