From 37d246e86c6235cede9e34fa1621a43a90cd9c90 Mon Sep 17 00:00:00 2001
From: moon2 <moon2protonmail@protonmail.com>
Date: Thu, 20 Apr 2023 11:21:54 +0200
Subject: [PATCH] scripted synth running in parallel with micropython,
 bugs/restrictions galore

---
 ports/esp32/badge23/CMakeLists.txt            |   6 +
 ports/esp32/badge23/Kconfig.projbuild         |  21 +
 ports/esp32/badge23/apa102LEDStrip.c          |  50 ++
 ports/esp32/badge23/apa102LEDStrip.h          |  17 +
 ports/esp32/badge23/audio.c                   | 266 ++++++++
 ports/esp32/badge23/audio.h                   |  14 +
 ports/esp32/badge23/captouch.c                | 417 ++++++++++++
 ports/esp32/badge23/captouch.h                |   6 +
 .../badge23/components/gc9a01/CMakeLists.txt  |   1 +
 ports/esp32/badge23/components/gc9a01/Kconfig | 121 ++++
 ports/esp32/badge23/components/gc9a01/LICENSE |  29 +
 .../esp32/badge23/components/gc9a01/gc9a01.c  | 641 ++++++++++++++++++
 .../esp32/badge23/components/gc9a01/gc9a01.h  |  61 ++
 .../esp32/badge23/components/gc9a01/readme.md |  51 ++
 ports/esp32/badge23/decode_image.h            |  33 +
 ports/esp32/badge23/display.c                 | 121 ++++
 ports/esp32/badge23/display.h                 |   6 +
 ports/esp32/badge23/espan.c                   | 178 +++++
 ports/esp32/badge23/espan.h                   |   2 +
 ports/esp32/badge23/idf_component.yml         |  17 +
 ports/esp32/badge23/instruments/instrument.c  |  73 ++
 ports/esp32/badge23/instruments/instrument.h  | 130 ++++
 .../badge23/instruments/minimal_example.c     |  89 +++
 ports/esp32/badge23/leds.c                    | 348 ++++++++++
 ports/esp32/badge23/leds.h                    |   4 +
 ports/esp32/badge23/scope.c                   |  63 ++
 ports/esp32/badge23/scope.h                   |  15 +
 ports/esp32/badge23/synth.c                   | 167 +++++
 ports/esp32/badge23/synth.h                   |  48 ++
 ports/esp32/badge23/tags                      | 162 +++++
 .../esp32/boards/GENERIC_S3_BADGE/board.json  |  18 +
 .../GENERIC_S3_BADGE/mpconfigboard.cmake      |   9 +
 .../boards/GENERIC_S3_BADGE/mpconfigboard.h   |  10 +
 .../boards/GENERIC_S3_BADGE/sdkconfig.board   |  12 +
 ports/esp32/boards/sdkconfig.badge23          |  21 +
 ports/esp32/main.c                            |   7 +-
 ports/esp32/main/CMakeLists.txt               |  13 +
 37 files changed, 3246 insertions(+), 1 deletion(-)
 create mode 100644 ports/esp32/badge23/CMakeLists.txt
 create mode 100644 ports/esp32/badge23/Kconfig.projbuild
 create mode 100644 ports/esp32/badge23/apa102LEDStrip.c
 create mode 100644 ports/esp32/badge23/apa102LEDStrip.h
 create mode 100644 ports/esp32/badge23/audio.c
 create mode 100644 ports/esp32/badge23/audio.h
 create mode 100644 ports/esp32/badge23/captouch.c
 create mode 100644 ports/esp32/badge23/captouch.h
 create mode 100644 ports/esp32/badge23/components/gc9a01/CMakeLists.txt
 create mode 100644 ports/esp32/badge23/components/gc9a01/Kconfig
 create mode 100644 ports/esp32/badge23/components/gc9a01/LICENSE
 create mode 100644 ports/esp32/badge23/components/gc9a01/gc9a01.c
 create mode 100644 ports/esp32/badge23/components/gc9a01/gc9a01.h
 create mode 100644 ports/esp32/badge23/components/gc9a01/readme.md
 create mode 100644 ports/esp32/badge23/decode_image.h
 create mode 100644 ports/esp32/badge23/display.c
 create mode 100644 ports/esp32/badge23/display.h
 create mode 100644 ports/esp32/badge23/espan.c
 create mode 100644 ports/esp32/badge23/espan.h
 create mode 100644 ports/esp32/badge23/idf_component.yml
 create mode 100644 ports/esp32/badge23/instruments/instrument.c
 create mode 100644 ports/esp32/badge23/instruments/instrument.h
 create mode 100644 ports/esp32/badge23/instruments/minimal_example.c
 create mode 100644 ports/esp32/badge23/leds.c
 create mode 100644 ports/esp32/badge23/leds.h
 create mode 100644 ports/esp32/badge23/scope.c
 create mode 100644 ports/esp32/badge23/scope.h
 create mode 100644 ports/esp32/badge23/synth.c
 create mode 100644 ports/esp32/badge23/synth.h
 create mode 100644 ports/esp32/badge23/tags
 create mode 100644 ports/esp32/boards/GENERIC_S3_BADGE/board.json
 create mode 100644 ports/esp32/boards/GENERIC_S3_BADGE/mpconfigboard.cmake
 create mode 100644 ports/esp32/boards/GENERIC_S3_BADGE/mpconfigboard.h
 create mode 100644 ports/esp32/boards/GENERIC_S3_BADGE/sdkconfig.board
 create mode 100644 ports/esp32/boards/sdkconfig.badge23

