From 31a2ae6355ee9dc65ddbdd991fe033c872617c6c Mon Sep 17 00:00:00 2001 From: Lucas Saavedra Vaz <32426024+lucasssvaz@users.noreply.github.com> Date: Thu, 11 Dec 2025 12:16:00 -0300 Subject: [PATCH] fix(tests): Fix periman and add missing diagrams --- cores/esp32/esp32-hal-adc.c | 107 ++++++--- tests/validation/periman/ci.yml | 3 - tests/validation/periman/periman.ino | 225 +++++++++++++++++-- tests/validation/periman/test_periman.py | 52 +++-- tests/validation/sdcard/ci.yml | 3 + tests/validation/sdcard/diagram.esp32h2.json | 38 ++++ tests/validation/sdcard/diagram.esp32p4.json | 53 +++++ 7 files changed, 405 insertions(+), 76 deletions(-) create mode 100644 tests/validation/sdcard/ci.yml create mode 100644 tests/validation/sdcard/diagram.esp32h2.json create mode 100644 tests/validation/sdcard/diagram.esp32p4.json diff --git a/cores/esp32/esp32-hal-adc.c b/cores/esp32/esp32-hal-adc.c index 342ce9aefb0..47e091a693b 100644 --- a/cores/esp32/esp32-hal-adc.c +++ b/cores/esp32/esp32-hal-adc.c @@ -20,6 +20,7 @@ #include "esp_adc/adc_oneshot.h" #include "esp_adc/adc_continuous.h" #include "esp_adc/adc_cali_scheme.h" +#include "esp_heap_caps.h" #if CONFIG_IDF_TARGET_ESP32P4 && CONFIG_ESP32P4_REV_MIN_FULL >= 300 // NOTE: These weak definitions allow successful linkage if the real efuse calibration functions are missing. @@ -403,43 +404,39 @@ adc_continuous_result_t *adc_result = NULL; static bool adcContinuousDetachBus(void *adc_unit_number) { adc_unit_t adc_unit = (adc_unit_t)adc_unit_number - 1; + // Guard against double-cleanup: check if already cleaned up if (adc_handle[adc_unit].adc_continuous_handle == NULL) { return true; - } else { - esp_err_t err = adc_continuous_deinit(adc_handle[adc_unit].adc_continuous_handle); + } + + // Clean up ADC driver + esp_err_t err = adc_continuous_deinit(adc_handle[adc_unit].adc_continuous_handle); + if (err != ESP_OK) { + return false; + } + adc_handle[adc_unit].adc_continuous_handle = NULL; + + // Clean up calibration handle if exists + if (adc_handle[adc_unit].adc_cali_handle != NULL) { +#if ADC_CALI_SCHEME_CURVE_FITTING_SUPPORTED + err = adc_cali_delete_scheme_curve_fitting(adc_handle[adc_unit].adc_cali_handle); if (err != ESP_OK) { return false; } - adc_handle[adc_unit].adc_continuous_handle = NULL; - if (adc_handle[adc_unit].adc_cali_handle != NULL) { -#if ADC_CALI_SCHEME_CURVE_FITTING_SUPPORTED - err = adc_cali_delete_scheme_curve_fitting(adc_handle[adc_unit].adc_cali_handle); - if (err != ESP_OK) { - return false; - } #elif ADC_CALI_SCHEME_LINE_FITTING_SUPPORTED - err = adc_cali_delete_scheme_line_fitting(adc_handle[adc_unit].adc_cali_handle); - if (err != ESP_OK) { - return false; - } -#else - log_e("ADC Calibration scheme is not supported!"); + err = adc_cali_delete_scheme_line_fitting(adc_handle[adc_unit].adc_cali_handle); + if (err != ESP_OK) { return false; -#endif } +#else + log_e("ADC Calibration scheme is not supported!"); + return false; +#endif adc_handle[adc_unit].adc_cali_handle = NULL; - - //set all used pins to INIT state - for (uint8_t channel = 0; channel < SOC_ADC_CHANNEL_NUM(adc_unit); channel++) { - int io_pin; - adc_oneshot_channel_to_io(adc_unit, channel, &io_pin); - if (perimanGetPinBusType(io_pin) == ESP32_BUS_TYPE_ADC_CONT) { - if (!perimanClearPinBus(io_pin)) { - return false; - } - } - } } + + // Don't call perimanClearPinBus() here - the peripheral manager already handles it + // This callback is only responsible for cleaning up the ADC driver itself return true; } @@ -549,6 +546,14 @@ bool analogContinuous(const uint8_t pins[], size_t pins_count, uint32_t conversi } #endif +#if CONFIG_IDF_TARGET_ESP32P4 + // Align conversion frame size to cache line size (required for DMA on targets with cache) + uint32_t alignment_remainder = adc_handle[adc_unit].conversion_frame_size % CONFIG_CACHE_L1_CACHE_LINE_SIZE; + if (alignment_remainder != 0) { + adc_handle[adc_unit].conversion_frame_size += (CONFIG_CACHE_L1_CACHE_LINE_SIZE - alignment_remainder); + } +#endif + adc_handle[adc_unit].buffer_size = adc_handle[adc_unit].conversion_frame_size * 2; //Conversion frame size buffer cant be bigger than 4092 bytes @@ -626,8 +631,21 @@ bool analogContinuousRead(adc_continuous_result_t **buffer, uint32_t timeout_ms) uint32_t bytes_read = 0; uint32_t read_raw[used_adc_channels]; uint32_t read_count[used_adc_channels]; - uint8_t adc_read[adc_handle[ADC_UNIT_1].conversion_frame_size]; - memset(adc_read, 0xcc, sizeof(adc_read)); + + // Allocate DMA buffer with cache line alignment (required for ESP32-P4 and other targets with cache) + size_t buffer_size = adc_handle[ADC_UNIT_1].conversion_frame_size; +#if CONFIG_IDF_TARGET_ESP32P4 + uint8_t *adc_read = (uint8_t *)heap_caps_aligned_alloc(CONFIG_CACHE_L1_CACHE_LINE_SIZE, buffer_size, MALLOC_CAP_DMA | MALLOC_CAP_INTERNAL); +#else + uint8_t *adc_read = (uint8_t *)heap_caps_malloc(buffer_size, MALLOC_CAP_DMA | MALLOC_CAP_INTERNAL); +#endif + if (adc_read == NULL) { + log_e("Failed to allocate DMA buffer"); + *buffer = NULL; + return false; + } + + memset(adc_read, 0xcc, buffer_size); memset(read_raw, 0, sizeof(read_raw)); memset(read_count, 0, sizeof(read_count)); @@ -638,6 +656,8 @@ bool analogContinuousRead(adc_continuous_result_t **buffer, uint32_t timeout_ms) } else { log_e("Reading data failed with error: %X", err); } + free(adc_read); + adc_read = NULL; *buffer = NULL; return false; } @@ -676,6 +696,8 @@ bool analogContinuousRead(adc_continuous_result_t **buffer, uint32_t timeout_ms) } } + free(adc_read); + adc_read = NULL; *buffer = adc_result; return true; @@ -708,16 +730,29 @@ bool analogContinuousStop() { } bool analogContinuousDeinit() { - if (adc_handle[ADC_UNIT_1].adc_continuous_handle != NULL) { - esp_err_t err = adc_continuous_deinit(adc_handle[ADC_UNIT_1].adc_continuous_handle); - if (err != ESP_OK) { - return false; + if (adc_handle[ADC_UNIT_1].adc_continuous_handle == NULL) { + log_i("ADC Continuous was not initialized"); + return true; + } + + // Clear all used pins from peripheral manager + // This will trigger adcContinuousDetachBus() callback which cleans up the ADC driver + for (uint8_t channel = 0; channel < SOC_ADC_CHANNEL_NUM(ADC_UNIT_1); channel++) { + int io_pin; + adc_oneshot_channel_to_io(ADC_UNIT_1, channel, &io_pin); + if (perimanGetPinBusType(io_pin) == ESP32_BUS_TYPE_ADC_CONT) { + if (!perimanClearPinBus(io_pin)) { + return false; + } } + } + + // Free the result buffer (callback doesn't do this) + if (adc_result != NULL) { free(adc_result); - adc_handle[ADC_UNIT_1].adc_continuous_handle = NULL; - } else { - log_i("ADC Continuous was not initialized"); + adc_result = NULL; } + return true; } diff --git a/tests/validation/periman/ci.yml b/tests/validation/periman/ci.yml index 85d8f908f4e..a5625fc9fa1 100644 --- a/tests/validation/periman/ci.yml +++ b/tests/validation/periman/ci.yml @@ -1,6 +1,3 @@ platforms: qemu: false wokwi: false - -targets: - esp32p4: false diff --git a/tests/validation/periman/periman.ino b/tests/validation/periman/periman.ino index 2b89e3529fd..783b43bd873 100644 --- a/tests/validation/periman/periman.ino +++ b/tests/validation/periman/periman.ino @@ -71,8 +71,6 @@ void setup_test(String test_name, int8_t rx_pin = UART1_RX_DEFAULT, int8_t tx_pi uart1_tx_pin = tx_pin; test_executed = false; - pinMode(uart1_rx_pin, INPUT_PULLUP); - pinMode(uart1_tx_pin, OUTPUT); // Ensure Serial1 is initialized and callback is set (in case it was terminated previously) Serial1.setPins(uart1_rx_pin, uart1_tx_pin); Serial1.begin(115200); @@ -86,20 +84,20 @@ void setup_test(String test_name, int8_t rx_pin = UART1_RX_DEFAULT, int8_t tx_pi void teardown_test(void) { log_v("Tearing down %s test", current_test.c_str()); if (test_executed) { + // Test 1: Peripheral manager auto-detach via pinMode pinMode(uart1_rx_pin, INPUT_PULLUP); pinMode(uart1_tx_pin, OUTPUT); Serial1.print(current_test); Serial1.println(" test: This should not be printed"); Serial1.flush(); - } - // Even if test didn't execute, ensure Serial1 is initialized - // (in case it was terminated by a previous test or setup issue) - Serial1.setPins(uart1_rx_pin, uart1_tx_pin); - Serial1.begin(115200); - Serial1.onReceive(onReceive_cb); - uart_internal_loopback(1, uart1_rx_pin); - delay(100); + // Restore Serial1 via peripheral manager + Serial1.setPins(uart1_rx_pin, uart1_tx_pin); + Serial1.begin(115200); + Serial1.onReceive(onReceive_cb); + uart_internal_loopback(1, uart1_rx_pin); + delay(100); + } Serial1.print(current_test); Serial1.println(" test: This should be printed"); @@ -134,7 +132,35 @@ void sigmadelta_test(void) { Serial.println("SigmaDelta init failed"); } #endif - teardown_test(); + teardown_test(); // Tests auto-detach via pinMode + +#if SOC_SDM_SUPPORTED + // Now test manual deinit path + setup_test("SigmaDelta_deinit"); + test_executed = false; // Skip the pinMode test in teardown + + if (!sigmaDeltaAttach(uart1_rx_pin, 312500)) { + Serial.println("SigmaDelta init failed"); + } + if (!sigmaDeltaAttach(uart1_tx_pin, 312500)) { + Serial.println("SigmaDelta init failed"); + } + + // Manual deinit + sigmaDeltaDetach(uart1_rx_pin); + sigmaDeltaDetach(uart1_tx_pin); + + // Verify Serial1 can be restored after manual deinit + Serial1.setPins(uart1_rx_pin, uart1_tx_pin); + Serial1.begin(115200); + Serial1.onReceive(onReceive_cb); + uart_internal_loopback(1, uart1_rx_pin); + delay(100); + + Serial1.print("SigmaDelta_deinit"); + Serial1.println(" test: This should be printed"); + Serial1.flush(); +#endif } void adc_oneshot_test(void) { @@ -185,7 +211,43 @@ void adc_continuous_test(void) { analogContinuousStop(); #endif - teardown_test(); + teardown_test(); // Tests auto-detach via pinMode + +#if SOC_ADC_SUPPORTED + // Now test manual deinit path + setup_test("ADC_Continuous_deinit", ADC1_DEFAULT, ADC2_DEFAULT); + test_executed = false; // Skip the pinMode test in teardown + adc_coversion_done = false; // Reset flag + + analogContinuousSetWidth(12); + analogContinuousSetAtten(ADC_11db); + analogContinuous(adc_pins, adc_pins_count, 6, 20000, &adcComplete); + analogContinuousStart(); + + while (adc_coversion_done == false) { + delay(1); + } + + if (!analogContinuousRead(&result, 0)) { + Serial.println("ADC continuous read failed"); + } + + analogContinuousStop(); + + // Manual deinit + analogContinuousDeinit(); + + // Verify Serial1 can be restored after manual deinit + Serial1.setPins(uart1_rx_pin, uart1_tx_pin); + Serial1.begin(115200); + Serial1.onReceive(onReceive_cb); + uart_internal_loopback(1, uart1_rx_pin); + delay(100); + + Serial1.print("ADC_Continuous_deinit"); + Serial1.println(" test: This should be printed"); + Serial1.flush(); +#endif } void dac_test(void) { @@ -211,7 +273,35 @@ void ledc_test(void) { Serial.println("LEDC init failed"); } #endif - teardown_test(); + teardown_test(); // Tests auto-detach via pinMode + +#if SOC_LEDC_SUPPORTED + // Now test manual deinit path + setup_test("LEDC_deinit"); + test_executed = false; // Skip the pinMode test in teardown + + if (!ledcAttach(uart1_rx_pin, 5000, 12)) { + Serial.println("LEDC init failed"); + } + if (!ledcAttach(uart1_tx_pin, 5000, 12)) { + Serial.println("LEDC init failed"); + } + + // Manual deinit + ledcDetach(uart1_rx_pin); + ledcDetach(uart1_tx_pin); + + // Verify Serial1 can be restored after manual deinit + Serial1.setPins(uart1_rx_pin, uart1_tx_pin); + Serial1.begin(115200); + Serial1.onReceive(onReceive_cb); + uart_internal_loopback(1, uart1_rx_pin); + delay(100); + + Serial1.print("LEDC_deinit"); + Serial1.println(" test: This should be printed"); + Serial1.flush(); +#endif } void rmt_test(void) { @@ -225,7 +315,35 @@ void rmt_test(void) { Serial.println("RMT init failed"); } #endif - teardown_test(); + teardown_test(); // Tests auto-detach via pinMode + +#if SOC_RMT_SUPPORTED + // Now test manual deinit path + setup_test("RMT_deinit"); + test_executed = false; // Skip the pinMode test in teardown + + if (!rmtInit(uart1_rx_pin, RMT_TX_MODE, RMT_MEM_NUM_BLOCKS_1, 10000000)) { + Serial.println("RMT init failed"); + } + if (!rmtInit(uart1_tx_pin, RMT_RX_MODE, RMT_MEM_NUM_BLOCKS_1, 10000000)) { + Serial.println("RMT init failed"); + } + + // Manual deinit + rmtDeinit(uart1_rx_pin); + rmtDeinit(uart1_tx_pin); + + // Verify Serial1 can be restored after manual deinit + Serial1.setPins(uart1_rx_pin, uart1_tx_pin); + Serial1.begin(115200); + Serial1.onReceive(onReceive_cb); + uart_internal_loopback(1, uart1_rx_pin); + delay(100); + + Serial1.print("RMT_deinit"); + Serial1.println(" test: This should be printed"); + Serial1.flush(); +#endif } void i2s_test(void) { @@ -240,7 +358,34 @@ void i2s_test(void) { Serial.println("I2S init failed"); } #endif - teardown_test(); + teardown_test(); // Tests auto-detach via pinMode + +#if SOC_I2S_SUPPORTED + // Now test manual deinit path + setup_test("I2S_deinit"); + test_executed = false; // Skip the pinMode test in teardown + + I2SClass i2s2; + i2s2.setPins(uart1_rx_pin, uart1_tx_pin, -1); + i2s2.setTimeout(1000); + if (!i2s2.begin(I2S_MODE_STD, 16000, I2S_DATA_BIT_WIDTH_16BIT, I2S_SLOT_MODE_STEREO)) { + Serial.println("I2S init failed"); + } + + // Manual deinit + i2s2.end(); + + // Verify Serial1 can be restored after manual deinit + Serial1.setPins(uart1_rx_pin, uart1_tx_pin); + Serial1.begin(115200); + Serial1.onReceive(onReceive_cb); + uart_internal_loopback(1, uart1_rx_pin); + delay(100); + + Serial1.print("I2S_deinit"); + Serial1.println(" test: This should be printed"); + Serial1.flush(); +#endif } void i2c_test(void) { @@ -251,7 +396,31 @@ void i2c_test(void) { Serial.println("I2C init failed"); } #endif - teardown_test(); + teardown_test(); // Tests auto-detach via pinMode + +#if SOC_I2C_SUPPORTED + // Now test manual deinit path + setup_test("I2C_deinit"); + test_executed = false; // Skip the pinMode test in teardown + + if (!Wire.begin(uart1_rx_pin, uart1_tx_pin)) { + Serial.println("I2C init failed"); + } + + // Manual deinit + Wire.end(); + + // Verify Serial1 can be restored after manual deinit + Serial1.setPins(uart1_rx_pin, uart1_tx_pin); + Serial1.begin(115200); + Serial1.onReceive(onReceive_cb); + uart_internal_loopback(1, uart1_rx_pin); + delay(100); + + Serial1.print("I2C_deinit"); + Serial1.println(" test: This should be printed"); + Serial1.flush(); +#endif } void spi_test(void) { @@ -260,7 +429,29 @@ void spi_test(void) { test_executed = true; SPI.begin(uart1_rx_pin, uart1_tx_pin, -1, -1); #endif - teardown_test(); + teardown_test(); // Tests auto-detach via pinMode + +#if SOC_GPSPI_SUPPORTED + // Now test manual deinit path + setup_test("SPI_deinit"); + test_executed = false; // Skip the pinMode test in teardown + + SPI.begin(uart1_rx_pin, uart1_tx_pin, -1, -1); + + // Manual deinit + SPI.end(); + + // Verify Serial1 can be restored after manual deinit + Serial1.setPins(uart1_rx_pin, uart1_tx_pin); + Serial1.begin(115200); + Serial1.onReceive(onReceive_cb); + uart_internal_loopback(1, uart1_rx_pin); + delay(100); + + Serial1.print("SPI_deinit"); + Serial1.println(" test: This should be printed"); + Serial1.flush(); +#endif } void touch_test(void) { diff --git a/tests/validation/periman/test_periman.py b/tests/validation/periman/test_periman.py index 2728abcef80..56be51406ca 100644 --- a/tests/validation/periman/test_periman.py +++ b/tests/validation/periman/test_periman.py @@ -3,21 +3,31 @@ def test_periman(dut): LOGGER = logging.getLogger(__name__) - peripherals = [ - "GPIO", - "SigmaDelta", - "LEDC", - "RMT", - "I2S", - "I2C", - "SPI", - "ADC_Oneshot", - "ADC_Continuous", - "DAC", - "Touch", + + # Define peripherals and whether they have a manual deinit test + # Format: (name, has_deinit_test) + PERIPHERALS = [ + ("GPIO", False), + ("SigmaDelta", True), + ("LEDC", True), + ("RMT", True), + ("I2S", True), + ("I2C", True), + ("SPI", True), + ("ADC_Oneshot", False), + ("ADC_Continuous", True), + ("DAC", False), + ("Touch", False), ] - pattern = rb"(?:\b\w+\b test: This should(?: not)? be printed|Peripheral Manager test done)" + # Build expected test names + pending_tests = set() + for name, has_deinit in PERIPHERALS: + pending_tests.add(name) + if has_deinit: + pending_tests.add(f"{name}_deinit") + + pattern = rb"(?:\b[\w_]+\b test: This should(?: not)? be printed|Peripheral Manager test done)" while True: try: @@ -26,17 +36,19 @@ def test_periman(dut): assert False, "Could not detect end of test" console_output = res.group(0).decode("utf-8") - peripheral = console_output.split()[0] + test_name = console_output.split()[0] if "Peripheral Manager test done" in console_output: break - if peripheral in peripherals: + if test_name in pending_tests: if "not" in console_output: - assert False, f"Output printed when it should not after peripheral {peripheral}" - LOGGER.info(f"Correct output after peripheral: {peripheral}") - peripherals.remove(peripheral) + assert False, f"Output printed when it should not for test: {test_name}" + else: + test_type = "manual deinit" if test_name.endswith("_deinit") else "auto-detach" + LOGGER.info(f"✓ {test_type} works for: {test_name}") + pending_tests.remove(test_name) else: - assert False, f"Unknown peripheral: {peripheral}" + assert False, f"Unknown test: {test_name}" - assert peripherals == [], f"Missing output after peripherals: {peripherals}" + assert not pending_tests, f"Missing tests: {sorted(pending_tests)}" diff --git a/tests/validation/sdcard/ci.yml b/tests/validation/sdcard/ci.yml new file mode 100644 index 00000000000..3f53d32a04e --- /dev/null +++ b/tests/validation/sdcard/ci.yml @@ -0,0 +1,3 @@ +platforms: + hardware: false + qemu: false diff --git a/tests/validation/sdcard/diagram.esp32h2.json b/tests/validation/sdcard/diagram.esp32h2.json new file mode 100644 index 00000000000..28d04665b60 --- /dev/null +++ b/tests/validation/sdcard/diagram.esp32h2.json @@ -0,0 +1,38 @@ +{ + "version": 1, + "author": "lucasssvaz", + "editor": "wokwi", + "parts": [ + { + "type": "board-esp32-h2-devkitm-1", + "id": "esp32", + "top": -55.37, + "left": -177.9, + "attrs": {} + }, + { + "type": "wokwi-microsd-card", + "id": "sd1", + "top": -151.03, + "left": -60.53, + "rotate": 90, + "attrs": {} + } + ], + "connections": [ + [ "esp32:RX", "$serialMonitor:TX", "", [] ], + [ "esp32:TX", "$serialMonitor:RX", "", [] ], + [ "sd1:DI", "esp32:6", "green", [ "v0.09", "h-142.56" ] ], + [ "sd1:CS", "esp32:7", "green", [ "h-57.66", "v-9.3" ] ], + [ "sd1:GND", "esp32:GND.7", "black", [ "h0" ] ], + [ "esp32:10", "sd1:SCK", "green", [ "h65.38", "v-74.57" ] ], + [ "sd1:GND", "esp32:GND.6", "black", [ "h-0.11", "v48" ] ], + [ "esp32:25", "sd1:DI", "green", [ "h46.18", "v-93.77" ] ], + [ "sd1:DO", "esp32:11", "green", [ "h-0.11", "v84.17" ] ], + [ "sd1:VCC", "esp32:3V3", "red", [ "v9.6", "h-0.14" ] ], + [ "esp32:0", "sd1:CS", "green", [ "h-19.2", "v-48", "h144" ] ] + ], + "dependencies": {} +} + + diff --git a/tests/validation/sdcard/diagram.esp32p4.json b/tests/validation/sdcard/diagram.esp32p4.json new file mode 100644 index 00000000000..f4328b6cd8b --- /dev/null +++ b/tests/validation/sdcard/diagram.esp32p4.json @@ -0,0 +1,53 @@ +{ + "version": 1, + "author": "lucasssvaz", + "editor": "wokwi", + "parts": [ + { + "type": "board-esp32-p4-function-ev", + "id": "esp", + "top": 164.08, + "left": -76.03, + "attrs": {} + }, + { + "type": "wokwi-microsd-card", + "id": "sd1", + "top": -26.23, + "left": 112.27, + "rotate": 90, + "attrs": {} + }, + { + "type": "wokwi-microsd-card", + "id": "sd2", + "top": -26.23, + "left": 237.07, + "rotate": 90, + "attrs": {} + } + ], + "connections": [ + [ "esp:37", "$serialMonitor:RX", "", [] ], + [ "esp:38", "$serialMonitor:TX", "", [] ], + [ "sd1:CS", "esp:10", "green", [ "h57.6", "v-38.16" ] ], + [ "sd1:DI", "esp:11", "green", [ "h48", "v-19.11" ] ], + [ "sd1:SCK", "esp:12", "green", [ "h38.4", "v9.77" ] ], + [ "sd1:DO", "esp:13", "green", [ "h67.2", "v38.69" ] ], + [ "sd1:VCC", "esp:3V3.1", "red", [ "v19.2", "h-105.74", "v115.2" ] ], + [ "sd2:VCC", "esp:3V3.1", "red", [ "v19.2", "h-230.54", "v115.2" ] ], + [ "esp:GND.1", "sd1:GND", "black", [ "v-9.6", "h-76.8", "v-86.4", "h115.2" ] ], + [ "esp:GND.1", "sd2:GND", "black", [ "v-9.6", "h-76.8", "v-86.4", "h240" ] ], + [ "esp:26", "sd2:CS", "green", [ "v0" ] ], + [ "esp:33", "sd2:DO", "green", [ "v-19.2", "h57.71" ] ], + [ "esp:36", "sd2:SCK", "green", [ "v-19.2", "h67.19" ] ], + [ "esp:32", "sd2:DI", "green", [ "v-28.8", "h38.31" ] ], + [ "esp:1", "sd1:SCK", "green", [ "v-19.2", "h-48" ] ], + [ "esp:2", "sd1:DO", "green", [ "v9.6", "h-124.8", "v-96", "h96.11" ] ], + [ "esp:3", "sd1:DI", "green", [ "v19.2", "h-124.8", "v-115.2", "h67.2", "v9.6" ] ], + [ "esp:8", "sd1:CS", "green", [ "v28.8", "h-67.2", "v-153.6", "h67.2" ] ] + ], + "dependencies": {} +} + +