From 33cb9a7a4ab645e3b5fbca1096fbac89c8b55a79 Mon Sep 17 00:00:00 2001 From: Lukas Date: Fri, 24 Jul 2026 19:46:58 +0200 Subject: [PATCH] Added custom chips to simulate creaturedex setup --- src/chips/common/wokwi-api.h | 30 +++ src/chips/main-display/Makefile | 19 ++ src/chips/main-display/chip.json | 6 + src/chips/main-display/dist/chip.json | 6 + src/chips/main-display/dist/chip.wasm | Bin 0 -> 13491 bytes src/chips/main-display/src/main.c | 243 +++++++++++++++++++++ src/chips/main-display/wokwi-api.h | 74 +++++++ src/chips/nfc-reader/Makefile | 19 ++ src/chips/nfc-reader/chip.json | 5 + src/chips/nfc-reader/dist/chip.json | 5 + src/chips/nfc-reader/dist/chip.wasm | Bin 0 -> 9392 bytes src/chips/nfc-reader/src/main.c | 127 +++++++++++ src/chips/nfc-reader/wokwi-api.h | 74 +++++++ src/chips/secondary-display/Makefile | 19 ++ src/chips/secondary-display/chip.json | 6 + src/chips/secondary-display/dist/chip.json | 6 + src/chips/secondary-display/dist/chip.wasm | Bin 0 -> 11402 bytes src/chips/secondary-display/src/main.c | 171 +++++++++++++++ src/chips/secondary-display/wokwi-api.h | 74 +++++++ src/diagram.json | 103 +++++++++ src/wokwi.toml | 16 ++ 21 files changed, 1003 insertions(+) create mode 100644 src/chips/common/wokwi-api.h create mode 100644 src/chips/main-display/Makefile create mode 100644 src/chips/main-display/chip.json create mode 100644 src/chips/main-display/dist/chip.json create mode 100755 src/chips/main-display/dist/chip.wasm create mode 100644 src/chips/main-display/src/main.c create mode 100644 src/chips/main-display/wokwi-api.h create mode 100644 src/chips/nfc-reader/Makefile create mode 100644 src/chips/nfc-reader/chip.json create mode 100644 src/chips/nfc-reader/dist/chip.json create mode 100755 src/chips/nfc-reader/dist/chip.wasm create mode 100644 src/chips/nfc-reader/src/main.c create mode 100644 src/chips/nfc-reader/wokwi-api.h create mode 100644 src/chips/secondary-display/Makefile create mode 100644 src/chips/secondary-display/chip.json create mode 100644 src/chips/secondary-display/dist/chip.json create mode 100755 src/chips/secondary-display/dist/chip.wasm create mode 100644 src/chips/secondary-display/src/main.c create mode 100644 src/chips/secondary-display/wokwi-api.h create mode 100644 src/diagram.json create mode 100644 src/wokwi.toml 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 0000000000000000000000000000000000000000..1aaa994377bf645a60311153446f41137f94b232 GIT binary patch literal 13491 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 Lb9H{8daLojPNy+; literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..85a67f6a379e047a9275525ce774285bb5df9c29 GIT binary patch literal 9392 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 +#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 0000000000000000000000000000000000000000..4e49010050c6bdb414fadb82693878a67f683d64 GIT binary patch literal 11402 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 +#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