diff --git a/ports/esp32/badge23/CMakeLists.txt b/ports/esp32/badge23/CMakeLists.txt
new file mode 100644
index 0000000000..a4cf9845e1
--- /dev/null
+++ b/ports/esp32/badge23/CMakeLists.txt
@@ -0,0 +1,6 @@
+idf_component_register(SRCS "espan.c" "apa102LEDStrip.c" "audio.c" "captouch.c" "decode_image.c" "leds.c" "display.c" "synth.c" "scope.c"
+    EMBED_FILES
+        ${project_dir}/resources/boot.snd
+        ${project_dir}/resources/pan.s16
+        ${project_dir}/resources/image.jpg
+                    INCLUDE_DIRS ".")
diff --git a/ports/esp32/badge23/Kconfig.projbuild b/ports/esp32/badge23/Kconfig.projbuild
new file mode 100644
index 0000000000..62f89be8f9
--- /dev/null
+++ b/ports/esp32/badge23/Kconfig.projbuild
@@ -0,0 +1,21 @@
+menu "Example Configuration"
+
+    orsource "$IDF_PATH/examples/common_components/env_caps/$IDF_TARGET/Kconfig.env_caps"
+
+    config I2C_MASTER_SCL
+        int "SCL GPIO Num"
+        range ENV_GPIO_RANGE_MIN ENV_GPIO_OUT_RANGE_MAX
+        default 19 if IDF_TARGET_ESP32 || IDF_TARGET_ESP32S2 || IDF_TARGET_ESP32S3
+        default 6
+        help
+            GPIO number for I2C Master clock line.
+
+    config I2C_MASTER_SDA
+        int "SDA GPIO Num"
+        range ENV_GPIO_RANGE_MIN ENV_GPIO_OUT_RANGE_MAX
+        default 18 if IDF_TARGET_ESP32 || IDF_TARGET_ESP32S2 || IDF_TARGET_ESP32S3
+        default 5
+        help
+            GPIO number for I2C Master data line.
+
+endmenu
diff --git a/ports/esp32/badge23/apa102LEDStrip.c b/ports/esp32/badge23/apa102LEDStrip.c
new file mode 100644
index 0000000000..32179d1f9f
--- /dev/null
+++ b/ports/esp32/badge23/apa102LEDStrip.c
@@ -0,0 +1,50 @@
+#include "apa102LEDStrip.h"
+#include <math.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+void initLEDs(struct apa102LEDStrip *ledObject, short int numLEDs, unsigned char bytesPerLED, unsigned char globalBrightness)
+{  
+  ledObject->_numLEDs = numLEDs;
+  ledObject->_bytesPerLED = bytesPerLED;
+  ledObject->_endFrameLength = 1;//round( (numLEDs/2)/8 );
+  ledObject->_frameLength = (1+numLEDs+ledObject->_endFrameLength)*bytesPerLED;
+  ledObject->_globalBrightness = globalBrightness;
+  ledObject->LEDs = (unsigned char *)malloc(ledObject->_frameLength*sizeof(unsigned char)); 
+  
+  //Start Frame
+  ledObject->LEDs[0] = 0;
+  ledObject->LEDs[1] = 0;
+  ledObject->LEDs[2] = 0;
+  ledObject->LEDs[3] = 0;
+  //Driver frame+PIXEL frames
+  for(ledObject->_counter=ledObject->_bytesPerLED; ledObject->_counter<ledObject->_frameLength-(ledObject->_endFrameLength*ledObject->_bytesPerLED); ledObject->_counter+=ledObject->_bytesPerLED)
+  {
+    ledObject->LEDs[ledObject->_counter] = ledObject->_globalBrightness;
+    ledObject->LEDs[ledObject->_counter+1] = 0;
+    ledObject->LEDs[ledObject->_counter+2] = 0;
+    ledObject->LEDs[ledObject->_counter+3] = 0;
+  }
+  //END frames
+  for(ledObject->_counter=ledObject->_frameLength-(ledObject->_endFrameLength*ledObject->_bytesPerLED); ledObject->_counter<ledObject->_frameLength; ledObject->_counter+=ledObject->_bytesPerLED)
+  {
+    ledObject->LEDs[ledObject->_counter] = 255;
+    ledObject->LEDs[ledObject->_counter+1] = 255;
+    ledObject->LEDs[ledObject->_counter+2] = 255;
+    ledObject->LEDs[ledObject->_counter+3] = 255;
+  }
+}
+void setPixel(struct apa102LEDStrip *ledObject, short int pixelIndex, unsigned char *pixelColour)
+{
+  ledObject->_counter = 4*(pixelIndex+1);
+  ledObject->LEDs[ ledObject->_counter + 1 ] = pixelColour[2];
+  ledObject->LEDs[ ledObject->_counter + 2 ] = pixelColour[1];
+  ledObject->LEDs[ ledObject->_counter + 3 ] = pixelColour[0];
+}
+void getPixel(struct apa102LEDStrip *ledObject, short int pixelIndex, unsigned char *pixelColour)
+{
+  ledObject->_counter = 4*(pixelIndex+1);
+  pixelColour[2] = ledObject->LEDs[ ledObject->_counter + 1 ];
+  pixelColour[1] = ledObject->LEDs[ ledObject->_counter + 2 ];
+  pixelColour[0] = ledObject->LEDs[ ledObject->_counter + 3 ];
+}
diff --git a/ports/esp32/badge23/apa102LEDStrip.h b/ports/esp32/badge23/apa102LEDStrip.h
new file mode 100644
index 0000000000..da495abf62
--- /dev/null
+++ b/ports/esp32/badge23/apa102LEDStrip.h
@@ -0,0 +1,17 @@
+#ifndef DEF_apa102LEDStrip
+#define DEF_apa102LEDStrip
+
+struct apa102LEDStrip
+{
+    unsigned char *LEDs;
+    short int _frameLength;
+    short int _endFrameLength;
+    short int _numLEDs;
+    unsigned char _bytesPerLED;
+    short int _counter;
+    unsigned char _globalBrightness;
+};
+void initLEDs(struct apa102LEDStrip *ledObject, short int numLEDs, unsigned char bytesPerLED, unsigned char globalBrightness);
+void setPixel(struct apa102LEDStrip *ledObject, short int pixelIndex, unsigned char *pixelColour);
+void getPixel(struct apa102LEDStrip *ledObject, short int pixelIndex, unsigned char *pixelColour);
+#endif
diff --git a/ports/esp32/badge23/audio.c b/ports/esp32/badge23/audio.c
new file mode 100644
index 0000000000..9bd88dd066
--- /dev/null
+++ b/ports/esp32/badge23/audio.c
@@ -0,0 +1,266 @@
+#include "audio.h"
+#include "synth.h" 
+#include "scope.h"
+
+#include "driver/i2s.h"
+
+#include <freertos/FreeRTOS.h>
+#include <freertos/task.h>
+#include <freertos/queue.h>
+#include <stdio.h>
+#include <math.h>
+#include <string.h>
+
+#define DRUMS_TOP 0
+
+#define NUM_SYNTH 10
+
+static trad_osc_t synths[NUM_SYNTH];
+static void audio_player_task(void* arg);
+
+#define DMA_BUFFER_SIZE     64
+#define DMA_BUFFER_COUNT    2
+#define I2S_PORT 0
+
+#if 0
+static i2s_chan_handle_t                tx_chan;        // I2S tx channel handler
+static i2s_chan_handle_t                rx_chan;        // I2S rx channel handler
+
+static void i2s_init_std_duplex(void)
+{
+    /* Setp 1: Determine the I2S channel configuration and allocate both channels
+     * The default configuration can be generated by the helper macro,
+     * it only requires the I2S controller id and I2S role */
+    i2s_chan_config_t chan_cfg = I2S_CHANNEL_DEFAULT_CONFIG(I2S_NUM_AUTO, I2S_ROLE_MASTER);
+    chan_cfg.dma_desc_num = DMA_BUFFER_COUNT;
+    chan_cfg.dma_frame_num = DMA_BUFFER_SIZE;
+
+    // Play silence after all DMA buffers are empty
+    chan_cfg.auto_clear = true;
+
+    ESP_ERROR_CHECK(i2s_new_channel(&chan_cfg, &tx_chan, &rx_chan));
+
+    /* Step 2: Setting the configurations of standard mode, and initialize rx & tx channels
+     * The slot configuration and clock configuration can be generated by the macros
+     * These two helper macros is defined in 'i2s_std.h' which can only be used in STD mode.
+     * They can help to specify the slot and clock configurations for initialization or re-configuring */
+    i2s_std_config_t std_cfg = {
+        .clk_cfg  = I2S_STD_CLK_DEFAULT_CONFIG(SAMPLE_RATE),
+        //.slot_cfg = I2S_STD_MSB_SLOT_DEFAULT_CONFIG(I2S_DATA_BIT_WIDTH_16BIT, I2S_SLOT_MODE_STEREO),
+        .slot_cfg = I2S_STD_PHILIPS_SLOT_DEFAULT_CONFIG(I2S_DATA_BIT_WIDTH_16BIT, I2S_SLOT_MODE_STEREO),
+        .gpio_cfg = {
+            .mclk = 11,
+            .bclk = 13,
+            .ws   = 12,
+            .dout = 14,
+            .din  = I2S_GPIO_UNUSED,
+            .invert_flags = {
+                .mclk_inv = false,
+                .bclk_inv = false,
+                .ws_inv   = false,
+            },
+        },
+    };
+    /* Initialize the channels */
+    ESP_ERROR_CHECK(i2s_channel_init_std_mode(tx_chan, &std_cfg));
+    ESP_ERROR_CHECK(i2s_channel_init_std_mode(rx_chan, &std_cfg));
+}
+#endif
+
+static void i2s_init_idk_lol(void){
+    
+    static const i2s_config_t i2s_config = {
+        .mode = I2S_MODE_MASTER | I2S_MODE_TX,
+        .sample_rate = SAMPLE_RATE,
+        .bits_per_sample = 16,
+        .channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT,
+        //.communication_format = I2S_COMM_FORMAT_I2S | I2S_COMM_FORMAT_I2S_MSB,
+
+        .communication_format = I2S_COMM_FORMAT_I2S | I2S_COMM_FORMAT_I2S_MSB,
+        .intr_alloc_flags = 0, // default interrupt priority
+        .dma_buf_count = DMA_BUFFER_COUNT,
+        .dma_buf_len = DMA_BUFFER_SIZE,
+        .use_apll = false
+    };
+    static const i2s_pin_config_t pin_config = {
+        .bck_io_num = 13,
+        .mck_io_num = 11,
+        .ws_io_num = 12,
+        .data_out_num = 14,
+        .data_in_num = I2S_PIN_NO_CHANGE
+    };
+    i2s_driver_install(I2S_PORT, &i2s_config, 0, NULL);
+
+    i2s_set_pin(I2S_PORT, &pin_config);
+
+}
+
+static float FREQ_TABLE[] = {
+    523.25,
+    587.33,
+ 659.25 ,
+ 698.46 ,
+ 783.99 ,
+ 880.00 ,
+ 987.77 ,
+ 1046.50,
+ 1174.66,
+ 1318.51,
+};
+
+#if 0
+static float FREQ_TABLE[] = {
+     783.99,
+     415.30,
+     440.0,
+     493.88,
+     523.25,
+     587.33,
+     659.25,
+     698.46,
+     830.61,
+     880.00,
+};
+#endif
+
+static void _audio_init(void) {
+    init_scope(241);
+    //i2s_init_std_duplex();
+    i2s_init_idk_lol();
+    //ESP_ERROR_CHECK(i2s_channel_enable(tx_chan));
+
+    for(int i = 0; i < NUM_SYNTH; i++){
+        if((i%2) || (!DRUMS_TOP) ){ //bottom leaves
+            synths[i].decay_steps = 50;
+            synths[i].attack_steps = 3;
+            if(i == 2 || i == 8) synths[i].attack_steps = 15;
+            synths[i].vol = 1.;
+            synths[i].gate = 0.01;
+            synths[i].freq = FREQ_TABLE[i]/2;
+            synths[i].counter = 0;
+            synths[i].bend = 1;
+            synths[i].noise_reg = 1;
+            synths[i].waveform = 1;
+            synths[i].skip_hold = !(i % 2);
+            if(DRUMS_TOP) synths[i].waveform = 4;
+        } else { //top leaves
+            synths[i].decay_steps = 50;
+            synths[i].attack_steps = 0;
+            synths[i].bend = 1;
+            synths[i].vol = 0.0;
+            synths[i].skip_hold = 1;
+            switch(i){
+                case 0: //kick
+                    synths[i].freq = 1800;
+                    break;
+                case 2: //snare 1
+                    synths[i].freq = 5000;
+                    break;
+                case 4: //snare 2
+                    synths[i].freq = 6000;
+                    break;
+                case 6: //hihat
+                    synths[i].freq = 12000;
+                    break;
+                case 8: //crash
+                    synths[i].freq = 16000;
+                    synths[i].gate = 0.1;
+                    break;
+            }
+            synths[i].counter = 0;
+            synths[i].waveform = 8;
+            synths[i].noise_reg = 1;
+        }
+    }
+
+    TaskHandle_t handle;
+    xTaskCreate(&audio_player_task, "Audio player", 20000, NULL, configMAX_PRIORITIES - 1, &handle);
+}
+
+#define MIN(a,b) ((a > b) ? b : a)
+#define GLOBAL_VOL 3000
+#define LR_PHASE -1
+
+static void audio_player_task(void* arg) {
+    int16_t buffer[DMA_BUFFER_SIZE * 2];
+    //memset(buffer, 0, sizeof(buffer));
+
+    while(true) {
+
+        for(int i = 0; i < (DMA_BUFFER_SIZE * 2); i += 2){
+            float sample = 0;
+            for(int j = 0; j<NUM_SYNTH; j++){
+                sample += trad_osc(&(synths[j]));
+            }
+            write_to_scope((int16_t) (1600. * sample));
+            sample = GLOBAL_VOL * sample;
+            if(sample > 32767) sample = 32767;
+            if(sample < -32767) sample = -32767;
+            buffer[i] = (int16_t) sample;
+            buffer[i+1] = LR_PHASE * buffer[i];
+        }
+
+        size_t count = 0;
+        //i2s_channel_write(tx_chan, buffer, sizeof(buffer), &count, 1000);
+        i2s_write(I2S_PORT, buffer, sizeof(buffer), &count, 1000);
+        if (count != sizeof(buffer)) {
+            printf("i2s_write_bytes: count (%d) != length (%d)\n", count, sizeof(buffer));
+            abort();
+        }
+    }
+}
+
+void audio_init() { _audio_init(); }
+
+extern const int16_t boot_snd_start[] asm("_binary_boot_snd_start");
+extern const int16_t boot_snd_end[] asm("_binary_boot_snd_end");
+
+extern const int16_t pan_s16_start[] asm("_binary_pan_s16_start");
+extern const int16_t pan_s16_end[] asm("_binary_pan_s16_end");
+
+void play_bootsound() {
+    /*
+    sound_cfg_t sound = {0,};
+    sound.buffer = boot_snd_start, sound.size = boot_snd_end - boot_snd_start;
+    //sound.buffer = pan_s16_start, sound.size = pan_s16_end - pan_s16_start;
+    sound.slot = 10;
+    xQueueSend(sound_queue, &sound, 0);
+    */
+}
+
+void synth_set_freq(int i, float freq){
+    synths[i].freq = freq;
+}
+
+void synth_set_vol(int i, float vol){
+    synths[i].vol = vol;
+}
+
+#define NAT_LOG_SEMITONE 0.05776226504666215
+
+void synth_set_bend(int i, float bend){
+    if(bend != bend) return;
+    if((bend > -0.0001) && (bend < 0.0001)){
+        synths[i].bend = 1;
+    } else {
+        synths[i].bend = exp(bend * NAT_LOG_SEMITONE);
+    }
+}
+
+void synth_stop(int i){
+    if(synths[i].env_phase){
+        synths[i].env_phase = 3;
+    }
+}
+
+void synth_fullstop(int i){
+    synths[i].env_phase = 0;
+}
+
+void synth_start(int i){
+    synths[i].env_phase = 1; //put into attack phase;
+}
+
+float synth_get_env(int i){
+    return synths[i].env;
+}
diff --git a/ports/esp32/badge23/audio.h b/ports/esp32/badge23/audio.h
new file mode 100644
index 0000000000..3ab9a98ae8
--- /dev/null
+++ b/ports/esp32/badge23/audio.h
@@ -0,0 +1,14 @@
+#pragma once
+#include <stdint.h>
+
+#define SAMPLE_RATE 16000
+
+void audio_init();
+void play_bootsound();
+void synth_set_freq(int i, float freq);
+void synth_set_vol(int i, float vol);
+void synth_set_bend(int i, float bend);
+void synth_start(int i);
+void synth_stop(int i);
+void synth_fullstop(int i);
+float synth_get_env(int i);
diff --git a/ports/esp32/badge23/captouch.c b/ports/esp32/badge23/captouch.c
new file mode 100644
index 0000000000..2a133699bb
--- /dev/null
+++ b/ports/esp32/badge23/captouch.c
@@ -0,0 +1,417 @@
+//#include <stdio.h>
+//#include <string.h>
+#include "esp_log.h"
+#include "driver/i2c.h"
+
+
+static const char *TAG = "captouch";
+
+#define I2C_MASTER_NUM              0                          /*!< I2C master i2c port number, the number of i2c peripheral interfaces available will depend on the chip */
+
+#define AD7147_BASE_ADDR            0x2C
+
+#define AD7147_REG_PWR_CONTROL              0x00
+#define AD7147_REG_STAGE_CAL_EN             0x01
+#define AD7147_REG_STAGE_HIGH_INT_ENABLE    0x06
+#define AD7147_REG_DEVICE_ID                0x17
+
+#define TIMEOUT_MS                  1000
+
+struct ad714x_chip {
+    uint8_t addr;
+    uint8_t gpio;
+    int afe_offsets[13];
+    int stages;
+};
+
+static const struct ad714x_chip chip_top = {.addr = AD7147_BASE_ADDR + 1, .gpio = 48, .afe_offsets = {24, 12, 16, 33, 30, 28, 31, 27, 22, 24, 18, 19, }, .stages=12};
+static const struct ad714x_chip chip_bot = {.addr = AD7147_BASE_ADDR, .gpio = 3, .afe_offsets = {3, 2, 1, 1 ,1, 1, 1, 1, 2, 3}, .stages=10};
+
+static esp_err_t ad714x_i2c_write(const struct ad714x_chip *chip, const uint16_t reg, const uint16_t data)
+{
+    const uint8_t tx[] = {reg >> 8, reg & 0xFF, data >> 8, data & 0xFF};
+    ESP_LOGI(TAG, "AD7147 write reg %X-> %X", reg, data);
+    return i2c_master_write_to_device(I2C_MASTER_NUM, chip->addr, tx, sizeof(tx), TIMEOUT_MS / portTICK_PERIOD_MS);
+}
+
+static esp_err_t ad714x_i2c_read(const struct ad714x_chip *chip, const uint16_t reg, uint16_t *data, const size_t len)
+{
+    const uint8_t tx[] = {reg >> 8, reg & 0xFF};
+    uint8_t rx[len * 2];
+    esp_err_t ret = i2c_master_write_read_device(I2C_MASTER_NUM, chip->addr, tx, sizeof(tx), rx, sizeof(rx), TIMEOUT_MS / portTICK_PERIOD_MS);
+    for(int i = 0; i < len; i++) {
+        data[i] = (rx[i * 2] << 8) | rx[i * 2 + 1];
+    }
+    return ret;
+}
+
+struct ad7147_stage_config {
+    unsigned int cinX_connection_setup[13];
+    unsigned int se_connection_setup:2;
+    unsigned int neg_afe_offset_disable:1;
+    unsigned int pos_afe_offset_disable:1;
+    unsigned int neg_afe_offset:6;
+    unsigned int neg_afe_offset_swap:1;
+    unsigned int pos_afe_offset:6;
+    unsigned int pos_afe_offset_swap:1;
+    unsigned int neg_threshold_sensitivity:4;
+    unsigned int neg_peak_detect:3;
+    unsigned int pos_threshold_sensitivity:4;
+    unsigned int pos_peak_detect:3;
+};
+
+#define CIN CDC_NONE    0
+#define CIN_CDC_NEG     1
+#define CIN_CDC_POS     2
+#define CIN_BIAS        3
+
+static const uint16_t bank2 = 0x80;
+
+static void ad714x_set_stage_config(const struct ad714x_chip *chip, const uint8_t stage, const struct ad7147_stage_config * config)
+{
+    const uint16_t connection_6_0 = (config->cinX_connection_setup[6] << 12) | (config->cinX_connection_setup[5] << 10) | (config->cinX_connection_setup[4] << 8) | (config->cinX_connection_setup[3] << 6) | (config->cinX_connection_setup[2] << 4) | (config->cinX_connection_setup[1] << 2) | (config->cinX_connection_setup[0] << 0);
+    const uint16_t connection_12_7 = (config->pos_afe_offset_disable << 15) | (config->neg_afe_offset_disable << 14) | (config->se_connection_setup << 12) | (config->cinX_connection_setup[12] << 10) | (config->cinX_connection_setup[11] << 8) | (config->cinX_connection_setup[10] << 6) | (config->cinX_connection_setup[9] << 4) | (config->cinX_connection_setup[8] << 2) | (config->cinX_connection_setup[7] << 0);
+    const uint16_t afe_offset = (config->pos_afe_offset_swap << 15) | (config->pos_afe_offset << 8) | (config->neg_afe_offset_swap << 7) | (config->neg_afe_offset << 0);
+    const uint16_t sensitivity = (config->pos_peak_detect << 12) | (config->pos_threshold_sensitivity << 8) | (config->neg_peak_detect << 4) | (config->neg_threshold_sensitivity << 0);
+
+    //ESP_LOGI(TAG, "Stage %d config-> %X %X %X %X", stage, connection_6_0, connection_12_7, afe_offset, sensitivity);
+    //ESP_LOGI(TAG, "Config: %X %X %X %X %X %X %X %X %X", config->pos_afe_offset_disable, config->pos_afe_offset_disable, config->se_connection_setup, config->cinX_connection_setup[12], config->cinX_connection_setup[11], config->cinX_connection_setup[10], config->cinX_connection_setup[9], config->cinX_connection_setup[8], config->cinX_connection_setup[7]);
+
+    ad714x_i2c_write(chip, bank2 + stage * 8, connection_6_0);
+    ad714x_i2c_write(chip, bank2 + stage * 8 + 1, connection_12_7);
+    ad714x_i2c_write(chip, bank2 + stage * 8 + 2, afe_offset);
+    ad714x_i2c_write(chip, bank2 + stage * 8 + 3, sensitivity);
+}
+
+struct ad7147_device_config {
+    unsigned int power_mode:2;
+    unsigned int lp_conv_delay:2;
+    unsigned int sequence_stage_num:4;
+    unsigned int decimation:2;
+    unsigned int sw_reset:1;
+    unsigned int int_pol:1;
+    unsigned int ext_source:1;
+    unsigned int cdc_bias:2;
+
+    unsigned int stage0_cal_en:1;
+    unsigned int stage1_cal_en:1;
+    unsigned int stage2_cal_en:1;
+    unsigned int stage3_cal_en:1;
+    unsigned int stage4_cal_en:1;
+    unsigned int stage5_cal_en:1;
+    unsigned int stage6_cal_en:1;
+    unsigned int stage7_cal_en:1;
+    unsigned int stage8_cal_en:1;
+    unsigned int stage9_cal_en:1;
+    unsigned int stage10_cal_en:1;
+    unsigned int stage11_cal_en:1;
+    unsigned int avg_fp_skip:2;
+    unsigned int avg_lp_skip:2;
+
+    unsigned int stage0_high_int_enable:1;
+    unsigned int stage1_high_int_enable:1;
+    unsigned int stage2_high_int_enable:1;
+    unsigned int stage3_high_int_enable:1;
+    unsigned int stage4_high_int_enable:1;
+    unsigned int stage5_high_int_enable:1;
+    unsigned int stage6_high_int_enable:1;
+    unsigned int stage7_high_int_enable:1;
+    unsigned int stage8_high_int_enable:1;
+    unsigned int stage9_high_int_enable:1;
+    unsigned int stage10_high_int_enable:1;
+    unsigned int stage11_high_int_enable:1;
+};
+
+
+static void ad714x_set_device_config(const struct ad714x_chip *chip, const struct ad7147_device_config * config)
+{
+    const uint16_t pwr_control = (config->cdc_bias << 14) | (config->ext_source << 12) | (config->int_pol << 11) | (config->sw_reset << 10) | (config->decimation << 8) | (config->sequence_stage_num << 4) | (config->lp_conv_delay << 2) | (config->power_mode << 0);
+    const uint16_t stage_cal_en = (config->avg_lp_skip << 14) | (config->avg_fp_skip << 12) | (config->stage11_cal_en << 11) | (config->stage10_cal_en << 10) | (config->stage9_cal_en << 9) | (config->stage8_cal_en << 8) | (config->stage7_cal_en << 7) | (config->stage6_cal_en << 6) | (config->stage5_cal_en << 5) | (config->stage4_cal_en << 4) | (config->stage3_cal_en << 3) | (config->stage2_cal_en << 2) | (config->stage1_cal_en << 1) | (config->stage0_cal_en << 0);
+    const uint16_t stage_high_int_enable = (config->stage11_high_int_enable << 11) | (config->stage10_high_int_enable << 10) | (config->stage9_high_int_enable << 9) | (config->stage8_high_int_enable << 8) | (config->stage7_high_int_enable << 7) | (config->stage6_high_int_enable << 6) | (config->stage5_high_int_enable << 5) | (config->stage4_high_int_enable << 4) | (config->stage3_high_int_enable << 3) | (config->stage2_high_int_enable << 2) | (config->stage1_high_int_enable << 1) | (config->stage0_high_int_enable << 0);
+
+    ad714x_i2c_write(chip, AD7147_REG_PWR_CONTROL, pwr_control);
+    ad714x_i2c_write(chip, AD7147_REG_STAGE_CAL_EN, stage_cal_en);
+    ad714x_i2c_write(chip, AD7147_REG_STAGE_HIGH_INT_ENABLE, stage_high_int_enable);
+}
+
+static struct ad7147_stage_config ad714x_default_config(void)
+{
+    return (struct ad7147_stage_config) {
+            .cinX_connection_setup={CIN_BIAS, CIN_BIAS, CIN_BIAS, CIN_BIAS, CIN_BIAS, CIN_BIAS, CIN_BIAS, CIN_BIAS, CIN_BIAS, CIN_BIAS, CIN_BIAS, CIN_BIAS},
+            .se_connection_setup=0b01,
+            .pos_afe_offset=0,
+        };
+}
+
+#define ESP_INTR_FLAG_DEFAULT 0
+
+static QueueHandle_t gpio_evt_queue = NULL;
+
+static void IRAM_ATTR gpio_isr_handler(void* arg)
+{
+    struct ad714x_chip* chip = (struct ad714x_chip *) arg;
+    xQueueSendFromISR(gpio_evt_queue, &chip, NULL);
+}
+
+void espan_handle_captouch(uint16_t pressed_top, uint16_t pressed_bot);
+
+static uint16_t pressed_top, pressed_bot;
+void gpio_event_handler(void* arg)
+{
+    static unsigned long counter = 0;
+    struct ad714x_chip* chip;
+    uint16_t pressed;
+    while(true) {
+        /*
+        if(xQueueReceive(gpio_evt_queue, &chip, portMAX_DELAY)) {
+            ad714x_i2c_read(chip, 9, &pressed, 1);
+            ESP_LOGI(TAG, "Addr %x, High interrupt %X", chip->addr, pressed);
+
+            pressed &= ((1 << chip->stages) - 1);
+
+            if(chip == &chip_top) pressed_top = pressed;
+            if(chip == &chip_bot) pressed_bot = pressed;
+            espan_handle_captouch(pressed_top, pressed_bot);
+        }
+        */
+        vTaskDelay(1000/portTICK_PERIOD_MS);
+        counter++;
+        pressed = counter & ((1 << 5) - 1);
+        pressed_bot = pressed;
+        pressed_top = pressed;
+        espan_handle_captouch(pressed_top, pressed_bot);
+    }
+}
+
+static void captouch_init_chip(const struct ad714x_chip* chip, const struct ad7147_device_config device_config)
+{
+    uint16_t data;
+    ad714x_i2c_read(chip, AD7147_REG_DEVICE_ID, &data, 1);
+    ESP_LOGI(TAG, "DEVICE ID = %X", data);
+
+    ad714x_set_device_config(chip, &device_config);
+
+    for(int i=0; i<chip->stages; i++) {
+        struct ad7147_stage_config stage_config;
+        stage_config = ad714x_default_config();
+        stage_config.cinX_connection_setup[i] = CIN_CDC_POS;
+        stage_config.pos_afe_offset=chip->afe_offsets[i];
+        ad714x_set_stage_config(chip, i, &stage_config);
+    }
+
+    // Force calibration
+    ad714x_i2c_write(chip, 2, (1 << 14));
+
+    gpio_config_t io_conf = {};
+    io_conf.intr_type = GPIO_INTR_NEGEDGE;
+    io_conf.mode = GPIO_MODE_INPUT;
+    io_conf.pin_bit_mask = (1ULL << chip->gpio);
+    io_conf.pull_up_en = 1;
+    io_conf.pull_down_en = 0;
+    gpio_config(&io_conf);
+
+    gpio_evt_queue = xQueueCreate(10, sizeof(const struct ad714x_chip*));
+    xTaskCreate(gpio_event_handler, "gpio_event_handler", 2048 * 2, NULL, configMAX_PRIORITIES - 2, NULL);
+    gpio_isr_handler_add(chip->gpio, gpio_isr_handler, (void *)chip);
+
+}
+
+void captouch_init(void)
+{
+    gpio_install_isr_service(ESP_INTR_FLAG_DEFAULT);
+    captouch_init_chip(&chip_top, (struct ad7147_device_config){.sequence_stage_num = 11,
+                                                 .decimation = 1,
+                                                 .stage0_cal_en = 1,
+                                                 .stage1_cal_en = 1,
+                                                 .stage2_cal_en = 1,
+                                                 .stage3_cal_en = 1,
+                                                 .stage4_cal_en = 1,
+                                                 .stage5_cal_en = 1,
+                                                 .stage6_cal_en = 1,
+                                                 .stage7_cal_en = 1,
+                                                 .stage8_cal_en = 1,
+                                                 .stage9_cal_en = 1,
+                                                 .stage10_cal_en = 1,
+                                                 .stage11_cal_en = 1,
+
+                                                 .stage0_high_int_enable = 1,
+                                                 .stage1_high_int_enable = 1,
+                                                 .stage2_high_int_enable = 1,
+                                                 .stage3_high_int_enable = 1,
+                                                 .stage4_high_int_enable = 1,
+                                                 .stage5_high_int_enable = 1,
+                                                 .stage6_high_int_enable = 1,
+                                                 .stage7_high_int_enable = 1,
+                                                 .stage8_high_int_enable = 1,
+                                                 .stage9_high_int_enable = 1,
+                                                 .stage10_high_int_enable = 1,
+                                                 .stage11_high_int_enable = 1,
+                                                 });
+
+    captouch_init_chip(&chip_bot, (struct ad7147_device_config){.sequence_stage_num = 11,
+                                                 .decimation = 1,
+                                                 .stage0_cal_en = 1,
+                                                 .stage1_cal_en = 1,
+                                                 .stage2_cal_en = 1,
+                                                 .stage3_cal_en = 1,
+                                                 .stage4_cal_en = 1,
+                                                 .stage5_cal_en = 1,
+                                                 .stage6_cal_en = 1,
+                                                 .stage7_cal_en = 1,
+                                                 .stage8_cal_en = 1,
+                                                 .stage9_cal_en = 1,
+
+                                                 .stage0_high_int_enable = 1,
+                                                 .stage1_high_int_enable = 1,
+                                                 .stage2_high_int_enable = 1,
+                                                 .stage3_high_int_enable = 1,
+                                                 .stage4_high_int_enable = 1,
+                                                 .stage5_high_int_enable = 1,
+                                                 .stage6_high_int_enable = 1,
+                                                 .stage7_high_int_enable = 1,
+                                                 .stage8_high_int_enable = 1,
+                                                 .stage9_high_int_enable = 1,
+                                                 });
+}
+
+static void captouch_print_debug_info_chip(const struct ad714x_chip* chip)
+{
+    uint16_t data[12] = {0,};
+    uint16_t ambient[12] = {0,};
+    const int stages = chip->stages;
+#if 1
+    for(int stage=0; stage<stages; stage++) {
+        ad714x_i2c_read(chip, 0x0FA + stage * (0x104 - 0xE0), data, 1);
+        ESP_LOGI(TAG, "stage %d threshold: %X", stage, data[0]);
+    }
+
+    ad714x_i2c_read(chip, 0xB, data, stages);
+    ESP_LOGI(TAG, "CDC results: %X %X %X %X %X %X %X %X %X %X %X %X", data[0], data[1], data[2], data[3], data[4], data[5], data[6], data[7], data[8], data[9], data[10], data[11]);
+
+    for(int stage=0; stage<stages; stage++) {
+        ad714x_i2c_read(chip, 0x0F1 + stage * (0x104 - 0xE0), &ambient[stage], 1);
+        ESP_LOGI(TAG, "stage %d ambient: %X diff: %d", stage, ambient[stage], data[stage] - ambient[stage]);
+    }
+
+#endif
+#if 1
+    ad714x_i2c_read(chip, 8, data, 1);
+    ESP_LOGI(TAG, "Low interrupt %X", data[0]);
+    ad714x_i2c_read(chip, 9, data, 1);
+    ESP_LOGI(TAG, "High interrupt %X", data[0]);
+    ad714x_i2c_read(chip, 0x42, data, 1);
+    ESP_LOGI(TAG, "Proximity %X", data[0]);
+    //ESP_LOGI(TAG, "CDC result = %X", data[0]);
+    //if(data[0] > 0xa000) {
+        //ESP_LOGI(TAG, "Touch! %X", data[0]);
+    //}
+#endif
+}
+
+void captouch_print_debug_info(void)
+{
+    captouch_print_debug_info_chip(&chip_top);
+    captouch_print_debug_info_chip(&chip_bot);
+}
+
+void captouch_get_cross(int paddle, int *x, int *y)
+{
+    uint16_t data[12] = {0,};
+    uint16_t ambient[12] = {0,};
+
+    int result[2] = {0, 0};
+    float total = 0;
+
+#if 0
+    if(paddle == 2) {
+        ad714x_i2c_read(&chip_top, 0xB, data, 3);
+        //ESP_LOGI(TAG, "CDC results: %X %X %X %X %X %X %X %X %X %X %X %X", data[0], data[1], data[2], data[3], data[4], data[5], data[6], data[7], data[8], data[9], data[10], data[11]);
+
+        for(int stage=0; stage<3; stage++) {
+            ad714x_i2c_read(&chip_top, 0x0F1 + stage * (0x104 - 0xE0), &ambient[stage], 1);
+            //ESP_LOGI(TAG, "stage %d ambient: %X diff: %d", stage, ambient[stage], data[stage] - ambient[stage]);
+        }
+
+        int vectors[][2] = {{0, 0}, {0,240}, {240, 120}};
+        total = (data[0] - ambient[0]) + (data[1] - ambient[1]) + (data[2] - ambient[2]);
+
+        result[0] = vectors[0][0] * (data[0] - ambient[0]) + vectors[1][0] * (data[1] - ambient[1]) + vectors[2][0] * (data[2] - ambient[2]);
+        result[1] = vectors[0][1] * (data[0] - ambient[0]) + vectors[1][1] * (data[1] - ambient[1]) + vectors[2][1] * (data[2] - ambient[2]);
+    }
+
+    if(paddle == 8) {
+        ad714x_i2c_read(&chip_top, 0xB + 5, data + 5, 3);
+        //ESP_LOGI(TAG, "CDC results: %X %X %X %X %X %X %X %X %X %X %X %X", data[0], data[1], data[2], data[3], data[4], data[5], data[6], data[7], data[8], data[9], data[10], data[11]);
+
+        for(int stage=5; stage<8; stage++) {
+            ad714x_i2c_read(&chip_top, 0x0F1 + stage * (0x104 - 0xE0), &ambient[stage], 1);
+            //ESP_LOGI(TAG, "stage %d ambient: %X diff: %d", stage, ambient[stage], data[stage] - ambient[stage]);
+        }
+
+        int vectors[][2] = {{240, 240}, {240, 0}, {0, 120}};
+        total = (data[5] - ambient[5]) + (data[6] - ambient[6]) + (data[7] - ambient[7]);
+
+        result[0] = vectors[0][0] * (data[5] - ambient[5]) + vectors[1][0] * (data[6] - ambient[6]) + vectors[2][0] * (data[7] - ambient[7]);
+        result[1] = vectors[0][1] * (data[5] - ambient[5]) + vectors[1][1] * (data[6] - ambient[6]) + vectors[2][1] * (data[7] - ambient[7]);
+    }
+
+    *x = result[0] / total;
+    *y = result[1] / total;
+
+    //ESP_LOGI(TAG, "x=%d y=%d\n", *x, *y);
+#endif
+
+    const int paddle_info_1[] = {
+        4,
+        0,
+        1,
+        2,
+        11,
+        4,
+        9,
+        7,
+        6,
+        9,
+    };
+    const int paddle_info_2[] = {
+        3,
+        1,
+        0,
+        3,
+        10,
+        5,
+        8,
+        6,
+        5,
+        8,
+    };
+
+    struct ad714x_chip* chip;
+    if (paddle % 2 == 0) {
+        chip = &chip_top;
+    } else {
+        chip = &chip_bot;
+    }
+
+    ad714x_i2c_read(chip, 0xB, data, 12);
+    //ESP_LOGI(TAG, "CDC results: %X %X %X %X %X %X %X %X %X %X %X %X", data[0], data[1], data[2], data[3], data[4], data[5], data[6], data[7], data[8], data[9], data[10], data[11]);
+
+    for(int stage=0; stage<12; stage++) {
+        ad714x_i2c_read(chip, 0x0F1 + stage * (0x104 - 0xE0), &ambient[stage], 1);
+        //ESP_LOGI(TAG, "stage %d ambient: %X diff: %d", stage, ambient[stage], data[stage] - ambient[stage]);
+    }
+
+    int diff1 = data[paddle_info_1[paddle]] - ambient[paddle_info_1[paddle]];
+    int diff2 = data[paddle_info_2[paddle]] - ambient[paddle_info_2[paddle]];
+
+    ESP_LOGI(TAG, "%10d %10d", diff1, diff2);
+
+    int vectors[][2] = {{240, 240}, {240, 0}, {0, 120}};
+    total = ((diff1) + (diff2));
+
+    result[0] = vectors[0][0] * (diff1) + vectors[1][0] * (diff2);
+    result[1] = vectors[0][1] * (diff1) + vectors[1][1] * (diff2);
+
+    *x = result[0] / total;
+    *y = result[1] / total;
+}
diff --git a/ports/esp32/badge23/captouch.h b/ports/esp32/badge23/captouch.h
new file mode 100644
index 0000000000..8c3ff2aaf6
--- /dev/null
+++ b/ports/esp32/badge23/captouch.h
@@ -0,0 +1,6 @@
+#pragma once
+
+void captouch_init(void);
+void captouch_print_debug_info(void);
+void gpio_event_handler(void * arg);
+void captouch_get_cross(int paddle, int * x, int * y);
diff --git a/ports/esp32/badge23/components/gc9a01/CMakeLists.txt b/ports/esp32/badge23/components/gc9a01/CMakeLists.txt
new file mode 100644
index 0000000000..cb20ec6a4e
--- /dev/null
+++ b/ports/esp32/badge23/components/gc9a01/CMakeLists.txt
@@ -0,0 +1 @@
+idf_component_register(SRCS "gc9a01.c" REQUIRES driver INCLUDE_DIRS ".")
diff --git a/ports/esp32/badge23/components/gc9a01/Kconfig b/ports/esp32/badge23/components/gc9a01/Kconfig
new file mode 100644
index 0000000000..5d8d28900b
--- /dev/null
+++ b/ports/esp32/badge23/components/gc9a01/Kconfig
@@ -0,0 +1,121 @@
+menu "GC9A01 LCD Config"
+    config GC9A01_Width
+        int "GC9A01 LCD Width"
+        range 64 1024
+        default 240
+
+    config GC9A01_Height
+        int "GC9A01 LCD Height"
+        range 64 1024
+        default 240
+    #---------------------------------------------
+    choice GC9A01_SPI_HOST
+        prompt "GC9A01 SPI HOST"
+        default USE_SPI3_HOST
+        help
+            Hardware SPI , HSPI=SPI2 , VSPI=SPI3
+
+        config USE_SPI1_HOST
+            bool "USE SPI1 HOST"
+        config USE_SPI2_HOST
+            bool "USE SPI2 HOST"
+        config USE_SPI3_HOST
+            bool "USE SPI3 HOST"
+        config USE_SPI4_HOST
+            bool "USE SPI4 HOST"
+    endchoice
+
+    config GC9A01_SPI_HOST
+        int
+        default 0 if USE_SPI1_HOST
+        default 1 if USE_SPI2_HOST
+        default 2 if USE_SPI3_HOST
+        default 3 if USE_SPI4_HOST
+    #---------------------------------------------
+
+    config GC9A01_PIN_NUM_SCK
+        int "LCD SPI SCK Pin"
+        range 0 48
+        default 18
+        help
+            Must Support SPI SCK
+
+    config GC9A01_PIN_NUM_MOSI
+        int "LCD SPI MOSI Pin"
+        range 0 48
+        default 23
+        help
+            Must Support SPI MOSI
+
+    config GC9A01_PIN_NUM_CS
+        int "LCD SPI CS Pin"
+        range 0 48
+        default 05
+
+    config GC9A01_PIN_NUM_DC
+        int "LCD DC(Data or Command) GPIO Pin Number"
+        range 0 48
+        default 21
+
+    config GC9A01_SPI_SCK_FREQ_M
+        int "SPI Clock Freq in MHz"
+        range 1 80
+        default 40
+
+    config GC9A01_CONTROL_BACK_LIGHT_USED
+        bool "LCD Control Back Light"
+        default y
+
+    config GC9A01_PIN_NUM_BCKL
+        int "LCD BL(Back Light) GPIO Pin Number"
+        depends on GC9A01_CONTROL_BACK_LIGHT_USED
+        range 0 48
+        default 19
+
+    #---------------------------------------------
+    choice GC9A01_CONTROL_BACK_LIGHT_MODE
+        prompt "GC9A01b Control Back Light Mode"
+        default GC9A01_BACK_LIGHT_MODE_PWM
+        depends on GC9A01_CONTROL_BACK_LIGHT_USED
+        help
+            PWM use LEDC_TIMER_0 and LEDC_CHANNEL_0
+
+        config GC9A01_BACK_LIGHT_MODE_On_OFF
+            bool "USE GPIO ON/OF"
+        config GC9A01_BACK_LIGHT_MODE_PWM
+            bool "USE PWM"
+    endchoice
+
+    config GC9A01_CONTROL_BACK_LIGHT_MODE
+        int
+        default 0 if GC9A01_BACK_LIGHT_MODE_On_OFF
+        default 1 if GC9A01_BACK_LIGHT_MODE_PWM
+    #---------------------------------------------
+
+    config GC9A01_RESET_USED
+        bool "GC9A01 RESET Pin Used"
+        default y
+        help
+            Use GC9A01 Hard Reset Pin
+
+    config GC9A01_PIN_NUM_RST
+        int "LCD RST GPIO Pin Number"
+        depends on GC9A01_RESET_USED
+        range 0 48
+        default 22
+
+    config GC9A01_BUFFER_MODE
+        bool "Enable Buffer Mode"
+        default y
+        help
+            Disable for Direct Mode
+
+    config GC9A01_BUFFER_SCREEN_FAST_MODE
+        bool "Don't Convert Buffer for Screen_Load() & Screen_Shot()"
+        default n
+        depends on GC9A01_BUFFER_MODE
+        help
+            If Enabled , the Screen_load() & Screen_Shot() don't run SwapBytes() for Buffer
+            & Direct Save Data to SPI Buffer,So the data must manually SwapBytes before Send to LCD.
+
+endmenu
diff --git a/ports/esp32/badge23/components/gc9a01/LICENSE b/ports/esp32/badge23/components/gc9a01/LICENSE
new file mode 100644
index 0000000000..9fd35d36ac
--- /dev/null
+++ b/ports/esp32/badge23/components/gc9a01/LICENSE
@@ -0,0 +1,29 @@
+BSD 3-Clause License
+
+Copyright (c) 2021, liyanboy74
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+1. Redistributions of source code must retain the above copyright notice, this
+   list of conditions and the following disclaimer.
+
+2. Redistributions in binary form must reproduce the above copyright notice,
+   this list of conditions and the following disclaimer in the documentation
+   and/or other materials provided with the distribution.
+
+3. Neither the name of the copyright holder nor the names of its
+   contributors may be used to endorse or promote products derived from
+   this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/ports/esp32/badge23/components/gc9a01/gc9a01.c b/ports/esp32/badge23/components/gc9a01/gc9a01.c
new file mode 100644
index 0000000000..ac5770dc9a
--- /dev/null
+++ b/ports/esp32/badge23/components/gc9a01/gc9a01.c
@@ -0,0 +1,641 @@
+//--------------------------------------------------------------------------------------------------------
+// Nadyrshin Ruslan - [YouTube-channel: https://www.youtube.com/channel/UChButpZaL5kUUl_zTyIDFkQ]
+// Liyanboy74
+//--------------------------------------------------------------------------------------------------------
+#include <stdio.h>
+#include <string.h>
+
+#include "freertos/FreeRTOS.h"
+#include "freertos/task.h"
+
+#include "driver/gpio.h"
+#include "driver/spi_master.h"
+#include "driver/ledc.h"
+
+#include "sdkconfig.h"
+#include "gc9a01.h"
+
+#if (CONFIG_GC9A01_RESET_USED)
+#define RESET_HIGH()           gpio_set_level(CONFIG_GC9A01_PIN_NUM_RST,1)
+#define RESET_LOW()            gpio_set_level(CONFIG_GC9A01_PIN_NUM_RST,0)
+#endif
+
+#if(CONFIG_GC9A01_CONTROL_BACK_LIGHT_USED)
+#define BLK_HIGH()             gpio_set_level(CONFIG_GC9A01_PIN_NUM_BCKL,1)
+#define BLK_LOW()              gpio_set_level(CONFIG_GC9A01_PIN_NUM_BCKL,0)
+#endif
+
+#define Cmd_SLPIN       0x10
+#define Cmd_SLPOUT      0x11
+#define Cmd_INVOFF      0x20
+#define Cmd_INVON       0x21
+#define Cmd_DISPOFF     0x28
+#define Cmd_DISPON      0x29
+#define Cmd_CASET       0x2A
+#define Cmd_RASET       0x2B
+#define Cmd_RAMWR       0x2C
+#define Cmd_TEON      	0x35    // Tearing effect line ON
+#define Cmd_MADCTL      0x36    // Memory data access control
+#define Cmd_COLMOD      0x3A   // Pixel format set
+
+#define Cmd_DisplayFunctionControl    0xB6
+#define Cmd_PWCTR1       0xC1    // Power control 1
+#define Cmd_PWCTR2       0xC3    // Power control 2
+#define Cmd_PWCTR3       0xC4    // Power control 3
+#define Cmd_PWCTR4       0xC9    // Power control 4
+#define Cmd_PWCTR7       0xA7    // Power control 7
+
+#define Cmd_FRAMERATE      0xE8
+#define Cmd_InnerReg1Enable   0xFE
+#define Cmd_InnerReg2Enable   0xEF
+
+#define Cmd_GAMMA1       0xF0    // Set gamma 1
+#define Cmd_GAMMA2       0xF1    // Set gamma 2
+#define Cmd_GAMMA3       0xF2    // Set gamma 3
+#define Cmd_GAMMA4       0xF3    // Set gamma 4
+
+#define ColorMode_RGB_16bit  0x50
+#define ColorMode_RGB_18bit  0x60
+#define ColorMode_MCU_12bit  0x03
+#define ColorMode_MCU_16bit  0x05
+#define ColorMode_MCU_18bit  0x06
+
+#define MADCTL_MY        0x80
+#define MADCTL_MX        0x40
+#define MADCTL_MV        0x20
+#define MADCTL_ML        0x10
+#define MADCTL_BGR       0x08
+#define MADCTL_MH        0x04
+
+uint8_t GC9A01_X_Start = 0, GC9A01_Y_Start = 0;
+
+#if (CONFIG_GC9A01_BUFFER_MODE)
+DMA_ATTR uint16_t ScreenBuff[GC9A01_Height * GC9A01_Width];
+#endif
+
+//SPI Config
+spi_device_handle_t spi;
+spi_host_device_t LCD_HOST=CONFIG_GC9A01_SPI_HOST;
+
+//LEDC Config
+#if(CONFIG_GC9A01_CONTROL_BACK_LIGHT_USED)
+#if(CONFIG_GC9A01_CONTROL_BACK_LIGHT_MODE)
+ledc_channel_config_t  ledc_cConfig;
+ledc_timer_config_t    ledc_tConfig;
+void LEDC_PWM_Duty_Set(uint8_t DutyP);
+#endif
+#endif
+
+/*
+ The LCD needs a bunch of command/argument values to be initialized. They are stored in this struct.
+*/
+typedef struct {
+    uint8_t cmd;
+    uint8_t data[16];
+    uint8_t databytes; //No of data in data; bit 7 = delay after set; 0xFF = end of cmds.
+} lcd_init_cmd_t;
+
+static const lcd_init_cmd_t lcd_init_cmds[]={
+    {0xef,{0},0},
+    {0xeb,{0x14},1},
+    {0xfe,{0},0},
+    {0xef,{0},0},
+    {0xeb,{0x14},1},
+    {0x84,{0x40},1},
+    {0x85,{0xff},1},
+    {0x86,{0xff},1},
+    {0x87,{0xff},1},
+    {0x88,{0x0a},1},
+    {0x89,{0x21},1},
+    {0x8a,{0x00},1},
+    {0x8b,{0x80},1},
+    {0x8c,{0x01},1},
+    {0x8d,{0x01},1},
+    {0x8e,{0xff},1},
+    {0x8f,{0xff},1},
+    {Cmd_DisplayFunctionControl,{0x00,0x20},2},// Scan direction S360 -> S1
+    //{Cmd_MADCTL,{0x08},1},//MemAccessModeSet(0, 0, 0, 1);
+    //{Cmd_COLMOD,{ColorMode_MCU_16bit&0x77},1},
+    {0x90,{0x08,0x08,0x08,0x08},4},
+    {0xbd,{0x06},1},
+    {0xbc,{0x00},1},
+    {0xff,{0x60,0x01,0x04},3},
+    {Cmd_PWCTR2,{0x13},1},
+    {Cmd_PWCTR3,{0x13},1},
+    {Cmd_PWCTR4,{0x22},1},
+    {0xbe,{0x11},1},
+    {0xe1,{0x10,0x0e},2},
+    {0xdf,{0x21,0x0c,0x02},3},
+    {Cmd_GAMMA1,{0x45,0x09,0x08,0x08,0x26,0x2a},6},
+    {Cmd_GAMMA2,{0x43,0x70,0x72,0x36,0x37,0x6f},6},
+    {Cmd_GAMMA3,{0x45,0x09,0x08,0x08,0x26,0x2a},6},
+    {Cmd_GAMMA4,{0x43,0x70,0x72,0x36,0x37,0x6f},6},
+    {0xed,{0x1b,0x0b},2},
+    {0xae,{0x77},1},
+    {0xcd,{0x63},1},
+    {0x70,{0x07,0x07,0x04,0x0e,0x0f,0x09,0x07,0x08,0x03},9},
+    {Cmd_FRAMERATE,{0x34},1},// 4 dot inversion
+    {0x62,{0x18,0x0D,0x71,0xED,0x70,0x70,0x18,0x0F,0x71,0xEF,0x70,0x70},12},
+    {0x63,{0x18,0x11,0x71,0xF1,0x70,0x70,0x18,0x13,0x71,0xF3,0x70,0x70},12},
+    {0x64,{0x28,0x29,0xF1,0x01,0xF1,0x00,0x07},7},
+    {0x66,{0x3C,0x00,0xCD,0x67,0x45,0x45,0x10,0x00,0x00,0x00},10},
+    {0x67,{0x00,0x3C,0x00,0x00,0x00,0x01,0x54,0x10,0x32,0x98},10},
+    {0x74,{0x10,0x85,0x80,0x00,0x00,0x4E,0x00},7},
+    {0x98,{0x3e,0x07},2},
+    {Cmd_TEON,{0},0},// Tearing effect line on
+    {0, {0}, 0xff},//END
+};
+
+//This function is called (in irq context!) just before a transmission starts. It will
+//set the D/C line to the value indicated in the user field.
+static IRAM_ATTR void lcd_spi_pre_transfer_callback(spi_transaction_t *t)
+{
+    int dc=(int)t->user;
+    gpio_set_level(CONFIG_GC9A01_PIN_NUM_DC, dc);
+}
+
+/* Send a command to the LCD. Uses spi_device_polling_transmit, which waits
+ * until the transfer is complete.
+ *
+ * Since command transactions are usually small, they are handled in polling
+ * mode for higher speed. The overhead of interrupt transactions is more than
+ * just waiting for the transaction to complete.
+ */
+void lcd_cmd(uint8_t cmd)
+{
+    esp_err_t ret;
+    spi_transaction_t t;
+    memset(&t, 0, sizeof(t));       //Zero out the transaction
+    t.length=8;                     //Command is 8 bits
+    t.tx_buffer=&cmd;               //The data is the cmd itself
+    t.user=(void*)0;                //D/C needs to be set to 0
+    ret=spi_device_polling_transmit(spi, &t);  //Transmit!
+    assert(ret==ESP_OK);            //Should have had no issues.
+}
+
+/* Send data to the LCD. Uses spi_device_polling_transmit, which waits until the
+ * transfer is complete.
+ *
+ * Since data transactions are usually small, they are handled in polling
+ * mode for higher speed. The overhead of interrupt transactions is more than
+ * just waiting for the transaction to complete.
+ */
+void lcd_data(const uint8_t *data, int len)
+{
+    esp_err_t ret;
+    if (len==0) return;             //no need to send anything
+
+    /*
+    On certain MC's the max SPI DMA transfer length might be smaller than the buffer. We then have to split the transmissions.
+    */
+    int offset = 0;
+    do {
+        spi_transaction_t t;
+        memset(&t, 0, sizeof(t));       //Zero out the transaction
+
+        int tx_len = ((len - offset) < SPI_MAX_DMA_LEN) ? (len - offset) : SPI_MAX_DMA_LEN;
+        t.length=tx_len * 8;                       //Len is in bytes, transaction length is in bits.
+        t.tx_buffer= data + offset;                //Data
+        t.user=(void*)1;                           //D/C needs to be set to 1
+        ret=spi_device_polling_transmit(spi, &t);  //Transmit!
+        assert(ret==ESP_OK);                       //Should have had no issues.
+        offset += tx_len;                          // Add the transmission length to the offset
+    }
+    while (offset < len);
+}
+
+void lcd_send_byte(uint8_t Data)
+{
+	lcd_data(&Data,1);
+}
+
+void delay_ms (uint32_t Delay_ms)
+{
+	vTaskDelay(Delay_ms/portTICK_PERIOD_MS);
+}
+
+uint16_t GC9A01_GetWidth() {
+	return GC9A01_Width;
+}
+
+uint16_t GC9A01_GetHeight() {
+	return GC9A01_Height;
+}
+
+void GC9A01_HardReset(void) {
+	#if (CONFIG_GC9A01_RESET_USED)
+	RESET_LOW();
+	delay_ms(10);
+	RESET_HIGH();
+	delay_ms(150);
+	#endif
+}
+
+void GC9A01_SleepMode(uint8_t Mode) {
+	if (Mode)
+		lcd_cmd(Cmd_SLPIN);
+	else
+		lcd_cmd(Cmd_SLPOUT);
+
+	delay_ms(500);
+}
+
+void GC9A01_InversionMode(uint8_t Mode) {
+	if (Mode)
+		lcd_cmd(Cmd_INVON);
+	else
+		lcd_cmd(Cmd_INVOFF);
+}
+
+void GC9A01_DisplayPower(uint8_t On) {
+	if (On)
+		lcd_cmd(Cmd_DISPON);
+	else
+		lcd_cmd(Cmd_DISPOFF);
+}
+
+static void ColumnSet(uint16_t ColumnStart, uint16_t ColumnEnd) {
+	if (ColumnStart > ColumnEnd)
+		return;
+	if (ColumnEnd > GC9A01_Width)
+		return;
+
+	ColumnStart += GC9A01_X_Start;
+	ColumnEnd += GC9A01_X_Start;
+
+	lcd_cmd(Cmd_CASET);
+	lcd_send_byte(ColumnStart >> 8);
+	lcd_send_byte(ColumnStart & 0xFF);
+	lcd_send_byte(ColumnEnd >> 8);
+	lcd_send_byte(ColumnEnd & 0xFF);
+}
+
+static void RowSet(uint16_t RowStart, uint16_t RowEnd) {
+	if (RowStart > RowEnd)
+		return;
+	if (RowEnd > GC9A01_Height)
+		return;
+
+	RowStart += GC9A01_Y_Start;
+	RowEnd += GC9A01_Y_Start;
+
+	lcd_cmd(Cmd_RASET);
+	lcd_send_byte(RowStart >> 8);
+	lcd_send_byte(RowStart & 0xFF);
+	lcd_send_byte(RowEnd >> 8);
+	lcd_send_byte(RowEnd & 0xFF);
+}
+
+void GC9A01_SetWindow(uint8_t x0, uint8_t y0, uint8_t x1, uint8_t y1) {
+	ColumnSet(x0, x1);
+	RowSet(y0, y1);
+
+	lcd_cmd(Cmd_RAMWR);
+}
+
+static void ColorModeSet(uint8_t ColorMode) {
+	lcd_cmd(Cmd_COLMOD);
+	lcd_send_byte(ColorMode & 0x77);
+}
+
+static void MemAccessModeSet(uint8_t Rotation, uint8_t VertMirror,
+		uint8_t HorizMirror, uint8_t IsBGR) {
+	uint8_t Ret=0;
+	Rotation &= 7;
+
+	lcd_cmd(Cmd_MADCTL);
+
+	switch (Rotation) {
+	case 0:
+		Ret = 0;
+		break;
+	case 1:
+		Ret = MADCTL_MX;
+		break;
+	case 2:
+		Ret = MADCTL_MY;
+		break;
+	case 3:
+		Ret = MADCTL_MX | MADCTL_MY;
+		break;
+	case 4:
+		Ret = MADCTL_MV;
+		break;
+	case 5:
+		Ret = MADCTL_MV | MADCTL_MX;
+		break;
+	case 6:
+		Ret = MADCTL_MV | MADCTL_MY;
+		break;
+	case 7:
+		Ret = MADCTL_MV | MADCTL_MX | MADCTL_MY;
+		break;
+	}
+
+	if (VertMirror)
+		Ret = MADCTL_ML;
+	if (HorizMirror)
+		Ret = MADCTL_MH;
+
+	if (IsBGR)
+		Ret |= MADCTL_BGR;
+
+	lcd_send_byte(Ret);
+}
+
+void GC9A01_SetBL(uint8_t Value)
+{
+	if (Value > 100) Value = 100;
+	#if(CONFIG_GC9A01_CONTROL_BACK_LIGHT_USED)
+		#if(CONFIG_GC9A01_CONTROL_BACK_LIGHT_MODE)
+		LEDC_PWM_Duty_Set(Value);
+		#else
+		if (Value) BLK_HIGH();
+		else BLK_LOW();
+		#endif
+	#endif
+}
+
+//Direct Mode
+#if (!CONFIG_GC9A01_BUFFER_MODE)
+
+	void GC9A01_RamWrite(uint16_t *pBuff, uint16_t Len)
+	{
+	while (Len--)
+	{
+		lcd_send_byte(*pBuff >> 8);
+		lcd_send_byte(*pBuff & 0xFF);
+	}
+	}
+
+	void GC9A01_DrawPixel(int16_t x, int16_t y, uint16_t color)
+	{
+	if ((x < 0) ||(x >= GC9A01_Width) || (y < 0) || (y >= GC9A01_Height))
+		return;
+
+	GC9A01_SetWindow(x, y, x, y);
+	GC9A01_RamWrite(&color, 1);
+	}
+
+	void GC9A01_FillRect(int16_t x, int16_t y, int16_t w, int16_t h, uint16_t color)
+	{
+	if ((x >= GC9A01_Width) || (y >= GC9A01_Height))
+		return;
+
+	if ((x + w) > GC9A01_Width)
+		w = GC9A01_Width - x;
+
+	if ((y + h) > GC9A01_Height)
+		h = GC9A01_Height - y;
+
+	GC9A01_SetWindow(x, y, x + w - 1, y + h - 1);
+
+	for (uint32_t i = 0; i < (h * w); i++)
+		GC9A01_RamWrite(&color, 1);
+	}
+
+//Buffer mode
+#else
+
+	static void SwapBytes(uint16_t *color) {
+		uint8_t temp = *color >> 8;
+		*color = (*color << 8) | temp;
+	}
+
+	uint16_t GC9A01_GetPixel(int16_t x, int16_t y) {
+		if ((x < 0) || (x >= GC9A01_Width) || (y < 0) || (y >= GC9A01_Height))
+			return 0;
+
+		uint16_t color = ScreenBuff[y * GC9A01_Width + x];
+		SwapBytes(&color);
+		return color;
+	}
+
+	void GC9A01_FillRect(int16_t x, int16_t y, int16_t w, int16_t h,
+			uint16_t color) {
+		if ((w <= 0) || (h <= 0) || (x >= GC9A01_Width) || (y >= GC9A01_Height))
+			return;
+
+		if (x < 0) {
+			w += x;
+			x = 0;
+		}
+		if (y < 0) {
+			h += y;
+			y = 0;
+		}
+
+		if ((w <= 0) || (h <= 0))
+			return;
+
+		if ((x + w) > GC9A01_Width)
+			w = GC9A01_Width - x;
+		if ((y + h) > GC9A01_Height)
+			h = GC9A01_Height - y;
+
+		SwapBytes(&color);
+
+		for (uint16_t row = 0; row < h; row++) {
+			for (uint16_t col = 0; col < w; col++)
+				//GC9A01_DrawPixel(col, row, color);
+				ScreenBuff[(y + row) * GC9A01_Width + x + col] = color;
+		}
+	}
+
+	void GC9A01_Update()
+	{
+		int len = GC9A01_Width * GC9A01_Height;
+		GC9A01_SetWindow(0, 0, GC9A01_Width - 1, GC9A01_Height - 1);
+		lcd_data((uint8_t*) &ScreenBuff[0], len*2);
+	}
+
+	void GC9A01_Clear(void)
+	{
+		GC9A01_FillRect(0, 0, GC9A01_Width, GC9A01_Height, 0x0000);
+	}
+
+#endif
+
+static void gc9a01_GPIO_init(void)
+{
+	gpio_config_t gpiocfg={
+		.pin_bit_mask= ((uint64_t)1UL<<CONFIG_GC9A01_PIN_NUM_DC),
+		.mode=GPIO_MODE_OUTPUT,
+		.pull_up_en=GPIO_PULLUP_DISABLE,
+		.pull_down_en=GPIO_PULLDOWN_DISABLE,
+		.intr_type=GPIO_INTR_DISABLE,
+	};
+
+	gpio_config(&gpiocfg);
+	gpio_set_level(CONFIG_GC9A01_PIN_NUM_DC,0);
+
+	#if(CONFIG_GC9A01_RESET_USED)
+	gpiocfg.pin_bit_mask|=((uint64_t)1UL<<CONFIG_GC9A01_PIN_NUM_RST);
+	gpio_config(&gpiocfg);
+	gpio_set_level(CONFIG_GC9A01_PIN_NUM_RST,1);
+	#endif
+
+	#if(CONFIG_GC9A01_CONTROL_BACK_LIGHT_USED)
+	#if(!CONFIG_GC9A01_CONTROL_BACK_LIGHT_MODE)
+	gpiocfg.pin_bit_mask|=((uint64_t)1UL<<CONFIG_GC9A01_PIN_NUM_BCKL);
+	gpio_config(&gpiocfg);
+	gpio_set_level(CONFIG_GC9A01_PIN_NUM_BCKL,0);
+	#endif
+	#endif
+
+}
+
+void gc9a01_SPI_init(void)
+{
+	esp_err_t ret;
+    spi_bus_config_t buscfg={
+        .mosi_io_num=CONFIG_GC9A01_PIN_NUM_MOSI,
+        .miso_io_num=GPIO_NUM_NC,
+        .sclk_io_num=CONFIG_GC9A01_PIN_NUM_SCK,
+        .quadwp_io_num=-1,
+        .quadhd_io_num=-1,
+        .max_transfer_sz=250*250*2,
+    };
+    spi_device_interface_config_t devcfg={
+        .clock_speed_hz=CONFIG_GC9A01_SPI_SCK_FREQ_M*1000*1000,
+        .mode=0,
+        .spics_io_num=CONFIG_GC9A01_PIN_NUM_CS,
+        .queue_size=7,
+        .pre_cb=lcd_spi_pre_transfer_callback,
+    };
+
+    ret=spi_bus_initialize(LCD_HOST,&buscfg,SPI_DMA_CH_AUTO);
+    ESP_ERROR_CHECK(ret);
+
+    ret=spi_bus_add_device(LCD_HOST,&devcfg,&spi);
+    ESP_ERROR_CHECK(ret);
+}
+
+#if(CONFIG_GC9A01_CONTROL_BACK_LIGHT_USED)
+#if(CONFIG_GC9A01_CONTROL_BACK_LIGHT_MODE)
+void LEDC_PWM_Duty_Set(uint8_t DutyP)
+{
+	uint16_t Duty,MaxD;
+
+	MaxD=(1<<(int)ledc_tConfig.duty_resolution)-1;
+
+	if(DutyP>=100)Duty=MaxD;
+	else
+	{
+		Duty=DutyP*(MaxD/(float)100);
+	}
+	ledc_cConfig.duty=Duty;
+	ledc_channel_config(&ledc_cConfig);
+}
+
+void LEDC_Channel_Config(void)
+{
+	ledc_cConfig.gpio_num=CONFIG_GC9A01_PIN_NUM_BCKL;
+	ledc_cConfig.speed_mode=LEDC_LOW_SPEED_MODE;
+	ledc_cConfig.channel=LEDC_CHANNEL_0;
+	ledc_cConfig.intr_type=LEDC_INTR_DISABLE;
+	ledc_cConfig.timer_sel=LEDC_TIMER_0;
+	ledc_cConfig.duty=0;
+	ledc_cConfig.hpoint=0;
+	ledc_channel_config(&ledc_cConfig);
+}
+
+void LEDC_Timer_Config(void)
+{
+	ledc_tConfig.speed_mode=LEDC_LOW_SPEED_MODE ;
+	ledc_tConfig.duty_resolution=LEDC_TIMER_8_BIT;
+	//ledc_tConfig.bit_num=LEDC_TIMER_8_BIT;
+	ledc_tConfig.timer_num=LEDC_TIMER_0;
+	ledc_tConfig.freq_hz=1000;
+	ledc_tConfig.clk_cfg=LEDC_AUTO_CLK;
+	ledc_timer_config(&ledc_tConfig);
+}
+#endif
+#endif
+
+void GC9A01_Init()
+{
+	int cmd=0;
+
+	GC9A01_X_Start = 0;
+	GC9A01_Y_Start = 0;
+
+	gc9a01_GPIO_init();
+	gc9a01_SPI_init();
+
+	#if(CONFIG_GC9A01_CONTROL_BACK_LIGHT_USED)
+	#if(CONFIG_GC9A01_CONTROL_BACK_LIGHT_MODE)
+	LEDC_Timer_Config();
+	LEDC_Channel_Config();
+	#endif
+	#endif
+
+	#if(CONFIG_GC9A01_RESET_USED)
+	GC9A01_HardReset();
+	#endif
+
+    //Send all the commands
+    while (lcd_init_cmds[cmd].databytes!=0xff)
+	{
+        lcd_cmd(lcd_init_cmds[cmd].cmd);
+        lcd_data(lcd_init_cmds[cmd].data, lcd_init_cmds[cmd].databytes&0x1F);
+        if (lcd_init_cmds[cmd].databytes&0x80)
+		{
+            delay_ms(100);
+        }
+        cmd++;
+    }
+
+	MemAccessModeSet(0,0,0,1);
+	ColorModeSet(ColorMode_MCU_16bit);
+
+	GC9A01_InversionMode(1);
+	GC9A01_SleepMode(0);
+
+	delay_ms(120);
+	GC9A01_DisplayPower(1);
+	delay_ms(20);
+
+	#if(CONFIG_GC9A01_BUFFER_MODE)
+	GC9A01_Clear();
+	GC9A01_Update();
+	delay_ms(30);
+	#endif
+
+	#if(CONFIG_GC9A01_CONTROL_BACK_LIGHT_USED)
+	GC9A01_SetBL(100);
+	#endif
+}
+
+#if(CONFIG_GC9A01_BUFFER_MODE)
+void GC9A01_Screen_Shot(uint16_t x,uint16_t y,uint16_t width ,uint16_t height,uint16_t * Buffer)
+{
+	uint16_t i,j;
+	for (i=0;i<height;i++)
+	{
+		for(j=0;j<width;j++)
+		{
+			#if(!CONFIG_GC9A01_BUFFER_SCREEN_FAST_MODE)
+			Buffer[i*width+j]=GC9A01_GetPixel(x+j,y+i);
+			#else
+			Buffer[i*width+j]=ScreenBuff[((y+i) * GC9A01_Width )+ (x+j)];
+			#endif
+		}
+	}
+}
+void GC9A01_Screen_Load(uint16_t x,uint16_t y,uint16_t width ,uint16_t height,uint16_t * Buffer)
+{
+	uint16_t i,j;
+	for (i=0;i<height;i++)
+	{
+		for(j=0;j<width;j++)
+		{
+			#if(!CONFIG_GC9A01_BUFFER_SCREEN_FAST_MODE)
+			GC9A01_DrawPixel(x+j,y+i,Buffer[i*width+j]);
+			#else
+			ScreenBuff[((y+i) * GC9A01_Width )+ (x+j)] = Buffer[i*width+j];
+			#endif
+		}
+	}
+}
+#endif
diff --git a/ports/esp32/badge23/components/gc9a01/gc9a01.h b/ports/esp32/badge23/components/gc9a01/gc9a01.h
new file mode 100644
index 0000000000..aa587a2f07
--- /dev/null
+++ b/ports/esp32/badge23/components/gc9a01/gc9a01.h
@@ -0,0 +1,61 @@
+//--------------------------------------------------------------------------------------------------------
+// Nadyrshin Ruslan - [YouTube-channel: https://www.youtube.com/channel/UChButpZaL5kUUl_zTyIDFkQ]
+// Liyanboy74
+//--------------------------------------------------------------------------------------------------------
+#ifndef _GC9A01_H
+#define _GC9A01_H
+
+#include "sdkconfig.h"
+#include <stdint.h>
+
+#define CONFIG_GC9A01_Width 240
+#define CONFIG_GC9A01_Height 240
+//#define # CONFIG_USE_SPI1_HOST is not set
+//#define # CONFIG_USE_SPI2_HOST is not set
+#define CONFIG_USE_SPI3_HOST 1
+//#define # CONFIG_USE_SPI4_HOST is not set
+#define CONFIG_GC9A01_SPI_HOST 2
+#define CONFIG_GC9A01_PIN_NUM_SCK 39
+#define CONFIG_GC9A01_PIN_NUM_MOSI 41
+#define CONFIG_GC9A01_PIN_NUM_CS 40
+#define CONFIG_GC9A01_PIN_NUM_DC 42
+#define CONFIG_GC9A01_SPI_SCK_FREQ_M 80
+//#define # CONFIG_GC9A01_CONTROL_BACK_LIGHT_USED is not set
+#define CONFIG_GC9A01_RESET_USED 1
+#define CONFIG_GC9A01_PIN_NUM_RST 38
+#define CONFIG_GC9A01_BUFFER_MODE 1
+//#define # CONFIG_GC9A01_BUFFER_SCREEN_FAST_MODE is not set
+//#define # end of GC9A01 LCD Config
+
+
+#define GC9A01_Width	CONFIG_GC9A01_Width
+#define GC9A01_Height 	CONFIG_GC9A01_Height
+
+extern uint16_t ScreenBuff[GC9A01_Height * GC9A01_Width];
+
+static inline void GC9A01_DrawPixel(int16_t x, int16_t y, uint16_t color) {
+		if ((x < 0) || (x >= GC9A01_Width) || (y < 0) || (y >= GC9A01_Height))
+			return;
+
+		//SwapBytes(&color);
+
+		ScreenBuff[y * GC9A01_Width + x] = color;
+	}
+
+
+uint16_t GC9A01_GetWidth();
+uint16_t GC9A01_GetHeight();
+
+void GC9A01_Init();
+void GC9A01_SleepMode(uint8_t Mode);
+void GC9A01_DisplayPower(uint8_t On);
+void GC9A01_DrawPixel(int16_t x, int16_t y, uint16_t color);
+void GC9A01_FillRect(int16_t x, int16_t y, int16_t w, int16_t h, uint16_t color);
+void GC9A01_Update();
+void GC9A01_SetBL(uint8_t Value);
+uint16_t GC9A01_GetPixel(int16_t x, int16_t y);
+
+void GC9A01_Screen_Shot(uint16_t x,uint16_t y,uint16_t width ,uint16_t height,uint16_t * Buffer);
+void GC9A01_Screen_Load(uint16_t x,uint16_t y,uint16_t width ,uint16_t height,uint16_t * Buffer);
+
+#endif
diff --git a/ports/esp32/badge23/components/gc9a01/readme.md b/ports/esp32/badge23/components/gc9a01/readme.md
new file mode 100644
index 0000000000..46156f31f4
--- /dev/null
+++ b/ports/esp32/badge23/components/gc9a01/readme.md
@@ -0,0 +1,51 @@
+# GC9A01 ESP-IDF Component    
+
+Clone to `components` folder and run `idf.py menuconfig`
+
+![ESP-IDF_menuconfig](https://user-images.githubusercontent.com/64005694/111914456-43582400-8a87-11eb-9173-375262b5a261.jpg)
+
+**Add as submodule:**
+
+`git submodule add https://github.com/liyanboy74/gc9a01-esp-idf.git components/gc9a01`
+
+**Example Test:**
+
+```c
+#include <stdio.h>
+#include <string.h>
+#include <math.h>
+#include "freertos/FreeRTOS.h"
+#include "freertos/task.h"
+#include "driver/gpio.h"
+#include "sdkconfig.h"
+
+#include "gc9a01.h"
+
+#define STACK_SIZE              2048
+
+void LCD(void * arg)
+{
+    uint16_t Color;
+    GC9A01_Init();
+    for(;;)
+    {
+        Color=rand();
+        GC9A01_FillRect(0,0,239,239,Color);
+        GC9A01_Update();
+        vTaskDelay(1000/portTICK_PERIOD_MS);
+    }
+}
+
+void app_main(void)
+{
+    TaskHandle_t LCDHandle;
+
+    xTaskCreate(LCD,"Test LCD",STACK_SIZE,NULL,tskIDLE_PRIORITY,&LCDHandle);
+    configASSERT(LCDHandle);
+}
+
+```
+
+- If you succeed, it's time to go one layer higher! Try [Dispcolor](https://github.com/liyanboy74/dispcolor)
+- You can also use the [BMP24 to RGB565](https://github.com/liyanboy74/bmp24-to-rgb565) tools to convert and display images
+
diff --git a/ports/esp32/badge23/decode_image.h b/ports/esp32/badge23/decode_image.h
new file mode 100644
index 0000000000..6100703afc
--- /dev/null
+++ b/ports/esp32/badge23/decode_image.h
@@ -0,0 +1,33 @@
+/*
+   This example code is in the Public Domain (or CC0 licensed, at your option.)
+
+   Unless required by applicable law or agreed to in writing, this
+   software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
+   CONDITIONS OF ANY KIND, either express or implied.
+*/
+
+#pragma once
+#include <stdint.h>
+#include "esp_err.h"
+
+#define IMAGE_W 240
+#define IMAGE_H 240
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * @brief Decode the jpeg ``image.jpg`` embedded into the program file into pixel data.
+ *
+ * @param pixels A pointer to a pointer for an array of rows, which themselves are an array of pixels.
+ *        Effectively, you can get the pixel data by doing ``decode_image(&myPixels); pixelval=myPixels[ypos][xpos];``
+ * @return - ESP_ERR_NOT_SUPPORTED if image is malformed or a progressive jpeg file
+ *         - ESP_ERR_NO_MEM if out of memory
+ *         - ESP_OK on succesful decode
+ */
+esp_err_t decode_image(uint16_t **pixels);
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/ports/esp32/badge23/display.c b/ports/esp32/badge23/display.c
new file mode 100644
index 0000000000..2b627c664c
--- /dev/null
+++ b/ports/esp32/badge23/display.c
@@ -0,0 +1,121 @@
+#include "display.h"
+#include "components/gc9a01/gc9a01.h"
+
+#include "esp_log.h"
+#include <freertos/FreeRTOS.h>
+#include <freertos/task.h>
+#include <freertos/timers.h>
+#include <freertos/queue.h>
+#include <stdio.h>
+#include <string.h>
+#include <math.h>
+//#include <audio.h>
+
+#include "scope.h"
+#include "esp_system.h"
+
+uint16_t *pixels;
+#include "decode_image.h"
+
+typedef struct leds_cfg {
+    bool active_paddles[10];
+} display_cfg_t;
+
+static QueueHandle_t display_queue = NULL;
+static void display_task(TimerHandle_t aaaaa);
+//static void display_task(void* arg);
+
+static void _display_init() {
+    GC9A01_Init();
+
+#if 0
+    uint16_t Color;
+    for(;;)
+    {
+        Color=rand();
+        GC9A01_FillRect(0,0,239,239,Color);
+        GC9A01_Update();
+        vTaskDelay(1000/portTICK_PERIOD_MS);
+    }
+#endif
+
+    //decode_image(&pixels);
+    GC9A01_Screen_Load(0,0,240,240,pixels);
+    GC9A01_Update();
+
+    /*
+    display_queue = xQueueCreate(1, sizeof(display_cfg_t));
+    TaskHandle_t handle;
+    xTaskCreate(&display_task, "Display", 4096, NULL, configMAX_PRIORITIES - 3, &handle);
+    */
+    TimerHandle_t aa = xTimerCreate("Display", pdMS_TO_TICKS(100), pdTRUE, (void *) 0, *display_task);
+    if( xTimerStart(aa, 0 ) != pdPASS )
+    {
+    }
+}
+
+//static void display_task(void* arg) {
+static void display_task(TimerHandle_t aaaaa) {
+    display_cfg_t  display_;
+        // printf("hewo!");
+
+    //static const int paddle_pos[10][2] = {{120, 240}, {190, 217}, {234, 157}, {234, 82}, {190, 22}, {120, 0}, {49, 22}, {5, 82}, {5, 157}, {49, 217}};
+    uint16_t line[240];
+    //while(true) {
+        /*
+        printf("waiting...\n");
+        xQueueReceive(display_queue, &display_, portMAX_DELAY);
+        printf("go...\n");
+
+        bool any_active = false;
+        for(int i=0; i<10; i++) {
+            any_active |= display_.active_paddles[i];
+        }
+
+        if(any_active) {
+        */
+        // printf("hewwo!");
+        if(1) {
+            uint32_t t0 = esp_log_timestamp();
+            /*
+            for(int y=0; y<240; y++) {
+                for(int x=0; x<240; x++) {
+                    uint16_t Color=0;
+                    for(int i=0; i<10; i++) {
+                        if(display_.active_paddles[i]) {
+                            int x_d = x - paddle_pos[i][0];
+                            int y_d = y - paddle_pos[i][1];
+
+                            int dist = (x_d * x_d + y_d * y_d) / 64;
+                            Color += dist;
+                        }
+                    }
+                    line[x] = Color;
+                }
+                memcpy(&ScreenBuff[y * 240], line, sizeof(line));
+            }
+            */
+            begin_scope_read();
+            for(int y=0; y<240; y++){
+                read_line_from_scope(&(line[0]), y);
+                memcpy(&ScreenBuff[y * 240], line, sizeof(line));
+            }
+            end_scope_read();
+            uint32_t td = esp_log_timestamp() - t0;
+            // printf("it took %lu\n", td);
+
+        } else {
+            GC9A01_Screen_Load(0,0,240,240,pixels);
+        }
+        GC9A01_Update();
+    //}
+}
+
+void display_init() { _display_init(); }
+
+void display_show(bool active_paddles[10]) {
+    display_cfg_t display = {0,};
+
+    memcpy(display.active_paddles, active_paddles, sizeof(active_paddles[0]) * 10);
+    xQueueOverwrite(display_queue, &display);
+}
diff --git a/ports/esp32/badge23/display.h b/ports/esp32/badge23/display.h
new file mode 100644
index 0000000000..cd319d1be7
--- /dev/null
+++ b/ports/esp32/badge23/display.h
@@ -0,0 +1,6 @@
+#pragma once
+
+#include <stdbool.h>
+
+void display_init();
+void display_show(bool active_paddles[10]);
diff --git a/ports/esp32/badge23/espan.c b/ports/esp32/badge23/espan.c
new file mode 100644
index 0000000000..b3dcd3c99a
--- /dev/null
+++ b/ports/esp32/badge23/espan.c
@@ -0,0 +1,178 @@
+#include "captouch.h"
+#include "audio.h"
+#include "leds.h"
+#include "../../py/mphal.h"
+#include "display.h"
+
+#include "esp_log.h"
+#include "driver/i2c.h"
+#include "driver/spi_master.h"
+
+#include <stdio.h>
+#include <string.h>
+#include <math.h>
+#include <stdint.h>
+
+static const char *TAG = "espan";
+
+#define I2C_MASTER_NUM              0                          /*!< I2C master i2c port number, the number of i2c peripheral interfaces available will depend on the chip */
+#define I2C_MASTER_FREQ_HZ          400000                     /*!< I2C master clock frequency */
+#define I2C_MASTER_TX_BUF_DISABLE   0                          /*!< I2C master doesn't need buffer */
+#define I2C_MASTER_RX_BUF_DISABLE   0                          /*!< I2C master doesn't need buffer */
+
+#define CONFIG_I2C_MASTER_SDA 10
+#define CONFIG_I2C_MASTER_SCL 9
+
+
+static esp_err_t i2c_master_init(void)
+{
+    int i2c_master_port = I2C_MASTER_NUM;
+
+    i2c_config_t conf = {
+        .mode = I2C_MODE_MASTER,
+        .sda_io_num = CONFIG_I2C_MASTER_SDA,
+        .scl_io_num = CONFIG_I2C_MASTER_SCL,
+        .sda_pullup_en = GPIO_PULLUP_ENABLE,
+        .scl_pullup_en = GPIO_PULLUP_ENABLE,
+        .master.clk_speed = I2C_MASTER_FREQ_HZ,
+    };
+
+    i2c_param_config(i2c_master_port, &conf);
+
+    return i2c_driver_install(i2c_master_port, conf.mode, I2C_MASTER_RX_BUF_DISABLE, I2C_MASTER_TX_BUF_DISABLE, 0);
+}
+
+// channel -> paddle
+uint8_t top_map[] = {2, 2, 2, 0, 0, 8, 8, 8, 6, 6, 4, 4};
+uint8_t bot_map[] = {1, 1, 3, 3, 5, 5, 7, 7, 9, 9};
+
+static bool active_paddles[10];
+void espan_handle_captouch(uint16_t pressed_top, uint16_t pressed_bot)
+{
+
+    bool paddles[10] = {0,};
+    for(int i=0; i<12; i++) {
+        if(pressed_top  & (1 << i)) {
+            paddles[top_map[i]] = true;
+        }
+    }
+
+    for(int i=0; i<10; i++) {
+        if(pressed_bot  & (1 << i)) {
+            paddles[bot_map[i]] = true;
+        }
+    }
+
+    bool changed = false;
+    for(int i=0; i<10; i++) {
+        if(active_paddles[i] == false && paddles[i] == true) {
+            if(!(i == 2 || i == 8)) synth_start(i);
+            leds_animate(i);
+            active_paddles[i] = true;
+            changed = true;
+        } else if(active_paddles[i] == true && paddles[i] == false) {
+            active_paddles[i] = false;
+            changed = true;
+        if(!(i == 2 || i == 8)) synth_stop(i);
+        }
+    }
+
+    if(changed) {
+        //display_show(active_paddles);
+    }
+}
+
+#define VIB_DEPTH 0.01
+#define VIB 0
+#define VIOLIN 1
+#define VIOLIN_DECAY 10
+#define VIOLIN_VOL_BOOST 0.004
+#define VIOLIN_SENS_POW 2
+
+void old_app_main(void)
+{
+    ESP_ERROR_CHECK(i2c_master_init());
+    ESP_LOGI(TAG, "I2C initialized successfully");
+
+    vTaskDelay(1000 / portTICK_PERIOD_MS);
+    mp_hal_stdout_tx_str("test\n\r");
+
+    audio_init();
+    leds_init();
+    //display_init();
+    captouch_init();
+
+    mp_hal_stdout_tx_str("test2\n\r");
+    play_bootsound();
+    //not sure how slow captouch_get_cross is so duplicating edge detection here;
+    bool prev_petals[10] = {0};
+    //pitch bend as movement relative to inital touch pos to make intonation easier
+    int petal_refs[10] = {0};
+    int petal_tot_prev[10] = {0};
+    int petal_min[10] = {0};
+    int petal_max[10] = {0};
+    int petal_dir_prev[10] = {0};
+    int i = 0;
+    void * asdasd = &i;
+    while(1) {
+        gpio_event_handler(asdasd);
+
+        i = (i + 1) % 10;
+        if(!(i == 2 || i == 8)) continue;
+        if(VIB){
+            if(active_paddles[i]){
+                int x = 0;
+                int y = 0;
+                captouch_get_cross(i, &x, &y);
+                int tot = (x >> 1) + (y >> 1);
+                if(!prev_petals[i]){
+                    prev_petals[i] = 1;
+                    petal_refs[i] = tot;
+                    synth_set_bend(i, 0);
+                } else {
+                    float bend = tot - petal_refs[i];
+                    bend *= VIB_DEPTH;
+                    synth_set_bend(i, bend);
+                }
+            } else {
+                prev_petals[i] = 0;
+            }
+        }
+        if(VIOLIN){
+            if(active_paddles[i]){
+                int x = 0;
+                int y = 0;
+                captouch_get_cross(i, &x, &y);
+                int tot = (x >> 1) + (y >> 1);
+
+                uint8_t dir = tot > petal_tot_prev[i];
+
+                if(dir != petal_dir_prev[i]){
+                    if(dir){
+                        petal_min[i] = petal_tot_prev[i];
+                    } else {
+                        petal_max[i] = petal_tot_prev[i];
+                    }
+                    petal_refs[i] = tot;
+                    float vol = (VIOLIN_VOL_BOOST) * (petal_max[i] - petal_min[i]);
+                    for(int i = 1; i < VIOLIN_SENS_POW; i++){
+                        vol *= vol;
+                    }
+                    vol += synth_get_env(i);
+                    if(vol > 1.) vol = 1;
+                    if(vol < 0.) vol = 0;
+                    synth_set_vol(i, vol);
+                    synth_start(i);
+                }
+                petal_tot_prev[i] = tot;
+                petal_dir_prev[i] = dir;
+            }
+        }
+
+        vTaskDelay(10 / portTICK_PERIOD_MS);
+        //captouch_print_debug_info();
+    }
+
+    ESP_ERROR_CHECK(i2c_driver_delete(I2C_MASTER_NUM));
+    ESP_LOGI(TAG, "I2C de-initialized successfully");
+}
diff --git a/ports/esp32/badge23/espan.h b/ports/esp32/badge23/espan.h
new file mode 100644
index 0000000000..85fa85ef1f
--- /dev/null
+++ b/ports/esp32/badge23/espan.h
@@ -0,0 +1,2 @@
+#pragma once
+void old_app_main(void);
diff --git a/ports/esp32/badge23/idf_component.yml b/ports/esp32/badge23/idf_component.yml
new file mode 100644
index 0000000000..6766c35944
--- /dev/null
+++ b/ports/esp32/badge23/idf_component.yml
@@ -0,0 +1,17 @@
+## IDF Component Manager Manifest File
+dependencies:
+  espressif/esp_jpeg: "==1.0.0"
+  ## Required IDF version
+  idf:
+    version: ">=4.1.0"
+  # # Put list of dependencies here
+  # # For components maintained by Espressif:
+  # component: "~1.0.0"
+  # # For 3rd party components:
+  # username/component: ">=1.0.0,<2.0.0"
+  # username2/component2:
+  #   version: "~1.0.0"
+  #   # For transient dependencies `public` flag can be set.
+  #   # `public` flag doesn't have an effect dependencies of the `main` component.
+  #   # All dependencies of `main` are public by default.
+  #   public: true
diff --git a/ports/esp32/badge23/instruments/instrument.c b/ports/esp32/badge23/instruments/instrument.c
new file mode 100644
index 0000000000..2b703921d5
--- /dev/null
+++ b/ports/esp32/badge23/instruments/instrument.c
@@ -0,0 +1,73 @@
+void render_audio(
+    instrument_descriptor_t instrument_descriptor,
+    instrument_t instance,
+    unsigned int sample_count,
+    float * output_vector
+){
+    for(unsigned int i = 0; i < sample_count; i++){
+        instrument_descriptor->render_audio_sample(instance, sample_count, output_vector[2*i]);
+    }
+}
+
+void render_audio_adding(
+    instrument_descriptor_t instrument_descriptor,
+    instrument_t instance,
+    unsigned int sample_count,
+    float * output_vector,
+    float gain
+){
+    if(gain <= 0.0000001) return;
+    float temp[2];
+    for(unsigned int i = 0; i < sample_count; i++){
+        instrument_descriptor->render_audio_sample(instance, sample_count, temp);
+        output_vector[2*i] += gain * buffer[0];
+        output_vector[2*i+1] += gain * buffer[1];
+    }
+}
+
+void append_instrument_descriptor(
+    instrument_descriptor_list_t * list,
+    void * construct_instrument_descriptor
+){
+    lle_t * element = list;
+    while(element->next != NULL){
+        element = element->next;
+    }
+    element->next = malloc(sizeof(lle_t);
+    if(element->next == NULL) return;
+    element = element->next;
+    element->next = NULL;
+
+    element->content = construct_instrument_descriptor();
+}
+
+instrument_descriptor_list_t * list_builtin_instrument_descriptors(){
+    //really hope we can make this more elegant some day
+    instrument_descriptor_list_t * list = NULL;
+
+    //add your instrument here!
+    append_instrument_descriptor(list, &minimal_example_descriptor);
+
+    return list;
+}
+
+void instantiate_instrument(active_instrument_list_t instruments,
+                            instrument_descriptor_t descriptor
+){
+    descriptor->new_instrument
+}
+
+void mix_instruments_audio(active_instrument_list_t instruments,
+                           unsigned int sample_count,
+                           float * output_vector
+){
+    active_instrument_t instrument = instruments;
+    while(instrument->next != NULL){
+        render_audio_adding(instrument->descriptor,
+                            instrument->instrument,
+                            sample_count,
+                            output_vector,
+                            instrument->gain)
+        instrument = instrument->next;
+    }
+}
diff --git a/ports/esp32/badge23/instruments/instrument.h b/ports/esp32/badge23/instruments/instrument.h
new file mode 100644
index 0000000000..77ad119847
--- /dev/null
+++ b/ports/esp32/badge23/instruments/instrument.h
@@ -0,0 +1,130 @@
+/* wip instrument api for badge23 (in large parts inspired by ladspa)
+
+current status: none of this is hooked up or functional or compiles, just drafting things rn
+
+some core concepts:
+
+- several instruments can run at the same time (e.g., drum computer running in background),
+with only one of them being in foreground and using buttons and display
+
+- instruments are as "self contained" as possible, i.e. they access buttons and display with
+minimal host involvement and pretty much just produce audio "on their own". aside from
+scheduling the host mostly does some cap touch preprocessing and the audio output mixdown
+
+- different timing requirements -> different "threads" for audio, buttons, display each
+(leds: special case, see below)
+
+open questions:
+
+- led animations: instruments should be able to output led patterns. maybe keeping a dummy
+led array in ram for each running instrument and allowing users to create and run "shaders"
+would be a desirable functional mode; do we want this/does this need any extra api?
+
+- for performance reasons: instruments are expected to behave themselves, i.e. not access hw
+without permission or write to read-only locations, can we do better than that without
+excessive overhead? and if we can, do we _want_ to? (devices that make electronic musical
+instruments malfunction on purpose are not uncommon in general)
+
+- badge link/cv data input/output: can probably be added to the descriptor easily? shouldn't
+freeze api before tho
+*/
+
+//===========================================================================================
+//some hardware dummy definitions, move somewhere else someday maybe
+typedef struct {
+    int intensity;  //touch strength, considered as no touch if below 0
+    int rad;        //radial position
+    int az;         //cw azimuthal position (only meaningful for top petals);
+} petal_t;
+
+typedef int button_t; //0: no press, -1: left, 1: right, 2: down
+
+typedef struct {    //read-only (shared between all instruments, unprotected)
+    petal_t petals[10];  //even: top, odd: bottom, 0 at usb-c jack, count ccw
+    button_t button;    //can be either left or right, depending on host
+                        //handedness settings. the other button is reserved for
+                        //host use and is not exposed here.
+} hardware_inputs_t;
+//===========================================================================================
+
+typedef void * instrument_t; //contains instrument instance data, not to be interpreted by host
+
+typedef struct _instrument_descriptor_t {
+    unsigned int unique_id;
+    //null terminated instrument name
+    const char * name;
+
+    //allocates memory for new instance data and returns pointer to it (mandatory)
+    instrument_t (* new_instrument)(const struct _instrument_descriptor * descriptor,
+                                    unsigned int sample_rate,
+                                    hardware_inputs_t * hw);
+
+    //frees instance data memory (mandatory)
+    void (* delete_instrument) (instrument_t instance);
+
+    //renders a single stereo audio sample (optional, NULL if not supported)
+    void (* render_audio_sample) (instrument_t instance);
+
+    //handles petal/button/sensor input, ideally runs every 3-8ms (optional, NULL if not supported)
+    void (* process_user_input) (instrument_t instance);
+
+    //only runs when instrument is in foreground (optional, NULL if not supported)
+    void (* update_display) (instrument_t instance);
+
+    //(optional, NULL if not supported)
+    void (* update_leds) (instrument_t instance);
+} instrument_descriptor_t;
+
+// this function is called once per instrument type before use (i.e. around boot) and returns a
+// filled out instrument descriptor struct to be used by the host for creating instrument instances
+// returns null if allocation fails
+const instrument_descriptor_t * contruct_instrument_descriptor();
+
+//===========================================================================================
+//host-side helper functions
+
+void render_audio(
+    instrument_descriptor_t instrument_descriptor,
+    instrument_t instance,
+    unsigned int sample_count,
+    float * output_vector
+);
+
+void render_audio_adding(
+    instrument_descriptor_t instrument_descriptor,
+    instrument_t instance,
+    unsigned int sample_count,
+    float * output_vector,
+    float gain
+);
+
+typedef struct {
+    void * content;
+    lle_t * next;
+} lle_t; //linked list element
+
+typedef lle_t instrument_descriptor_list_t;
+
+typedef struct {
+    instrument_t * instrument;
+    instrument_descriptor_t descriptor;
+    char is_foreground;
+    char is_rendering_leds;
+    float gain;
+} active_instrument_t;
+
+void append_instrument_descriptor(
+    instrument_descriptor_list_t * list,
+    void * construct_instrument_descriptor
+);
+
+instrument_descriptor_list_t * list_builtin_instrument_descriptors();
+
+typedef lle_t active_instrument_list_t;
+void mix_instruments_audio(active_instrument_list_t instruments,
+                           unsigned int sample_count,
+                           float * output_vector
+);
+
+
+//===========================================================================================
diff --git a/ports/esp32/badge23/instruments/minimal_example.c b/ports/esp32/badge23/instruments/minimal_example.c
new file mode 100644
index 0000000000..7673f22e17
--- /dev/null
+++ b/ports/esp32/badge23/instruments/minimal_example.c
@@ -0,0 +1,89 @@
+//simple instrument example implementation
+
+//trad_osc is made up rn, didn't check how the existing implementation works
+
+typedef struct{
+    unsigned int * sample_rate;
+    hardware_inputs_t * hw;
+    trad_osc_t[30] osc;
+    unsigned int sample_rate;
+    hardware_inputs_t * hw;
+    float last_freq; //frequency of last note played to write to display
+} minimal_example_t;
+
+void minimal_example_render_sample(instrument_handle inst, float * stereo_output){
+    float acc = 0;
+    for(int i = 0; i < 30; i++){
+        acc += trad_osc_run(&inst.osc[i], inst.sample_rate);
+    }
+    stereo_output[0] = acc; // both channels the same -> mono
+    stereo_output[1] = acc;
+}
+
+void minimal_example_process_user_input(instrument_handle inst){
+    static int petal_prev[10];
+    for(int i = 0; i < 10; i++){
+        if(inst->hw.petals[i].intensity > 0){    //if the pad is touched...
+            if(!petal_prev[i]){                 //and it's a new touch...
+                if(button != 2){ //if button isn't pressed: play single note in different octaves
+                    int j = i + (inst->hw.button + 1) * 10; //choose osc
+                    trad_osc_start(&inst.osc[j]);   //play a tone
+                    inst->last_freq = inst.osc[j].freq;
+                } else { //for button center: all the octaves at once
+                    trad_osc_start(&inst.osc[i]);   //play a tone
+                    trad_osc_start(&inst.osc[i+10]);   //play a tone
+                    trad_osc_start(&inst.osc[i+20]);   //play a tone
+                    inst->last_freq = inst.osc[i+10].freq;
+                }
+            }
+            petal_prev[i] = 1;
+        } else {
+            petal_prev[i] = 0;
+        }
+    }
+}
+
+void minimal_example_update_display(instrument_handle inst){
+    display_print("%f", inst->last_freq);
+}
+
+static float pad_to_freq(int pad){
+    int j = pad % 10;
+    if(j) j++;      //skip min 2nd
+    if(j>8) j++;    //skip maj 6th
+    j += (pad/10) * 12; //add octaves
+    float freq = 440 * pow(2., j/12.);
+}
+
+instrument_t new_minimal_example(
+    const struct _instrument_descriptor * descriptor, 
+    unsigned int sample_rate,
+    hardware_inputs_t * hw)
+{
+    instrument_t inst = malloc(sizeof(minimal_example_t));
+    if(inst == NULL) return NULL;
+    inst->sample_rate = sample_rate;
+    inst->hw = hw;
+    for(int i = 0; i < 30; i++){
+        inst->osc[i] = trad_osc_new(pad_to_freq(i), sample_rate);
+        //other osc parameters (wave, envelope, etc.) omitted for clarity
+    }
+    return inst;
+}
+
+void delete_minimal_example(instrument_t inst){
+    free(inst);
+}
+
+const instrument_descriptor_t * minimal_example_descriptor(){
+    instrument_descriptor_t * inst = malloc(sizeof(instrument_descriptor_t));
+    if(inst == NULL) return NULL;
+    inst->unique_id = 0;
+    inst->name = "simple instrument";
+    inst->render_audio_sample = &minimal_example_render_sample;
+    inst->new_instrument = &new_minimal_example;
+    inst->delete_instrument = &delete_minimal_example;
+    inst->update_display = NULL;
+    inst->process_user_input = minimal_example_user_input;
+    inst->update_leds = NULL;
+}
diff --git a/ports/esp32/badge23/leds.c b/ports/esp32/badge23/leds.c
new file mode 100644
index 0000000000..f314192269
--- /dev/null
+++ b/ports/esp32/badge23/leds.c
@@ -0,0 +1,348 @@
+#include "leds.h"
+
+#include "driver/spi_master.h"
+
+#include <freertos/FreeRTOS.h>
+#include <freertos/task.h>
+#include <freertos/queue.h>
+#include <stdio.h>
+#include <string.h>
+#include <math.h>
+
+#include "esp_system.h"
+
+typedef struct leds_cfg {
+    int             leaf;
+    size_t          size;
+    size_t          position;
+    size_t          slot;
+    int             timer;
+} leds_cfg_t;
+
+static QueueHandle_t leds_queue = NULL;
+static leds_cfg_t active_leds[11];
+static void leds_task(void* arg);
+
+#include "apa102LEDStrip.h"
+static struct apa102LEDStrip leds;
+
+//SPI Vars
+static spi_device_handle_t spi_led;
+static spi_transaction_t spiTransObject;
+static esp_err_t ret;
+static spi_bus_config_t buscfg;
+static spi_device_interface_config_t devcfg;
+
+#define totalPixels 40
+#define bytesPerPixel 4
+#define maxValuePerColour 255
+#define maxSPIFrameInBytes 8000
+#define maxSPIFrequency 10000000
+
+struct RGB
+{
+    unsigned char R;
+    unsigned char G;
+    unsigned char B;
+};
+
+struct HSV
+{
+    double H;
+    double S;
+    double V;
+};
+
+struct RGB HSVToRGB(struct HSV hsv) {
+    double r = 0, g = 0, b = 0;
+
+    if (hsv.S == 0)
+    {
+        r = hsv.V;
+        g = hsv.V;
+        b = hsv.V;
+    }
+    else
+    {
+        int i;
+        double f, p, q, t;
+
+        if (hsv.H == 360)
+            hsv.H = 0;
+        else
+            hsv.H = hsv.H / 60;
+
+        i = (int)trunc(hsv.H);
+        f = hsv.H - i;
+
+        p = hsv.V * (1.0 - hsv.S);
+        q = hsv.V * (1.0 - (hsv.S * f));
+        t = hsv.V * (1.0 - (hsv.S * (1.0 - f)));
+
+        switch (i)
+        {
+            case 0:
+                r = hsv.V;
+                g = t;
+                b = p;
+                break;
+
+            case 1:
+                r = q;
+                g = hsv.V;
+                b = p;
+                break;
+
+            case 2:
+                r = p;
+                g = hsv.V;
+                b = t;
+                break;
+
+            case 3:
+                r = p;
+                g = q;
+                b = hsv.V;
+                break;
+
+            case 4:
+                r = t;
+                g = p;
+                b = hsv.V;
+                break;
+
+            default:
+                r = hsv.V;
+                g = p;
+                b = q;
+                break;
+        }
+
+    }
+
+    struct RGB rgb;
+    rgb.R = r * 255;
+    rgb.G = g * 255;
+    rgb.B = b * 255;
+
+    return rgb;
+}
+
+static void renderLEDs()
+{
+    spi_device_queue_trans(spi_led, &spiTransObject, portMAX_DELAY);
+}
+
+static int setupSPI()
+{
+    //Set up the Bus Config struct
+    buscfg.miso_io_num=-1;
+    buscfg.mosi_io_num=18;
+    buscfg.sclk_io_num=8;
+    buscfg.quadwp_io_num=-1;
+    buscfg.quadhd_io_num=-1;
+    buscfg.max_transfer_sz=maxSPIFrameInBytes;
+
+    //Set up the SPI Device Configuration Struct
+    devcfg.clock_speed_hz=maxSPIFrequency;
+    devcfg.mode=0;
+    devcfg.spics_io_num=-1;
+    devcfg.queue_size=1;
+
+    //Initialize the SPI driver
+    ret=spi_bus_initialize(SPI2_HOST, &buscfg, SPI_DMA_CH_AUTO);
+    ESP_ERROR_CHECK(ret);
+    //Add SPI port to bus
+    ret=spi_bus_add_device(SPI2_HOST, &devcfg, &spi_led);
+    ESP_ERROR_CHECK(ret);
+    return ret;
+}
+
+static uint8_t black[] = {0,0,0};
+
+static const uint8_t pl[][9] = {
+                          {39,  0,  1,  2,  3,  4,  5,  6,  7},
+                          {3,   4,  5,  6,  7,  8,  9, 10, 11},
+                          {7,   8,  9, 10, 11, 12, 13, 14, 15},
+                          {11, 12, 13, 14, 15, 16, 17, 18, 19},
+                          {15, 16, 17, 18, 19, 20, 21, 22, 23},
+                          {19, 20, 21, 22, 23, 24, 25, 26, 27},
+                          {23, 24, 25, 26, 27, 28, 29, 30, 31},
+                          {27, 28, 29, 30, 31, 32, 33, 34, 35},
+                          {31, 32, 33, 34, 35, 36, 37, 38, 39},
+                          {35, 36, 37, 38, 39, 0,   1,  2,  3}
+                          };
+
+static const int8_t pan_leds[][11][2] = {
+                          {
+                                {pl[0][4], pl[0][4]}, {pl[0][3], pl[0][5]}, {pl[0][2], pl[0][6]}, {pl[0][1], pl[0][7]}, {pl[0][0], pl[0][8]},
+                                    {pl[4][0], pl[6][8]}, {pl[4][1], pl[6][7]}, {pl[4][2], pl[6][6]}, {pl[4][3], pl[6][5]}, {pl[4][4], pl[6][4]}
+                                ,{-1, -1}
+                          },
+                          {
+                                {pl[2][4], pl[0][4]}, {pl[2][3], pl[0][5]}, {pl[2][2], pl[0][6]}, {pl[2][1], pl[0][7]}, {pl[2][0], pl[0][8]},
+                                    {pl[6][8], pl[6][0]}, {pl[6][6], pl[6][1]}, {pl[6][6], pl[6][2]}, {pl[6][5], pl[6][3]}, {pl[6][4], pl[6][4]}
+                                ,{-1, -1}
+                          },
+                          {
+                                {pl[2][4], pl[2][4]}, {pl[2][3], pl[2][5]}, {pl[2][2], pl[2][6]}, {pl[2][1], pl[2][7]}, {pl[2][0], pl[2][8]},
+                                    {pl[6][0], pl[8][8]}, {pl[6][1], pl[8][7]}, {pl[6][2], pl[8][6]}, {pl[6][3], pl[8][5]}, {pl[6][4], pl[8][4]}
+                                ,{-1, -1}
+                          },
+                          {
+                                {pl[4][4], pl[2][4]}, {pl[4][3], pl[2][5]}, {pl[4][2], pl[2][6]}, {pl[4][1], pl[2][7]}, {pl[4][0], pl[2][8]},
+                                    {pl[8][8], pl[8][0]}, {pl[8][6], pl[8][1]}, {pl[8][6], pl[8][2]}, {pl[8][5], pl[8][3]}, {pl[8][4], pl[8][4]}
+                                ,{-1, -1}
+                          },
+                          {
+                                {pl[4][4], pl[4][4]}, {pl[4][3], pl[4][5]}, {pl[4][2], pl[4][6]}, {pl[4][1], pl[4][7]}, {pl[4][0], pl[4][8]},
+                                    {pl[8][0], pl[0][8]}, {pl[8][1], pl[0][7]}, {pl[8][2], pl[0][6]}, {pl[8][3], pl[0][5]}, {pl[8][4], pl[0][4]}
+                                ,{-1, -1}
+                          },
+                          {
+                                {pl[6][4], pl[4][4]}, {pl[6][3], pl[4][5]}, {pl[6][2], pl[4][6]}, {pl[6][1], pl[4][7]}, {pl[6][0], pl[4][8]},
+                                    {pl[0][8], pl[0][0]}, {pl[0][6], pl[0][1]}, {pl[0][6], pl[0][2]}, {pl[0][5], pl[0][3]}, {pl[0][4], pl[0][4]}
+                                ,{-1, -1}
+                          },
+                          {
+                                {pl[6][4], pl[6][4]}, {pl[6][3], pl[6][5]}, {pl[6][2], pl[6][6]}, {pl[6][1], pl[6][7]}, {pl[6][0], pl[6][8]},
+                                    {pl[0][0], pl[6][2]}, {pl[0][1], pl[2][7]}, {pl[0][2], pl[2][6]}, {pl[0][3], pl[2][5]}, {pl[0][4], pl[2][4]}
+                                ,{-1, -1}
+                          },
+                          {
+                                {pl[8][4], pl[6][4]}, {pl[8][3], pl[6][5]}, {pl[8][2], pl[6][6]}, {pl[8][1], pl[6][7]}, {pl[8][0], pl[6][8]},
+                                    {pl[2][8], pl[2][0]}, {pl[2][6], pl[2][1]}, {pl[2][6], pl[2][2]}, {pl[2][5], pl[2][3]}, {pl[2][4], pl[2][4]}
+                                ,{-1, -1}
+                          },
+                          {
+                                {pl[8][4], pl[8][4]}, {pl[8][3], pl[8][5]}, {pl[8][2], pl[8][6]}, {pl[8][1], pl[8][7]}, {pl[8][0], pl[8][8]},
+                                    {pl[2][0], pl[4][4]}, {pl[2][1], pl[4][7]}, {pl[2][2], pl[4][6]}, {pl[2][3], pl[4][5]}, {pl[2][4], pl[4][4]}
+                                ,{-1, -1}
+                          },
+                          {
+                                {pl[0][4], pl[8][4]}, {pl[0][3], pl[8][5]}, {pl[0][2], pl[8][6]}, {pl[0][1], pl[8][7]}, {pl[0][0], pl[8][8]},
+                                    {pl[4][8], pl[4][0]}, {pl[4][6], pl[4][1]}, {pl[4][6], pl[4][2]}, {pl[4][5], pl[4][3]}, {pl[4][4], pl[4][4]}
+                                ,{-1, -1}
+                          },
+
+                          };
+
+
+static void _leds_init() {
+    leds_queue = xQueueCreate(10, sizeof(leds_cfg_t));
+    memset(active_leds, 0 , sizeof(active_leds));
+
+    setupSPI();
+    initLEDs(&leds, totalPixels, bytesPerPixel, 255);
+
+    //Set up SPI tx/rx storage Object
+    memset(&spiTransObject, 0, sizeof(spiTransObject));
+    spiTransObject.length = leds._frameLength*8;
+    spiTransObject.tx_buffer = leds.LEDs;
+
+
+    TaskHandle_t handle;
+    xTaskCreate(&leds_task, "LEDs player", 4096, NULL, configMAX_PRIORITIES - 2, &handle);
+}
+
+static bool leds_active(void)
+{
+    for(size_t i=0; i<sizeof(active_leds) / sizeof(active_leds[0]); i++) {
+        if(active_leds[i].size != active_leds[i].position) {
+            return true;
+        }
+    }
+    return false;
+}
+
+
+static void leds_task(void* arg) {
+    leds_cfg_t  leds_;
+
+    static int timer = 0;
+
+    while(true) {
+        if(xQueueReceive(leds_queue, &leds_, 1)) {
+            leds_.position = 0;
+            active_leds[leds_.slot] = leds_;
+        }
+
+        if(leds_active()) {
+            for(int i=0; i<40; i++) {
+                setPixel(&leds, i, black);
+            }
+
+            for(size_t i=0; i<sizeof(active_leds) / sizeof(active_leds[0]); i++) {
+                if(active_leds[i].size == active_leds[i].position) continue;
+
+                int leaf = active_leds[i].leaf;
+                int position = active_leds[i].position;
+
+                uint8_t c[] = {0,0,0};
+                if(leaf % 2 == 0)
+                    c[0] = 32;
+                else
+                    c[2] = 32;
+
+                struct RGB rgb = HSVToRGB((struct HSV){leaf/9.*360,1,1});
+                c[0] = rgb.R / 4;
+                c[1] = rgb.G / 4;
+                c[2] = rgb.B / 4;
+
+                if(pan_leds[leaf][position][0] >= 0)
+                    setPixel(&leds, pan_leds[leaf][position][0], c);
+                if(pan_leds[leaf][position][1] >= 0)
+                    setPixel(&leds, pan_leds[leaf][position][1], c);
+
+                if(active_leds[i].timer-- == 0) {
+                    active_leds[i].timer = 2;
+                    active_leds[i].position += 1;
+                }
+
+            }
+
+            renderLEDs();
+            timer = 30;
+        }
+
+        static int phase = 0;
+
+        if(!leds_active()) {
+            // background anim
+
+            if(timer-- == 0) {
+                timer = 60;
+                for(int i=0; i<40; i++) {
+                    setPixel(&leds, i, black);
+                }
+
+                if(phase++ == 360) {
+                    phase = 0;
+                }
+
+                uint8_t c[] = {0,0,0};
+
+                for(int i=0; i<40; i++) {
+                    struct RGB rgb = HSVToRGB((struct HSV){(phase+i*3) % 360,1,0.125});
+                    c[0] = rgb.R / 1;
+                    c[1] = rgb.G / 1;
+                    c[2] = rgb.B / 1;
+                    setPixel(&leds, i, c);
+                }
+
+                renderLEDs();
+            }
+        }
+    }
+}
+
+void leds_init() { _leds_init(); }
+
+void leds_animate(int leaf) {
+    leds_cfg_t leds = {0,};
+
+    leds.leaf = leaf;
+    leds.slot = leaf;
+    leds.size = 11;
+    xQueueSend(leds_queue, &leds, 0);
+}
diff --git a/ports/esp32/badge23/leds.h b/ports/esp32/badge23/leds.h
new file mode 100644
index 0000000000..a14fc13bd1
--- /dev/null
+++ b/ports/esp32/badge23/leds.h
@@ -0,0 +1,4 @@
+#pragma once
+
+void leds_init();
+void leds_animate(int leaf);
diff --git a/ports/esp32/badge23/scope.c b/ports/esp32/badge23/scope.c
new file mode 100644
index 0000000000..689c691a1f
--- /dev/null
+++ b/ports/esp32/badge23/scope.c
@@ -0,0 +1,63 @@
+#include "scope.h"
+#include <string.h>
+
+scope_t * scope;
+
+void init_scope(uint16_t size){
+    scope_t * scp = malloc(sizeof(scope_t));
+    if(scp == NULL) scope = NULL;
+    scp->buffer_size = size;
+    scp->buffer = malloc(sizeof(int16_t) * scp->buffer_size);
+    if(scp->buffer == NULL){
+        free(scp);
+        scope = NULL;
+    } else {
+        memset(scp->buffer, 0, sizeof(int16_t) * scp->buffer_size);
+        scope = scp;
+    }
+}
+
+void write_to_scope(int16_t value){
+    if(scope->is_being_read) return;
+    if(scope == NULL) return;
+    scope->write_head_position++;
+    if(scope->write_head_position >= scope->buffer_size) scope->write_head_position = 0;
+    scope->buffer[scope->write_head_position] = value;
+}
+
+void begin_scope_read(){
+    if(scope == NULL) return;
+    scope->is_being_read = 1; //best mutex
+}
+
+void read_line_from_scope(uint16_t * line, int16_t point){
+    memset(line, 0, 480);
+    int16_t startpoint = scope->write_head_position - point;
+    while(startpoint < 0){
+        startpoint += scope->buffer_size;
+    }
+    int16_t stoppoint = startpoint - 1;
+    if(stoppoint < 0){
+        stoppoint += scope->buffer_size;
+    }
+    int16_t start = (scope->buffer[point]/32 + 120);
+    int16_t stop = (scope->buffer[point+1]/32 + 120);
+    if(start>240)   start = 240;
+    if(start<0)     start = 0;
+    if(stop>240)    stop = 240;
+    if(stop<0)     stop = 0;
+
+    if(start > stop){
+        int16_t inter = start;
+        start = stop;
+        stop = inter;
+    }
+    for(int16_t i = start; i <= stop; i++){
+        line[i] = 255;
+    }
+}
+
+void end_scope_read(){
+    if(scope == NULL) return;
+    scope->is_being_read = 0; //best mutex
+}
diff --git a/ports/esp32/badge23/scope.h b/ports/esp32/badge23/scope.h
new file mode 100644
index 0000000000..302bea447a
--- /dev/null
+++ b/ports/esp32/badge23/scope.h
@@ -0,0 +1,15 @@
+#pragma once
+#include <stdint.h>
+
+typedef struct {
+    int16_t * buffer;
+    int16_t buffer_size;
+    int16_t write_head_position; // last written value
+    volatile uint8_t is_being_read;
+} scope_t;
+
+void init_scope(uint16_t size);
+void write_to_scope(int16_t value);
+void begin_scope_read();
+void end_scope_read();
+void read_line_from_scope(uint16_t * line, int16_t point);
diff --git a/ports/esp32/badge23/synth.c b/ports/esp32/badge23/synth.c
new file mode 100644
index 0000000000..1c87a89b11
--- /dev/null
+++ b/ports/esp32/badge23/synth.c
@@ -0,0 +1,167 @@
+#include "synth.h"
+#include "audio.h"
+#include <math.h>
+
+float ks_osc(ks_osc_t * ks, float input){
+    //TODO: FIX THIS
+    ks->real_feedback = ks->feedback;
+
+    float delay_time = ((float) (SAMPLE_RATE))/ks->freq;
+    if(delay_time >= (KS_BUFFER_SIZE)) delay_time = (KS_BUFFER_SIZE) - 1;
+
+
+    //ks->tape[0] = input + real_feedback * ks->tape[delay_time];    
+    return ks->tape[0];
+}
+
+float waveshaper(uint8_t shape, float in);
+float nes_noise(uint16_t * reg, uint8_t mode, uint8_t run);
+
+void trad_env(trad_osc_t * osc){
+    switch(osc->env_phase){
+        case 0:
+            osc->env = 0; osc->counter = 0; osc->env_counter = 0;
+            break;
+        case 1:
+            if(osc->attack_steps){
+                if(osc->env == 0){
+                    osc->env = (TRAD_OSC_MIN_ATTACK_ENV);
+                } else {
+                    osc->env_counter++;
+                    if(osc->env_counter > osc->attack_steps){
+                        osc->env *= (1. + (TRAD_OSC_ATTACK_STEP));
+                        osc->env_counter = 0;
+                    }
+                }
+            } else {
+                osc->env += osc->vol/TRAD_OSC_ATTACK_POP_BLOCK;
+            }
+            if(osc->env > osc->vol){
+                osc->env_phase = 2;
+                osc->env = osc->vol;
+            }
+            break;
+        case 2:
+            osc->env = osc->vol;
+            osc->env_counter = 0;
+            if(osc->skip_hold) osc->env_phase = 3;
+            break;
+        case 3:
+            if(osc->decay_steps){
+                osc->env_counter++;
+                if(osc->env_counter > osc->decay_steps){
+                    osc->env *= (1. - (TRAD_OSC_DECAY_STEP));
+                    osc->env_counter = 0;
+                }
+                if(osc->env < osc->gate){
+                    osc->env_phase = 0; osc->env = 0; osc->counter = 0;
+                }
+            } else {
+                osc->env_phase = 0; osc->env = 0; osc->counter = 0;
+            }
+            break;
+    }
+}
+
+float trad_osc(trad_osc_t * osc){
+    trad_env(osc);
+    if(!osc->env_phase) return 0;
+    float ret = 0;
+
+    //run core sawtooth
+    float freq = osc->freq * osc->bend;
+    if(freq > 10000) freq = 10000;
+    if(freq < -10000) freq = -10000;
+    if(freq != freq) freq = 0;
+    osc->counter += 2. * freq / ((float)(SAMPLE_RATE));
+    if(osc->counter != osc->counter){
+        printf("trad_osc counter is NaN");
+        abort();
+    }
+    while(osc->counter > 1.){
+        osc->counter -= 2.;
+        osc->overflow_event = 1;
+    }
+    while(osc->counter < -1.){
+        osc->counter += 2.;
+        osc->overflow_event = -1;
+    }
+
+    if(osc->waveform >= 7){
+        ret = nes_noise(&(osc->noise_reg), osc->waveform == 7, osc->overflow_event);
+        osc->overflow_event = 0;
+    } else {
+        //apply waveshaper
+        ret = waveshaper(osc->waveform, osc->counter);
+    }
+
+    //apply volume
+    ret *= osc->env;
+    return ret;
+}
+
+float nes_noise(uint16_t * reg, uint8_t mode, uint8_t run){
+    if(run) {
+        uint8_t fb = *reg;
+        if(mode){
+            fb = fb>>6;
+        } else {
+            fb = fb>>1;
+        }
+        fb = (fb ^ (*reg)) & 1;
+        *reg = (*reg >> 1);
+        *reg = (*reg) | (((uint16_t) fb) << 14);
+    }
+    return ((float) ((*reg) & 1)) * 2 - 1;
+}
+
+float waveshaper(uint8_t shape, float in){
+    //expects sawtooth input in [-1..1] range
+    switch(shape){
+        case 0: // TODO: implement proper sine
+            in = sin(in * 3.1415);
+            break;
+        case 1: //fast sine
+            in = waveshaper(2, in);
+            if(in > 0.){
+                in = 1. - in;
+                in *= in;
+                in = 1. - in;
+            } else {
+                in = 1. + in;
+                in *= in;
+                in = in - 1.;
+            }
+            break;
+        case 2: //triangle
+            in += 0.5;
+            if(in > 1.0) in -= 2;
+            if(in > 0.) in = -in;
+            in = (2. * in) + 1.;
+            break;
+        case 3: //sawtooth
+            break;
+        case 4: //square
+            if(in > 0){
+                in = 1.;
+            } else {
+                in = -1.;
+            }
+            break;
+        case 5: //33% pulse
+            if(in > 0.33){
+                in = 1.;
+            } else {
+                in = -1.;
+            }
+            break;
+        case 6: //25% pulse
+            if(in > 0.5){
+                in = 1.;
+            } else {
+                in = -1.;
+            }
+            break;
+    }
+    return in;
+}
diff --git a/ports/esp32/badge23/synth.h b/ports/esp32/badge23/synth.h
new file mode 100644
index 0000000000..5d498731cd
--- /dev/null
+++ b/ports/esp32/badge23/synth.h
@@ -0,0 +1,48 @@
+#pragma once
+#include <stdint.h>
+#include <stdio.h>
+
+#define TRAD_OSC_DECAY_STEP          0.01
+#define TRAD_OSC_ATTACK_STEP          0.01
+#define TRAD_OSC_MIN_ATTACK_ENV          0.01
+#define TRAD_OSC_ATTACK_POP_BLOCK          16
+typedef struct {
+    //user variables
+    float       freq;           //in hertz, negative frequencies for linFM allowed
+    float       bend;
+    float       vol;            //output volume
+    float       env;
+    uint8_t     env_phase;      //0: off, 1: attack, 2: hold, 3: decay
+    uint8_t     skip_hold;
+    float       gate;           //below what volume the oscillator stops and returns 0
+    uint16_t    decay_steps;    //after how many sample rate steps the volume reduces
+                                //by factor TRAD_OSC_DECAY_STEP, set 0 for no decay
+    uint16_t    attack_steps;
+    uint8_t     waveform;       //0: sine, 1: fast sine, 2: tri, 3: saw,
+                                //4: square, 5: 33% pulse, 6: 25% pulse
+
+    //internal data storage, not for user access
+    float       counter;        //state of central sawtooth oscillator, [-1..1] typ.
+    uint16_t    env_counter;
+    int8_t      overflow_event; //set to -1 when counter underflows (below -1),
+                                //set to +1 when counter overflows (above 1)
+                                //not reset or used by anything so far
+    uint16_t    noise_reg;
+} trad_osc_t;
+
+//#define KS_BUFFER_SIZE (SAMPLE_RATE)/20
+#define KS_BUFFER_SIZE 800
+
+typedef struct {
+    //user variables
+    float freq;                 //frequency in hertz, negative frequencies are rectified,
+                                //minimum freq determined by KS_BUFFER_SIZE
+    float feedback;             //feedback value, will be compensated with frequency
+                                //for equal decay across spectrum, [-1..1] without
+    
+    //internal data storage, not for user access
+    float tape[KS_BUFFER_SIZE]; //the delay chain
+    float real_feedback;        //compensated feedback value
+} ks_osc_t; //karplus strong
+
+float trad_osc(trad_osc_t * osc);
diff --git a/ports/esp32/badge23/tags b/ports/esp32/badge23/tags
new file mode 100644
index 0000000000..afb47d0f15
--- /dev/null
+++ b/ports/esp32/badge23/tags
@@ -0,0 +1,162 @@
+!_TAG_FILE_FORMAT	2	/extended format; --format=1 will not append ;" to lines/
+!_TAG_FILE_SORTED	1	/0=unsorted, 1=sorted, 2=foldcase/
+!_TAG_OUTPUT_EXCMD	mixed	/number, pattern, mixed, or combineV2/
+!_TAG_OUTPUT_FILESEP	slash	/slash or backslash/
+!_TAG_OUTPUT_MODE	u-ctags	/u-ctags or e-ctags/
+!_TAG_PATTERN_LENGTH_LIMIT	96	/0 for no limit/
+!_TAG_PROC_CWD	/home/zjfms/fluff/badge2023/bootstrap/software/espan/main/	//
+!_TAG_PROGRAM_AUTHOR	Universal Ctags Team	//
+!_TAG_PROGRAM_NAME	Universal Ctags	/Derived from Exuberant Ctags/
+!_TAG_PROGRAM_URL	https://ctags.io/	/official site/
+!_TAG_PROGRAM_VERSION	5.9.0	/p5.9.20220828.0/
+AD7147_BASE_ADDR	captouch.c	/^#define AD7147_BASE_ADDR /;"	d	file:
+AD7147_REG_DEVICE_ID	captouch.c	/^#define AD7147_REG_DEVICE_ID /;"	d	file:
+AD7147_REG_PWR_CONTROL	captouch.c	/^#define AD7147_REG_PWR_CONTROL /;"	d	file:
+AD7147_REG_STAGE_CAL_EN	captouch.c	/^#define AD7147_REG_STAGE_CAL_EN /;"	d	file:
+AD7147_REG_STAGE_HIGH_INT_ENABLE	captouch.c	/^#define AD7147_REG_STAGE_HIGH_INT_ENABLE /;"	d	file:
+CIN	captouch.c	/^#define CIN /;"	d	file:
+CIN_BIAS	captouch.c	/^#define CIN_BIAS /;"	d	file:
+CIN_CDC_NEG	captouch.c	/^#define CIN_CDC_NEG /;"	d	file:
+CIN_CDC_POS	captouch.c	/^#define CIN_CDC_POS /;"	d	file:
+CONFIG_I2C_MASTER_SCL	Kconfig.projbuild	/^    config I2C_MASTER_SCL$/;"	c	menu:Example Configuration
+CONFIG_I2C_MASTER_SCL_MODULE	Kconfig.projbuild	/^    config I2C_MASTER_SCL$/;"	c	menu:Example Configuration
+CONFIG_I2C_MASTER_SDA	Kconfig.projbuild	/^    config I2C_MASTER_SDA$/;"	c	menu:Example Configuration
+CONFIG_I2C_MASTER_SDA_MODULE	Kconfig.projbuild	/^    config I2C_MASTER_SDA$/;"	c	menu:Example Configuration
+DEF_apa102LEDStrip	apa102LEDStrip.h	/^#define DEF_apa102LEDStrip$/;"	d
+DMA_BUFFER_COUNT	audio.c	/^#define DMA_BUFFER_COUNT /;"	d	file:
+DMA_BUFFER_SIZE	audio.c	/^#define DMA_BUFFER_SIZE /;"	d	file:
+ESP_INTR_FLAG_DEFAULT	captouch.c	/^#define ESP_INTR_FLAG_DEFAULT /;"	d	file:
+Example Configuration	Kconfig.projbuild	/^menu "Example Configuration"$/;"	m
+I2C_MASTER_FREQ_HZ	espan.c	/^#define I2C_MASTER_FREQ_HZ /;"	d	file:
+I2C_MASTER_NUM	captouch.c	/^#define I2C_MASTER_NUM /;"	d	file:
+I2C_MASTER_NUM	espan.c	/^#define I2C_MASTER_NUM /;"	d	file:
+I2C_MASTER_RX_BUF_DISABLE	espan.c	/^#define I2C_MASTER_RX_BUF_DISABLE /;"	d	file:
+I2C_MASTER_SCL	Kconfig.projbuild	/^    config I2C_MASTER_SCL$/;"	c	menu:Example Configuration
+I2C_MASTER_SCL_IO	espan.c	/^#define I2C_MASTER_SCL_IO /;"	d	file:
+I2C_MASTER_SDA	Kconfig.projbuild	/^    config I2C_MASTER_SDA$/;"	c	menu:Example Configuration
+I2C_MASTER_SDA_IO	espan.c	/^#define I2C_MASTER_SDA_IO /;"	d	file:
+I2C_MASTER_TX_BUF_DISABLE	espan.c	/^#define I2C_MASTER_TX_BUF_DISABLE /;"	d	file:
+LEDs	apa102LEDStrip.h	/^    unsigned char *LEDs;$/;"	m	struct:apa102LEDStrip	typeref:typename:unsigned char *
+MIN	audio.c	/^#define MIN(/;"	d	file:
+SAMPLE_RATE	audio.c	/^#define SAMPLE_RATE /;"	d	file:
+TAG	captouch.c	/^static const char *TAG = "captouch";$/;"	v	typeref:typename:const char *	file:
+TAG	espan.c	/^static const char *TAG = "espan";$/;"	v	typeref:typename:const char *	file:
+TIMEOUT_MS	captouch.c	/^#define TIMEOUT_MS /;"	d	file:
+_audio_init	audio.c	/^static void _audio_init(int i2s_num) {$/;"	f	typeref:typename:void	file:
+_bytesPerLED	apa102LEDStrip.h	/^    unsigned char _bytesPerLED;$/;"	m	struct:apa102LEDStrip	typeref:typename:unsigned char
+_counter	apa102LEDStrip.h	/^    short int _counter;$/;"	m	struct:apa102LEDStrip	typeref:typename:short int
+_endFrameLength	apa102LEDStrip.h	/^    short int _endFrameLength;$/;"	m	struct:apa102LEDStrip	typeref:typename:short int
+_frameLength	apa102LEDStrip.h	/^    short int _frameLength;$/;"	m	struct:apa102LEDStrip	typeref:typename:short int
+_globalBrightness	apa102LEDStrip.h	/^    unsigned char _globalBrightness;$/;"	m	struct:apa102LEDStrip	typeref:typename:unsigned char
+_numLEDs	apa102LEDStrip.h	/^    short int _numLEDs;$/;"	m	struct:apa102LEDStrip	typeref:typename:short int
+active_paddles	espan.c	/^static bool active_paddles[10];$/;"	v	typeref:typename:bool[10]	file:
+active_sounds	audio.c	/^static sound_cfg_t active_sounds[11];$/;"	v	typeref:typename:sound_cfg_t[11]	file:
+ad7147_device_config	captouch.c	/^struct ad7147_device_config {$/;"	s	file:
+ad7147_stage_config	captouch.c	/^struct ad7147_stage_config {$/;"	s	file:
+ad714x_chip	captouch.c	/^struct ad714x_chip {$/;"	s	file:
+ad714x_default_config	captouch.c	/^static struct ad7147_stage_config ad714x_default_config(void)$/;"	f	typeref:struct:ad7147_stage_config	file:
+ad714x_i2c_read	captouch.c	/^static esp_err_t ad714x_i2c_read(const struct ad714x_chip *chip, const uint16_t reg, uint16_t *d/;"	f	typeref:typename:esp_err_t	file:
+ad714x_i2c_write	captouch.c	/^static esp_err_t ad714x_i2c_write(const struct ad714x_chip *chip, const uint16_t reg, const uint/;"	f	typeref:typename:esp_err_t	file:
+ad714x_set_device_config	captouch.c	/^static void ad714x_set_device_config(const struct ad714x_chip *chip, const struct ad7147_device_/;"	f	typeref:typename:void	file:
+ad714x_set_stage_config	captouch.c	/^static void ad714x_set_stage_config(const struct ad714x_chip *chip, const uint8_t stage, const s/;"	f	typeref:typename:void	file:
+addr	captouch.c	/^    uint8_t addr;$/;"	m	struct:ad714x_chip	typeref:typename:uint8_t	file:
+afe_offsets	captouch.c	/^    int afe_offsets[13];$/;"	m	struct:ad714x_chip	typeref:typename:int[13]	file:
+apa102LEDStrip	apa102LEDStrip.h	/^struct apa102LEDStrip$/;"	s
+app_main	espan.c	/^void app_main(void)$/;"	f	typeref:typename:void
+audio_init	audio.c	/^void audio_init() { _audio_init(0); }$/;"	f	typeref:typename:void
+audio_player_task	audio.c	/^static void audio_player_task(void* arg) {$/;"	f	typeref:typename:void	file:
+avg_fp_skip	captouch.c	/^    unsigned int avg_fp_skip:2;$/;"	m	struct:ad7147_device_config	typeref:typename:unsigned int:2	file:
+avg_lp_skip	captouch.c	/^    unsigned int avg_lp_skip:2;$/;"	m	struct:ad7147_device_config	typeref:typename:unsigned int:2	file:
+bank2	captouch.c	/^static const uint16_t bank2 = 0x80;$/;"	v	typeref:typename:const uint16_t	file:
+black	espan.c	/^uint8_t black[] = {0,0,0};$/;"	v	typeref:typename:uint8_t[]
+blue	espan.c	/^uint8_t blue[] = {0,0,32};$/;"	v	typeref:typename:uint8_t[]
+bot_map	espan.c	/^uint8_t bot_map[] = {0, 0, 1, 1, 2, 2, 3, 3, 4, 4};$/;"	v	typeref:typename:uint8_t[]
+buffer	audio.c	/^    const int16_t*  buffer;$/;"	m	struct:sound_cfg	typeref:typename:const int16_t *	file:
+buscfg	espan.c	/^spi_bus_config_t buscfg;$/;"	v	typeref:typename:spi_bus_config_t
+bytesPerPixel	espan.c	/^#define bytesPerPixel /;"	d	file:
+captouch_init	captouch.c	/^void captouch_init(void)$/;"	f	typeref:typename:void
+captouch_init_chip	captouch.c	/^static void captouch_init_chip(const struct ad714x_chip* chip)$/;"	f	typeref:typename:void	file:
+captouch_print_debug_info	captouch.c	/^void captouch_print_debug_info(void)$/;"	f	typeref:typename:void
+captouch_print_debug_info_chip	captouch.c	/^static void captouch_print_debug_info_chip(const struct ad714x_chip* chip)$/;"	f	typeref:typename:void	file:
+cdc_bias	captouch.c	/^    unsigned int cdc_bias:2;$/;"	m	struct:ad7147_device_config	typeref:typename:unsigned int:2	file:
+chip_bot	captouch.c	/^static const struct ad714x_chip chip_bot = {.addr = AD7147_BASE_ADDR, .gpio = 3, .afe_offsets = /;"	v	typeref:typename:const struct ad714x_chip	file:
+chip_top	captouch.c	/^static const struct ad714x_chip chip_top = {.addr = AD7147_BASE_ADDR + 1, .gpio = 48, .afe_offse/;"	v	typeref:typename:const struct ad714x_chip	file:
+cinX_connection_setup	captouch.c	/^    unsigned int cinX_connection_setup[13];$/;"	m	struct:ad7147_stage_config	typeref:typename:unsigned int[13]	file:
+decimation	captouch.c	/^    unsigned int decimation:2;$/;"	m	struct:ad7147_device_config	typeref:typename:unsigned int:2	file:
+devcfg	espan.c	/^spi_device_interface_config_t devcfg;$/;"	v	typeref:typename:spi_device_interface_config_t
+espan_handle_captouch	espan.c	/^void espan_handle_captouch(uint16_t pressed_top, uint16_t pressed_bot)$/;"	f	typeref:typename:void
+ext_source	captouch.c	/^    unsigned int ext_source:1;$/;"	m	struct:ad7147_device_config	typeref:typename:unsigned int:1	file:
+free_buffer	audio.c	/^    bool            free_buffer;$/;"	m	struct:sound_cfg	typeref:typename:bool	file:
+getPixel	apa102LEDStrip.c	/^void getPixel(struct apa102LEDStrip *ledObject, short int pixelIndex, unsigned char *pixelColour/;"	f	typeref:typename:void
+gpio	captouch.c	/^    uint8_t gpio;$/;"	m	struct:ad714x_chip	typeref:typename:uint8_t	file:
+gpio_event_handler	captouch.c	/^static void gpio_event_handler(void* arg)$/;"	f	typeref:typename:void	file:
+gpio_evt_queue	captouch.c	/^static QueueHandle_t gpio_evt_queue = NULL;$/;"	v	typeref:typename:QueueHandle_t	file:
+gpio_isr_handler	captouch.c	/^static void IRAM_ATTR gpio_isr_handler(void* arg)$/;"	f	typeref:typename:void IRAM_ATTR	file:
+i2c_master_init	espan.c	/^static esp_err_t i2c_master_init(void)$/;"	f	typeref:typename:esp_err_t	file:
+initLEDs	apa102LEDStrip.c	/^void initLEDs(struct apa102LEDStrip *ledObject, short int numLEDs, unsigned char bytesPerLED, un/;"	f	typeref:typename:void
+int_pol	captouch.c	/^    unsigned int int_pol:1;$/;"	m	struct:ad7147_device_config	typeref:typename:unsigned int:1	file:
+leds	espan.c	/^struct apa102LEDStrip leds;$/;"	v	typeref:struct:apa102LEDStrip
+lp_conv_delay	captouch.c	/^    unsigned int lp_conv_delay:2;$/;"	m	struct:ad7147_device_config	typeref:typename:unsigned int:2	file:
+maxSPIFrameInBytes	espan.c	/^#define maxSPIFrameInBytes /;"	d	file:
+maxSPIFrequency	espan.c	/^#define maxSPIFrequency /;"	d	file:
+maxValuePerColour	espan.c	/^#define maxValuePerColour /;"	d	file:
+neg_afe_offset	captouch.c	/^    unsigned int neg_afe_offset:6;$/;"	m	struct:ad7147_stage_config	typeref:typename:unsigned int:6	file:
+neg_afe_offset_disable	captouch.c	/^    unsigned int neg_afe_offset_disable:1;$/;"	m	struct:ad7147_stage_config	typeref:typename:unsigned int:1	file:
+neg_afe_offset_swap	captouch.c	/^    unsigned int neg_afe_offset_swap:1;$/;"	m	struct:ad7147_stage_config	typeref:typename:unsigned int:1	file:
+neg_peak_detect	captouch.c	/^    unsigned int neg_peak_detect:3;$/;"	m	struct:ad7147_stage_config	typeref:typename:unsigned int:3	file:
+neg_threshold_sensitivity	captouch.c	/^    unsigned int neg_threshold_sensitivity:4;$/;"	m	struct:ad7147_stage_config	typeref:typename:unsigned int:4	file:
+paddle_leds	espan.c	/^static const uint8_t paddle_leds[][9] = {$/;"	v	typeref:typename:const uint8_t[][9]	file:
+play_bootsound	audio.c	/^void play_bootsound() {$/;"	f	typeref:typename:void
+play_pan	audio.c	/^void play_pan(int pan) {$/;"	f	typeref:typename:void
+pos_afe_offset	captouch.c	/^    unsigned int pos_afe_offset:6;$/;"	m	struct:ad7147_stage_config	typeref:typename:unsigned int:6	file:
+pos_afe_offset_disable	captouch.c	/^    unsigned int pos_afe_offset_disable:1;$/;"	m	struct:ad7147_stage_config	typeref:typename:unsigned int:1	file:
+pos_afe_offset_swap	captouch.c	/^    unsigned int pos_afe_offset_swap:1;$/;"	m	struct:ad7147_stage_config	typeref:typename:unsigned int:1	file:
+pos_peak_detect	captouch.c	/^    unsigned int pos_peak_detect:3;$/;"	m	struct:ad7147_stage_config	typeref:typename:unsigned int:3	file:
+pos_threshold_sensitivity	captouch.c	/^    unsigned int pos_threshold_sensitivity:4;$/;"	m	struct:ad7147_stage_config	typeref:typename:unsigned int:4	file:
+position	audio.c	/^    size_t          position;$/;"	m	struct:sound_cfg	typeref:typename:size_t	file:
+power_mode	captouch.c	/^    unsigned int power_mode:2;$/;"	m	struct:ad7147_device_config	typeref:typename:unsigned int:2	file:
+pressed_bot	captouch.c	/^static uint16_t pressed_top, pressed_bot;$/;"	v	typeref:typename:uint16_t	file:
+pressed_top	captouch.c	/^static uint16_t pressed_top, pressed_bot;$/;"	v	typeref:typename:uint16_t	file:
+red	espan.c	/^uint8_t red[] = {32,0,0};$/;"	v	typeref:typename:uint8_t[]
+renderLEDs	espan.c	/^void renderLEDs()$/;"	f	typeref:typename:void
+ret	espan.c	/^esp_err_t ret;$/;"	v	typeref:typename:esp_err_t
+se_connection_setup	captouch.c	/^    unsigned int se_connection_setup:2;$/;"	m	struct:ad7147_stage_config	typeref:typename:unsigned int:2	file:
+sequence_stage_num	captouch.c	/^    unsigned int sequence_stage_num:4;$/;"	m	struct:ad7147_device_config	typeref:typename:unsigned int:4	file:
+setPixel	apa102LEDStrip.c	/^void setPixel(struct apa102LEDStrip *ledObject, short int pixelIndex, unsigned char *pixelColour/;"	f	typeref:typename:void
+setupSPI	espan.c	/^int setupSPI()$/;"	f	typeref:typename:int
+size	audio.c	/^    size_t          size;$/;"	m	struct:sound_cfg	typeref:typename:size_t	file:
+slot	audio.c	/^    size_t          slot;$/;"	m	struct:sound_cfg	typeref:typename:size_t	file:
+sound_active	audio.c	/^static bool sound_active(void)$/;"	f	typeref:typename:bool	file:
+sound_cfg	audio.c	/^typedef struct sound_cfg {$/;"	s	file:
+sound_cfg_t	audio.c	/^} sound_cfg_t;$/;"	t	typeref:struct:sound_cfg	file:
+sound_queue	audio.c	/^static QueueHandle_t sound_queue = NULL;$/;"	v	typeref:typename:QueueHandle_t	file:
+spiTransObject	espan.c	/^spi_transaction_t spiTransObject;$/;"	v	typeref:typename:spi_transaction_t
+spi_led	espan.c	/^spi_device_handle_t spi_led;$/;"	v	typeref:typename:spi_device_handle_t
+stage0_cal_en	captouch.c	/^    unsigned int stage0_cal_en:1;$/;"	m	struct:ad7147_device_config	typeref:typename:unsigned int:1	file:
+stage0_high_int_enable	captouch.c	/^    unsigned int stage0_high_int_enable:1;$/;"	m	struct:ad7147_device_config	typeref:typename:unsigned int:1	file:
+stage10_cal_en	captouch.c	/^    unsigned int stage10_cal_en:1;$/;"	m	struct:ad7147_device_config	typeref:typename:unsigned int:1	file:
+stage10_high_int_enable	captouch.c	/^    unsigned int stage10_high_int_enable:1;$/;"	m	struct:ad7147_device_config	typeref:typename:unsigned int:1	file:
+stage11_cal_en	captouch.c	/^    unsigned int stage11_cal_en:1;$/;"	m	struct:ad7147_device_config	typeref:typename:unsigned int:1	file:
+stage11_high_int_enable	captouch.c	/^    unsigned int stage11_high_int_enable:1;$/;"	m	struct:ad7147_device_config	typeref:typename:unsigned int:1	file:
+stage1_cal_en	captouch.c	/^    unsigned int stage1_cal_en:1;$/;"	m	struct:ad7147_device_config	typeref:typename:unsigned int:1	file:
+stage1_high_int_enable	captouch.c	/^    unsigned int stage1_high_int_enable:1;$/;"	m	struct:ad7147_device_config	typeref:typename:unsigned int:1	file:
+stage2_cal_en	captouch.c	/^    unsigned int stage2_cal_en:1;$/;"	m	struct:ad7147_device_config	typeref:typename:unsigned int:1	file:
+stage2_high_int_enable	captouch.c	/^    unsigned int stage2_high_int_enable:1;$/;"	m	struct:ad7147_device_config	typeref:typename:unsigned int:1	file:
+stage3_cal_en	captouch.c	/^    unsigned int stage3_cal_en:1;$/;"	m	struct:ad7147_device_config	typeref:typename:unsigned int:1	file:
+stage3_high_int_enable	captouch.c	/^    unsigned int stage3_high_int_enable:1;$/;"	m	struct:ad7147_device_config	typeref:typename:unsigned int:1	file:
+stage4_cal_en	captouch.c	/^    unsigned int stage4_cal_en:1;$/;"	m	struct:ad7147_device_config	typeref:typename:unsigned int:1	file:
+stage4_high_int_enable	captouch.c	/^    unsigned int stage4_high_int_enable:1;$/;"	m	struct:ad7147_device_config	typeref:typename:unsigned int:1	file:
+stage5_cal_en	captouch.c	/^    unsigned int stage5_cal_en:1;$/;"	m	struct:ad7147_device_config	typeref:typename:unsigned int:1	file:
+stage5_high_int_enable	captouch.c	/^    unsigned int stage5_high_int_enable:1;$/;"	m	struct:ad7147_device_config	typeref:typename:unsigned int:1	file:
+stage6_cal_en	captouch.c	/^    unsigned int stage6_cal_en:1;$/;"	m	struct:ad7147_device_config	typeref:typename:unsigned int:1	file:
+stage6_high_int_enable	captouch.c	/^    unsigned int stage6_high_int_enable:1;$/;"	m	struct:ad7147_device_config	typeref:typename:unsigned int:1	file:
+stage7_cal_en	captouch.c	/^    unsigned int stage7_cal_en:1;$/;"	m	struct:ad7147_device_config	typeref:typename:unsigned int:1	file:
+stage7_high_int_enable	captouch.c	/^    unsigned int stage7_high_int_enable:1;$/;"	m	struct:ad7147_device_config	typeref:typename:unsigned int:1	file:
+stage8_cal_en	captouch.c	/^    unsigned int stage8_cal_en:1;$/;"	m	struct:ad7147_device_config	typeref:typename:unsigned int:1	file:
+stage8_high_int_enable	captouch.c	/^    unsigned int stage8_high_int_enable:1;$/;"	m	struct:ad7147_device_config	typeref:typename:unsigned int:1	file:
+stage9_cal_en	captouch.c	/^    unsigned int stage9_cal_en:1;$/;"	m	struct:ad7147_device_config	typeref:typename:unsigned int:1	file:
+stage9_high_int_enable	captouch.c	/^    unsigned int stage9_high_int_enable:1;$/;"	m	struct:ad7147_device_config	typeref:typename:unsigned int:1	file:
+stages	captouch.c	/^    int stages;$/;"	m	struct:ad714x_chip	typeref:typename:int	file:
+sw_reset	captouch.c	/^    unsigned int sw_reset:1;$/;"	m	struct:ad7147_device_config	typeref:typename:unsigned int:1	file:
+top_map	espan.c	/^uint8_t top_map[] = {1, 1, 1, 0, 0, 4, 4, 4, 3, 3, 2, 2};$/;"	v	typeref:typename:uint8_t[]
+totalPixels	espan.c	/^#define totalPixels /;"	d	file:
diff --git a/ports/esp32/boards/GENERIC_S3_BADGE/board.json b/ports/esp32/boards/GENERIC_S3_BADGE/board.json
new file mode 100644
index 0000000000..0db9b32503
--- /dev/null
+++ b/ports/esp32/boards/GENERIC_S3_BADGE/board.json
@@ -0,0 +1,18 @@
+{
+    "deploy": [
+        "../deploy_s3.md"
+    ],
+    "docs": "",
+    "features": [
+        "BLE",
+        "WiFi"
+    ],
+    "images": [
+        "generic_s3.jpg"
+    ],
+    "mcu": "esp32s3",
+    "product": "ESP32-S3",
+    "thumbnail": "",
+    "url": "https://www.espressif.com/en/products/modules",
+    "vendor": "Espressif"
+}
diff --git a/ports/esp32/boards/GENERIC_S3_BADGE/mpconfigboard.cmake b/ports/esp32/boards/GENERIC_S3_BADGE/mpconfigboard.cmake
new file mode 100644
index 0000000000..cb32e3ceea
--- /dev/null
+++ b/ports/esp32/boards/GENERIC_S3_BADGE/mpconfigboard.cmake
@@ -0,0 +1,9 @@
+set(IDF_TARGET esp32s3)
+
+set(SDKCONFIG_DEFAULTS
+    boards/sdkconfig.base
+    boards/sdkconfig.badge23
+    boards/sdkconfig.usb
+    boards/sdkconfig.ble
+    boards/GENERIC_S3/sdkconfig.board
+)
diff --git a/ports/esp32/boards/GENERIC_S3_BADGE/mpconfigboard.h b/ports/esp32/boards/GENERIC_S3_BADGE/mpconfigboard.h
new file mode 100644
index 0000000000..b8f5fb2560
--- /dev/null
+++ b/ports/esp32/boards/GENERIC_S3_BADGE/mpconfigboard.h
@@ -0,0 +1,10 @@
+#define MICROPY_HW_BOARD_NAME               "ESP32S3 module"
+#define MICROPY_HW_MCU_NAME                 "ESP32S3"
+
+#define MICROPY_PY_MACHINE_DAC              (0)
+
+// Enable UART REPL for modules that have an external USB-UART and don't use native USB.
+#define MICROPY_HW_ENABLE_UART_REPL         (1)
+
+#define MICROPY_HW_I2C0_SCL                 (9)
+#define MICROPY_HW_I2C0_SDA                 (8)
diff --git a/ports/esp32/boards/GENERIC_S3_BADGE/sdkconfig.board b/ports/esp32/boards/GENERIC_S3_BADGE/sdkconfig.board
new file mode 100644
index 0000000000..c9726d4232
--- /dev/null
+++ b/ports/esp32/boards/GENERIC_S3_BADGE/sdkconfig.board
@@ -0,0 +1,12 @@
+CONFIG_FLASHMODE_QIO=y
+CONFIG_ESPTOOLPY_FLASHFREQ_80M=y
+CONFIG_ESPTOOLPY_FLASHSIZE_DETECT=y
+CONFIG_ESPTOOLPY_AFTER_NORESET=y
+
+CONFIG_SPIRAM_MEMTEST=
+
+CONFIG_ESPTOOLPY_FLASHSIZE_4MB=
+CONFIG_ESPTOOLPY_FLASHSIZE_8MB=y
+CONFIG_ESPTOOLPY_FLASHSIZE_16MB=
+CONFIG_PARTITION_TABLE_CUSTOM=y
+CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions-8MiB.csv"
diff --git a/ports/esp32/boards/sdkconfig.badge23 b/ports/esp32/boards/sdkconfig.badge23
new file mode 100644
index 0000000000..7d93f2b3ee
--- /dev/null
+++ b/ports/esp32/boards/sdkconfig.badge23
@@ -0,0 +1,21 @@
+CONFIG_GC9A01_Width=240
+CONFIG_GC9A01_Height=240
+# CONFIG_USE_SPI1_HOST is not set
+# CONFIG_USE_SPI2_HOST is not set
+CONFIG_USE_SPI3_HOST=y 
+# CONFIG_USE_SPI4_HOST is not set
+CONFIG_GC9A01_SPI_HOST=2
+CONFIG_GC9A01_PIN_NUM_SCK=39
+CONFIG_GC9A01_PIN_NUM_MOSI=41
+CONFIG_GC9A01_PIN_NUM_CS=40
+CONFIG_GC9A01_PIN_NUM_DC=42
+CONFIG_GC9A01_SPI_SCK_FREQ_M=80
+# CONFIG_GC9A01_CONTROL_BACK_LIGHT_USED is not set
+CONFIG_GC9A01_RESET_USED=y
+CONFIG_GC9A01_PIN_NUM_RST=38
+CONFIG_GC9A01_BUFFER_MODE=y
+# CONFIG_GC9A01_BUFFER_SCREEN_FAST_MODE is not set
+# end of GC9A01 LCD Config
+
+#
+
diff --git a/ports/esp32/main.c b/ports/esp32/main.c
index e2a803fcb6..490a931fd4 100644
--- a/ports/esp32/main.c
+++ b/ports/esp32/main.c
@@ -62,6 +62,7 @@
 #include "modmachine.h"
 #include "modnetwork.h"
 #include "mpthreadport.h"
+#include "badge23/espan.h"
 
 #if MICROPY_BLUETOOTH_NIMBLE
 #include "extmod/modbluetooth.h"
@@ -233,12 +234,16 @@ void boardctrl_startup(void) {
 }
 
 void app_main(void) {
+    printf("die my darling");
     // Hook for a board to run code at start up.
     // This defaults to initialising NVS.
     MICROPY_BOARD_STARTUP();
 
     // Create and transfer control to the MicroPython task.
-    xTaskCreatePinnedToCore(mp_task, "mp_task", MP_TASK_STACK_SIZE / sizeof(StackType_t), NULL, MP_TASK_PRIORITY, &mp_main_task_handle, MP_TASK_COREID);
+    //xTaskCreatePinnedToCore(mp_task, "mp_task", MP_TASK_STACK_SIZE / sizeof(StackType_t), NULL, MP_TASK_PRIORITY, &mp_main_task_handle, MP_TASK_COREID);
+    xTaskCreate(mp_task, "mp_task", (MP_TASK_STACK_SIZE / sizeof(StackType_t)) / 2, NULL, configMAX_PRIORITIES-3, &mp_main_task_handle);
+    printf("don't utter a single word");
+    old_app_main(); // ./badge23/ entry point
 }
 
 void nlr_jump_fail(void *val) {
diff --git a/ports/esp32/main/CMakeLists.txt b/ports/esp32/main/CMakeLists.txt
index f8acfa9052..77ff07204c 100644
--- a/ports/esp32/main/CMakeLists.txt
+++ b/ports/esp32/main/CMakeLists.txt
@@ -19,6 +19,18 @@ set(MICROPY_QSTRDEFS_PORT
     ${PROJECT_DIR}/qstrdefsport.h
 )
 
+set(BADGE23_LIB
+    ${PROJECT_DIR}/badge23/apa102LEDStrip.c
+    ${PROJECT_DIR}/badge23/audio.c
+    ${PROJECT_DIR}/badge23/captouch.c
+    ${PROJECT_DIR}/badge23/display.c
+    ${PROJECT_DIR}/badge23/espan.c
+    ${PROJECT_DIR}/badge23/leds.c
+    ${PROJECT_DIR}/badge23/scope.c
+    ${PROJECT_DIR}/badge23/synth.c
+    ${PROJECT_DIR}/badge23/components/gc9a01/gc9a01.c
+)
+
 set(MICROPY_SOURCE_SHARED
     ${MICROPY_DIR}/shared/readline/readline.c
     ${MICROPY_DIR}/shared/netutils/netutils.c
@@ -164,6 +176,7 @@ idf_component_register(
         ${MICROPY_SOURCE_DRIVERS}
         ${MICROPY_SOURCE_PORT}
         ${MICROPY_SOURCE_BOARD}
+        ${BADGE23_LIB}
     INCLUDE_DIRS
         ${MICROPY_INC_CORE}
         ${MICROPY_INC_USERMOD}
-- 
GitLab