From 2a818d64b19a5550aea5cb37c193fd2f4218c8d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20K=C3=A4berich?= Date: Tue, 13 May 2025 18:12:12 +0200 Subject: [PATCH 01/10] switch FW build to Ubuntu 24.04 --- .github/workflows/Build.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/Build.yml b/.github/workflows/Build.yml index 02fa981..baaeae2 100644 --- a/.github/workflows/Build.yml +++ b/.github/workflows/Build.yml @@ -4,6 +4,7 @@ on: push: branches: - main + - build_FW_test pull_request: branches: - main @@ -199,7 +200,7 @@ jobs: Embedded_Firmware: - runs-on: ubuntu-22.04 + runs-on: ubuntu-24.04 steps: - uses: actions/checkout@v4 From 597262ed89af31aa598b42d0fc0bb7fcfb111ef6 Mon Sep 17 00:00:00 2001 From: Joshua Wise Date: Thu, 29 May 2025 01:36:49 -0700 Subject: [PATCH 02/10] Add support for Siglent eCal emulation. This patch allows a Siglent SVA1032X to calibrate using a LibreCAL. It adds support in the firmware to boot into a Siglent-compatible mode (removes USB MSC, to avoid the instrument's UI trying to write to the disk; adds a very limited USB TMC that knows how to speak the Siglent calibration commands) when Siglent calibration coefficients are present, and the unit has the FUNCTION key held while powering on. We also add a `convert_siglent.py` script, which converts the calibration data stored in the LibreCAL's native Touchstone format to the reverse-engineered header and ZIP+CSV format that a Siglent VNA requires. Some fields in the Siglent header are not fully understood, but they don't seem to make a difference at least on SVA1032X. The calibration appears to provide good results tested against other calibration standards I have lying around, and so the S-parameter conversion for ports 1 and 2 at least are probably correct. Unimplemented is an "ATT" mode, used for the VNA's Confidence Check function; I suspect this may be an attenuator, but without a real Siglent eCal, I can't know for sure, and more to the point, the LibreCAL doesn't have one other than the Through port, so we do not implement it. Signed-off-by: Joshua Wise --- Software/LibreCAL/CMakeLists.txt | 1 + Software/LibreCAL/src/USB/tmc.cpp | 260 ++++++++++++++++++++ Software/LibreCAL/src/USB/tusb_config.h | 3 + Software/LibreCAL/src/USB/usb.h | 1 + Software/LibreCAL/src/USB/usb_descriptors.c | 71 +++++- Software/LibreCAL/src/UserInterface.cpp | 4 + Software/LibreCAL/src/UserInterface.hpp | 1 + Software/LibreCAL/src/main.cpp | 10 + Software/Scripts/convert_siglent.py | 131 ++++++++++ 9 files changed, 477 insertions(+), 5 deletions(-) create mode 100644 Software/LibreCAL/src/USB/tmc.cpp create mode 100755 Software/Scripts/convert_siglent.py diff --git a/Software/LibreCAL/CMakeLists.txt b/Software/LibreCAL/CMakeLists.txt index 8a8e171..1df4462 100644 --- a/Software/LibreCAL/CMakeLists.txt +++ b/Software/LibreCAL/CMakeLists.txt @@ -48,6 +48,7 @@ add_executable(LibreCAL src/USB/msc_disk.cpp src/USB/usb_descriptors.c src/USB/usb.c + src/USB/tmc.cpp src/fatfs/ff.c src/fatfs/ffsystem.c src/fatfs/ffunicode.c diff --git a/Software/LibreCAL/src/USB/tmc.cpp b/Software/LibreCAL/src/USB/tmc.cpp new file mode 100644 index 0000000..7f2e622 --- /dev/null +++ b/Software/LibreCAL/src/USB/tmc.cpp @@ -0,0 +1,260 @@ +/* + * Emulation for a Siglent SEM5000A eCal attached over USBTMC. + * + * Author: Joshua Wise + * Copyright (c) 2025 Accelerated Tech, Inc. + * + * The MIT License (MIT) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + */ + +#include +#include /* atoi */ +#include "tusb.h" +#include "bsp/board_api.h" +#include "serial.h" +#include "ff.h" +#include "Switch.hpp" + +static usbtmc_response_capabilities_t const tud_usbtmc_app_capabilities = { + .USBTMC_status = USBTMC_STATUS_SUCCESS, + .bcdUSBTMC = USBTMC_VERSION, + .bmIntfcCapabilities = { + .listenOnly = 0, + .talkOnly = 0, + .supportsIndicatorPulse = 1 + }, + .bmDevCapabilities = { + .canEndBulkInOnTermChar = 0 + }, +}; + +static size_t ibuf_len; +static uint8_t ibuf[256]; + +static size_t obuf_pos = 0; +static size_t obuf_len = 0; +static uint8_t obuf[1024]; + +static FIL fl_file; +static bool fl_file_open = false; + +extern "C" void tud_usbtmc_open_cb(uint8_t interface_id) { + (void)interface_id; + tud_usbtmc_start_bus_read(); +} + +extern "C" usbtmc_response_capabilities_t const * tud_usbtmc_get_capabilities_cb() { + return &tud_usbtmc_app_capabilities; +} + + +extern "C" bool tud_usbtmc_msg_trigger_cb(usbtmc_msg_generic_t* msg) { + (void)msg; + return true; +} + +extern "C" bool tud_usbtmc_msgBulkOut_start_cb(usbtmc_msg_request_dev_dep_out const * msgHeader) { + ibuf_len = 0; + obuf_pos = 0; + obuf_len = 0; + if (msgHeader->TransferSize > sizeof(ibuf)) { + return false; + } + return true; +} + +extern "C" bool tud_usbtmc_msg_data_cb(void *data, size_t len, bool transfer_complete) { + if (len + ibuf_len < sizeof(ibuf)) { + memcpy(&(ibuf[ibuf_len]), data, len); + ibuf_len += len; + } else { + return false; // buffer overflow! + } + + if (!transfer_complete) { + tud_usbtmc_start_bus_read(); + return true; + } + + /* Ok, we're good, and we've received a message; go parse it. */ + + ibuf[ibuf_len] = 0; + + /* This SCPI interface supports a very different command set than the main + * SCPI interface does, so we don't even share a parser. */ + + if (!strcmp((char *)ibuf, "*IDN?\n")) { + obuf_pos = 0; + obuf_len = snprintf((char *)obuf, sizeof(obuf), "LibreCAL,LibreCAL,%s,%d.%d.%d\n", getSerial(), FW_MAJOR, FW_MINOR, FW_PATCH); + } + + /* The FL:DATA: commands convert to filesystem reads and writes. In a + * more serious USB device, we would kick these onto another thread, but + * for this application it's ok to block the USB thread for the small + * reads. + */ + + if (!strcmp((char *)ibuf, "FL:DATA:READ:STARt\n")) { + fl_file_open = f_open(&fl_file, "0:siglent/info.dat", FA_OPEN_EXISTING | FA_READ) == FR_OK; + obuf_len = 0; + obuf_pos = 0; + } + + if (!strncmp((char *)ibuf, "FL:DATA:INDEX ", 14)) { + int idx = atoi((char *)ibuf + 14); + char name[32]; + snprintf(name, sizeof(name), "0:siglent/data%d.zip", idx); + fl_file_open = f_open(&fl_file, name, FA_OPEN_EXISTING | FA_READ) == FR_OK; + obuf_len = 0; + obuf_pos = 0; + } + + if (!strncmp((char *)ibuf, "FL:DATA:READ? ", 14)) { + size_t req = atoi((char *)ibuf + 14); + + size_t len = tu_min32(sizeof(obuf), req); + size_t rv; + + obuf_len = 0; + obuf_pos = 0; + + if (!fl_file_open) { + goto done; + } + + if (f_read(&fl_file, obuf, len, &rv) != FR_OK) { + goto done; + } + + obuf_len = rv; + obuf_pos = 0; + } + + if (!strncmp((char *)ibuf, "SL ", 3)) { + obuf_len = 0; + obuf_pos = 0; + + /* This is not a very robust parser. */ + char *buf = (char *)ibuf + 3; + const char *cmd = strtok(buf, ","); + if (!cmd) { + goto done; + } + const char *srcport_s = strtok(NULL, ","); + if (!srcport_s) { + goto done; + } + int srcport = atoi(srcport_s) - 1; + if (!strcmp(cmd, "OPEN")) { + Switch::SetStandard(srcport, Switch::Standard::Open); + } else if (!strcmp(cmd, "SHORT")) { + Switch::SetStandard(srcport, Switch::Standard::Short); + } else if (!strcmp(cmd, "LOAD")) { + Switch::SetStandard(srcport, Switch::Standard::Load); + } else if (!strcmp(cmd, "THRU")) { + const char *dstport_s = strtok(NULL, ",\n"); + if (!dstport_s) { + goto done; + } + int dstport = atoi(dstport_s) - 1; + Switch::SetThrough(srcport, dstport); + } + + /* We do not support 'SL ATT,n,m', whatever that is. It seems to be + * used in the VNA confidence check. */ + } + +done: + tud_usbtmc_start_bus_read(); + return true; +} + +extern "C" bool tud_usbtmc_msgBulkIn_complete_cb() { + tud_usbtmc_start_bus_read(); + + return true; +} + +extern "C" bool tud_usbtmc_msgBulkIn_request_cb(usbtmc_msg_request_dev_dep_in const * request) { + size_t txlen = tu_min32(obuf_len - obuf_pos, request->TransferSize); + if (txlen == 0) { + return true; + } + + tud_usbtmc_transmit_dev_msg_data(obuf + obuf_pos, txlen, (obuf_pos + txlen) == obuf_len, false); + obuf_pos += txlen; + + return true; +} + +extern "C" void usbtmc_app_task_iter(void) { +} + +extern "C" bool tud_usbtmc_initiate_clear_cb(uint8_t *tmcResult) { + *tmcResult = USBTMC_STATUS_SUCCESS; + return true; +} + +extern "C" bool tud_usbtmc_check_clear_cb(usbtmc_get_clear_status_rsp_t *rsp) { + ibuf_len = 0; + obuf_len = 0; + obuf_pos = 0; + rsp->USBTMC_status = USBTMC_STATUS_SUCCESS; + rsp->bmClear.BulkInFifoBytes = 0u; + return true; +} + +extern "C" bool tud_usbtmc_initiate_abort_bulk_in_cb(uint8_t *tmcResult) { + *tmcResult = USBTMC_STATUS_SUCCESS; + return true; +} + +extern "C" bool tud_usbtmc_check_abort_bulk_in_cb(usbtmc_check_abort_bulk_rsp_t *rsp) { + (void)rsp; + tud_usbtmc_start_bus_read(); + return true; +} + +extern "C" bool tud_usbtmc_initiate_abort_bulk_out_cb(uint8_t *tmcResult) { + *tmcResult = USBTMC_STATUS_SUCCESS; + return true; +} + +extern "C" bool tud_usbtmc_check_abort_bulk_out_cb(usbtmc_check_abort_bulk_rsp_t *rsp) { + (void)rsp; + tud_usbtmc_start_bus_read(); + return true; +} + +extern "C" void tud_usbtmc_bulkIn_clearFeature_cb(void) { +} + +extern "C" void tud_usbtmc_bulkOut_clearFeature_cb(void) { + tud_usbtmc_start_bus_read(); +} + +extern "C" bool tud_usbtmc_indicator_pulse_cb(tusb_control_request_t const * msg, uint8_t *tmcResult) { + (void)msg; + // led_indicator_pulse(); + *tmcResult = USBTMC_STATUS_SUCCESS; + return true; +} diff --git a/Software/LibreCAL/src/USB/tusb_config.h b/Software/LibreCAL/src/USB/tusb_config.h index bc6b138..3a1d76a 100644 --- a/Software/LibreCAL/src/USB/tusb_config.h +++ b/Software/LibreCAL/src/USB/tusb_config.h @@ -100,6 +100,9 @@ #define CFG_TUD_MSC 1 #define CFG_TUD_MIDI 0 #define CFG_TUD_VENDOR 1 +#define CFG_TUD_USBTMC 1 +#define CFG_TUD_USBTMC_ENABLE_INT_EP 0 +#define CFG_TUD_USBTMC_ENABLE_488 0 // CDC FIFO size of TX and RX #define CFG_TUD_CDC_RX_BUFSIZE (TUD_OPT_HIGH_SPEED ? 512 : 256) diff --git a/Software/LibreCAL/src/USB/usb.h b/Software/LibreCAL/src/USB/usb.h index 73d1968..ab5e746 100644 --- a/Software/LibreCAL/src/USB/usb.h +++ b/Software/LibreCAL/src/USB/usb.h @@ -17,6 +17,7 @@ typedef enum { typedef void(*usbd_recv_callback_t)(const uint8_t *buf, uint16_t len, usb_interface_t i); void usb_init(usbd_recv_callback_t receive_callback); +void usb_is_siglent(); uint16_t usb_available_buffer(); bool usb_transmit(const uint8_t *data, uint16_t length, uint8_t interface); void usb_log(const char *log, uint16_t length); diff --git a/Software/LibreCAL/src/USB/usb_descriptors.c b/Software/LibreCAL/src/USB/usb_descriptors.c index 4122453..4e5a23f 100644 --- a/Software/LibreCAL/src/USB/usb_descriptors.c +++ b/Software/LibreCAL/src/USB/usb_descriptors.c @@ -58,11 +58,43 @@ tusb_desc_device_t const desc_device = .bNumConfigurations = 0x01 }; +tusb_desc_device_t const desc_device_siglent = +{ + .bLength = sizeof(tusb_desc_device_t), + .bDescriptorType = TUSB_DESC_DEVICE, + .bcdUSB = 0x0210, // at least 2.1 or 3.x for BOS & webUSB + + // We behave like a USBTMC device for this. + .bDeviceClass = 0x00, + .bDeviceSubClass = 0x00, + .bDeviceProtocol = 0x00, + .bMaxPacketSize0 = CFG_TUD_ENDPOINT0_SIZE, + + // Pretend to be a Siglent eCal. + .idVendor = 0xf4ec, + .idProduct = 0x1600, + .bcdDevice = 0x0100, + + .iManufacturer = 0x01, + .iProduct = 0x08, + .iSerialNumber = 0x03, + + .bNumConfigurations = 0x01 +}; + + +static bool is_siglent = false; + +// emulate a Siglent eCal +void usb_is_siglent() { + is_siglent = true; +} + // Invoked when received GET DEVICE DESCRIPTOR // Application return pointer to descriptor uint8_t const * tud_descriptor_device_cb(void) { - return (uint8_t const *) &desc_device; + return is_siglent ? (uint8_t const *) &desc_device_siglent : (uint8_t const *) &desc_device; } //--------------------------------------------------------------------+ @@ -77,7 +109,8 @@ enum ITF_NUM_TOTAL }; -#define CONFIG_TOTAL_LEN (TUD_CONFIG_DESC_LEN + TUD_CDC_DESC_LEN + TUD_VENDOR_DESC_LEN + TUD_MSC_DESC_LEN) +#define ITF_NUM_USBTMC ITF_NUM_MSC + #if CFG_TUSB_MCU == OPT_MCU_LPC175X_6X || CFG_TUSB_MCU == OPT_MCU_LPC177X_8X || CFG_TUSB_MCU == OPT_MCU_LPC40XX // LPC 17xx and 40xx endpoint type (bulk/interrupt/iso) are fixed by its number @@ -102,8 +135,12 @@ enum #define EPNUM_MSC_OUT 4 #define EPNUM_MSC2_IN 5 #define EPNUM_MSC2_OUT 5 + #define EPNUM_TMC_IN 6 + #define EPNUM_TMC_OUT 6 #endif +#define CONFIG_TOTAL_LEN (TUD_CONFIG_DESC_LEN + TUD_CDC_DESC_LEN + TUD_VENDOR_DESC_LEN + TUD_MSC_DESC_LEN) + uint8_t const desc_configuration[] = { // Config number, interface count, string index, total length, attribute, power in mA @@ -119,13 +156,35 @@ uint8_t const desc_configuration[] = TUD_MSC_DESCRIPTOR(ITF_NUM_MSC, 6, EPNUM_MSC_OUT, 0x80 | EPNUM_MSC_IN, 64), }; +/* We don't want a MSC device to confuse the UI and attempt to save files to + * "USB storage", so Siglent mode exposes its TMC interface *instead* of the + * MSC interface. + */ + +#define CONFIG_TOTAL_LEN_SIGLENT (TUD_CONFIG_DESC_LEN + TUD_CDC_DESC_LEN + TUD_VENDOR_DESC_LEN + TUD_USBTMC_IF_DESCRIPTOR_LEN + TUD_USBTMC_BULK_DESCRIPTORS_LEN) + +uint8_t const desc_configuration_siglent[] = +{ + // Config number, interface count, string index, total length, attribute, power in mA + TUD_CONFIG_DESCRIPTOR(1, ITF_NUM_TOTAL, 0, CONFIG_TOTAL_LEN_SIGLENT, 0x00, 100), + + // Interface number, string index, EP notification address and size, EP data address (out, in) and size. + TUD_CDC_DESCRIPTOR(ITF_NUM_CDC, 4, 0x81, 8, EPNUM_CDC_OUT, 0x80 | EPNUM_CDC_IN, TUD_OPT_HIGH_SPEED ? 512 : 64), + + // Interface number, string index, EP Out & IN address, EP size + TUD_VENDOR_DESCRIPTOR(ITF_NUM_VENDOR, 5, EPNUM_VENDOR_OUT, 0x80 | EPNUM_VENDOR_IN, TUD_OPT_HIGH_SPEED ? 512 : 64), + + TUD_USBTMC_IF_DESCRIPTOR(ITF_NUM_USBTMC, /* _bNumEndpoints = */ 2u, /*_stridx = */ 7u, 0 /* no subclass */), + TUD_USBTMC_BULK_DESCRIPTORS(/* OUT = */ EPNUM_TMC_OUT, /* IN = */ 0x80 | EPNUM_TMC_IN, /* packet size = */ 64), +}; + // Invoked when received GET CONFIGURATION DESCRIPTOR // Application return pointer to descriptor // Descriptor contents must exist long enough for transfer to complete uint8_t const * tud_descriptor_configuration_cb(uint8_t index) { (void) index; // for multiple configurations - return desc_configuration; + return is_siglent ? desc_configuration_siglent : desc_configuration; } //--------------------------------------------------------------------+ @@ -210,9 +269,11 @@ char const* string_desc_arr [] = "LibreCAL CDC", // 4: CDC Interface "LibreCAL Vendor", // 5: Vendor Interface "LibreCAL Storage", // 6: MSC Interface + "LibreCAL Siglent TMC", // 7: USBTMC interface + "LibreCAL (Siglent eCal emulation mode)", // 8: Product ID in eCal emulation mode }; -static uint16_t _desc_str[32]; +static uint16_t _desc_str[64]; // Invoked when received GET STRING DESCRIPTOR request // Application return pointer to descriptor, whose contents must exist long enough for transfer to complete @@ -242,7 +303,7 @@ uint16_t const* tud_descriptor_string_cb(uint8_t index, uint16_t langid) // Cap at max char chr_count = (uint8_t) strlen(str); - if ( chr_count > 31 ) chr_count = 31; + if ( chr_count > 63 ) chr_count = 63; // Convert ASCII string into UTF-16 for(uint8_t i=0; i Date: Thu, 29 May 2025 14:22:42 +0200 Subject: [PATCH 03/10] Build test with older toolchain --- .github/workflows/Build.yml | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/.github/workflows/Build.yml b/.github/workflows/Build.yml index baaeae2..45385eb 100644 --- a/.github/workflows/Build.yml +++ b/.github/workflows/Build.yml @@ -207,7 +207,14 @@ jobs: - name: Install toolchain run: | sudo apt-get update - sudo apt install -y build-essential gcc-arm-none-eabi binutils-arm-none-eabi + sudo apt install -y build-essential binutils-arm-none-eabi + + # Ubuntu 24.04 comes with version 10.3 of the ARM GCC toolchain. For some reason builds with that version result in buggy file reading/writing and unstable USB connections. + # Manually install older version of the toolchain which does not have the problem + wget https://developer.arm.com/-/media/Files/downloads/gnu-rm/9-2020q2/gcc-arm-none-eabi-9-2020-q2-update-aarch64-linux.tar.bz2 + tar -xvjf gcc-arm-none-eabi-9-2020-q2-update-x86_64-linux.tar.bz2 -C /opt + export PATH=/opt/gcc-arm-none-eabi-9-2020-q2-update/bin:$PATH + sudo git clone https://github.com/raspberrypi/pico-sdk.git /opt/pico-sdk sudo git -C /opt/pico-sdk checkout 2.1.1 sudo git -C /opt/pico-sdk submodule update --init @@ -234,7 +241,6 @@ jobs: mkdir build && cd build cmake .. make -j9 - make -j9 shell: bash - name: Upload From af8e2b685d2f94415936df75b6d16ba33a98aaf7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20K=C3=A4berich?= Date: Thu, 29 May 2025 14:24:07 +0200 Subject: [PATCH 04/10] Build test with older toolchain --- .github/workflows/Build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/Build.yml b/.github/workflows/Build.yml index 45385eb..7b511bf 100644 --- a/.github/workflows/Build.yml +++ b/.github/workflows/Build.yml @@ -211,7 +211,7 @@ jobs: # Ubuntu 24.04 comes with version 10.3 of the ARM GCC toolchain. For some reason builds with that version result in buggy file reading/writing and unstable USB connections. # Manually install older version of the toolchain which does not have the problem - wget https://developer.arm.com/-/media/Files/downloads/gnu-rm/9-2020q2/gcc-arm-none-eabi-9-2020-q2-update-aarch64-linux.tar.bz2 + wget https://developer.arm.com/-/media/Files/downloads/gnu-rm/9-2020q2/gcc-arm-none-eabi-9-2020-q2-update-x86_64-linux.tar.bz2 tar -xvjf gcc-arm-none-eabi-9-2020-q2-update-x86_64-linux.tar.bz2 -C /opt export PATH=/opt/gcc-arm-none-eabi-9-2020-q2-update/bin:$PATH From 540e6bb4a47f006b848a45710d85977303981fd8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20K=C3=A4berich?= Date: Thu, 29 May 2025 14:30:38 +0200 Subject: [PATCH 05/10] Build test with older toolchain --- .github/workflows/Build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/Build.yml b/.github/workflows/Build.yml index 7b511bf..b6493cd 100644 --- a/.github/workflows/Build.yml +++ b/.github/workflows/Build.yml @@ -213,7 +213,6 @@ jobs: # Manually install older version of the toolchain which does not have the problem wget https://developer.arm.com/-/media/Files/downloads/gnu-rm/9-2020q2/gcc-arm-none-eabi-9-2020-q2-update-x86_64-linux.tar.bz2 tar -xvjf gcc-arm-none-eabi-9-2020-q2-update-x86_64-linux.tar.bz2 -C /opt - export PATH=/opt/gcc-arm-none-eabi-9-2020-q2-update/bin:$PATH sudo git clone https://github.com/raspberrypi/pico-sdk.git /opt/pico-sdk sudo git -C /opt/pico-sdk checkout 2.1.1 @@ -237,6 +236,7 @@ jobs: - name: Build application run: | export PICO_SDK_PATH=/opt/pico-sdk + export PATH=/opt/gcc-arm-none-eabi-9-2020-q2-update/bin:$PATH cd Software/LibreCAL mkdir build && cd build cmake .. From fa75511de3f93873b248fce9c4787603267d3b03 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20K=C3=A4berich?= Date: Fri, 30 May 2025 14:06:21 +0200 Subject: [PATCH 06/10] Improvements to Siglent emulation mode - handle *IDN? response and SET:PORT commands send by some VNAs - default to emulation mode if files are present (press function to force default mode) - Adjusted script to set number of ports and handle early LibreCALs with partially missing factory data - Add optional logging to LibreCAL firmware - indicate emulation mode by blinking wait/ready LEDs --- .github/workflows/Build.yml | 2 +- Software/LibreCAL/CMakeLists.txt | 6 +- Software/LibreCAL/src/Log.cpp | 109 ++++++++++++++++++ Software/LibreCAL/src/Log.h | 73 ++++++++++++ .../src/USB/{tmc.cpp => siglent_tmc.cpp} | 78 +++++++++++-- Software/LibreCAL/src/USB/usb_descriptors.c | 108 ++++++++--------- Software/LibreCAL/src/UserInterface.cpp | 19 ++- Software/LibreCAL/src/main.cpp | 37 ++++-- Software/LibreCAL/src/main.h | 16 +++ Software/Scripts/convert_siglent.py | 31 ++--- 10 files changed, 386 insertions(+), 93 deletions(-) create mode 100644 Software/LibreCAL/src/Log.cpp create mode 100644 Software/LibreCAL/src/Log.h rename Software/LibreCAL/src/USB/{tmc.cpp => siglent_tmc.cpp} (75%) create mode 100644 Software/LibreCAL/src/main.h diff --git a/.github/workflows/Build.yml b/.github/workflows/Build.yml index b6493cd..d6a5199 100644 --- a/.github/workflows/Build.yml +++ b/.github/workflows/Build.yml @@ -4,7 +4,7 @@ on: push: branches: - main - - build_FW_test + - siglent pull_request: branches: - main diff --git a/Software/LibreCAL/CMakeLists.txt b/Software/LibreCAL/CMakeLists.txt index 1df4462..05f52b9 100644 --- a/Software/LibreCAL/CMakeLists.txt +++ b/Software/LibreCAL/CMakeLists.txt @@ -33,6 +33,7 @@ add_definitions( -DFW_MAJOR=0 -DFW_MINOR=2 -DFW_PATCH=4 +#-DENABLE_UART ) add_executable(LibreCAL @@ -48,11 +49,12 @@ add_executable(LibreCAL src/USB/msc_disk.cpp src/USB/usb_descriptors.c src/USB/usb.c - src/USB/tmc.cpp + src/USB/siglent_tmc.cpp src/fatfs/ff.c src/fatfs/ffsystem.c src/fatfs/ffunicode.c src/fatfs/flashdisk.cpp + src/Log.cpp ) target_include_directories(LibreCAL PUBLIC @@ -63,4 +65,4 @@ target_include_directories(LibreCAL PUBLIC ) pico_add_extra_outputs(LibreCAL) -target_link_libraries(LibreCAL pico_stdlib pico_unique_id hardware_rtc hardware_spi hardware_pwm hardware_adc FreeRTOS tinyusb_device tinyusb_board) +target_link_libraries(LibreCAL pico_stdlib pico_unique_id hardware_rtc hardware_uart hardware_spi hardware_pwm hardware_adc FreeRTOS tinyusb_device tinyusb_board) diff --git a/Software/LibreCAL/src/Log.cpp b/Software/LibreCAL/src/Log.cpp new file mode 100644 index 0000000..67870f4 --- /dev/null +++ b/Software/LibreCAL/src/Log.cpp @@ -0,0 +1,109 @@ +#include "Log.h" + +#ifdef ENABLE_UART + +#include +#include +#include + +#include "hardware/uart.h" +#include "hardware/gpio.h" + +#include "FreeRTOS.h" +#include "task.h" + +extern "C" { + +#define MAX_LINE_LENGTH 256 + +static char fifo[LOG_SENDBUF_LENGTH + MAX_LINE_LENGTH]; +static uint16_t fifo_write, fifo_read; + +#ifdef LOG_USE_MUTEX +#include "FreeRTOS.h" +#include "semphr.h" +static StaticSemaphore_t xMutex; +static SemaphoreHandle_t mutex; +#endif + +#define INC_FIFO_POS(pos, inc) do { pos = (pos + inc) % LOG_SENDBUF_LENGTH; } while(0) + +static uint16_t fifo_space() { + uint16_t used; + if(fifo_write >= fifo_read) { + used = fifo_write - fifo_read; + } else { + used = fifo_write - fifo_read + LOG_SENDBUF_LENGTH; + } + return LOG_SENDBUF_LENGTH - used - 1; +} + +void on_uart_needs_data(void) { + if(fifo_read != fifo_write) { + uart_putc_raw(LOG_UART_ID, fifo[fifo_read]); + INC_FIFO_POS(fifo_read, 1); + } else { + // all done, disable interrupt + uart_set_irq_enables(LOG_UART_ID, false, false); + } +} + +void Log_Init() { + fifo_write = 0; + fifo_read = 0; +#ifdef LOG_USE_MUTEXES + mutex = xSemaphoreCreateMutexStatic(&xMutex); +#endif + + // initialize the UART + uart_init(LOG_UART_ID, 115200); + gpio_set_function(LOG_UART_PIN, UART_FUNCSEL_NUM(LOG_UART_ID, LOG_UART_PIN)); + + uart_set_hw_flow(LOG_UART_ID, false, false); + uart_set_fifo_enabled(LOG_UART_ID, false); + + // Set up the TX interrupt + int UART_IRQ = LOG_UART_ID == uart0 ? UART0_IRQ : UART1_IRQ; + irq_set_exclusive_handler(UART_IRQ, on_uart_needs_data); + irq_set_enabled(UART_IRQ, true); + + // we need to write something here, otherwise the handler is never called later? + uart_puts(LOG_UART_ID, "LibreCAL log start\r\n"); +} + + +void _log_write(const char *module, const char *level, const char *fmt, ...) { + int written = 0; + va_list args; + va_start(args, fmt); +#ifdef LOG_USE_MUTEX + if (!STM::InInterrupt()) { + xSemaphoreTake(mutex, portMAX_DELAY); + } +#endif + written = snprintf(&fifo[fifo_write], MAX_LINE_LENGTH, "%05lu [%6.6s,%s]: ", + xTaskGetTickCount(), module, level); + written += vsnprintf(&fifo[fifo_write + written], MAX_LINE_LENGTH - written, + fmt, args); + written += snprintf(&fifo[fifo_write + written], MAX_LINE_LENGTH - written, + "\r\n"); + + // check if line still fits into ring buffer + if (written > fifo_space()) { + // unable to fit line, skip + return; + } + + int16_t overflow = (fifo_write + written) - LOG_SENDBUF_LENGTH; + if (overflow > 0) { + // printf wrote over the end of the ring buffer -> wrap around + memmove(&fifo[0], &fifo[LOG_SENDBUF_LENGTH], overflow); + } + INC_FIFO_POS(fifo_write, written); + // enable interrupt + uart_set_irq_enables(LOG_UART_ID, false, true); +} +} + +#endif + diff --git a/Software/LibreCAL/src/Log.h b/Software/LibreCAL/src/Log.h new file mode 100644 index 0000000..22cb3b8 --- /dev/null +++ b/Software/LibreCAL/src/Log.h @@ -0,0 +1,73 @@ +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#ifndef ENABLE_UART +// Log output disabled +#define LOG_CRIT(fmt, ...) +#define LOG_ERR(fmt, ...) +#define LOG_WARN(fmt, ...) +#define LOG_INFO(fmt, ...) +#define LOG_DEBUG(fmt, ...) +#else + +#define LOG_UART_ID uart1 +#define LOG_UART_PIN 24 + +#define LOG_SENDBUF_LENGTH 1024 + +#define LOG_LEVEL_DEBUG 4 +#define LOG_LEVEL_INFO 3 +#define LOG_LEVEL_WARN 2 +#define LOG_LEVEL_ERR 1 +#define LOG_LEVEL_CRIT 0 + +#define LOG_LEVEL_DEFAULT LOG_LEVEL_ERR + +#ifndef LOG_LEVEL +#define LOG_LEVEL LOG_LEVEL_DEFAULT +#endif + +#ifndef LOG_MODULE +#define LOG_MODULE "Log" +#endif + +#if LOG_LEVEL >= LOG_LEVEL_CRIT +#define LOG_CRIT(fmt, ...) _log_write(LOG_MODULE, "CRT", fmt, ## __VA_ARGS__) +#else +#define LOG_CRIT(fmt, ...) +#endif +#if LOG_LEVEL >= LOG_LEVEL_ERR +#define LOG_ERR(fmt, ...) _log_write(LOG_MODULE, "ERR", fmt, ## __VA_ARGS__) +#else +#define LOG_ERR(fmt, ...) +#endif +#if LOG_LEVEL >= LOG_LEVEL_WARN +#define LOG_WARN(fmt, ...) _log_write(LOG_MODULE, "WRN", fmt, ## __VA_ARGS__) +#else +#define LOG_WARN(fmt, ...) +#endif +#if LOG_LEVEL >= LOG_LEVEL_INFO +#define LOG_INFO(fmt, ...) _log_write(LOG_MODULE, "INF", fmt, ## __VA_ARGS__) +#else +#define LOG_INFO(fmt, ...) +#endif +#if LOG_LEVEL >= LOG_LEVEL_DEBUG +#define LOG_DEBUG(fmt, ...) _log_write(LOG_MODULE, "DBG", fmt, ## __VA_ARGS__) +#else +#define LOG_DEBUG(fmt, ...) +#endif + +#include + +void Log_Init(); +typedef void (*log_redirect_t)(const char *line, uint16_t length); +void _log_write(const char *module, const char *level, const char *fmt, ...); + +#endif + +#ifdef __cplusplus +} +#endif diff --git a/Software/LibreCAL/src/USB/tmc.cpp b/Software/LibreCAL/src/USB/siglent_tmc.cpp similarity index 75% rename from Software/LibreCAL/src/USB/tmc.cpp rename to Software/LibreCAL/src/USB/siglent_tmc.cpp index 7f2e622..aa6e933 100644 --- a/Software/LibreCAL/src/USB/tmc.cpp +++ b/Software/LibreCAL/src/USB/siglent_tmc.cpp @@ -33,6 +33,12 @@ #include "serial.h" #include "ff.h" #include "Switch.hpp" +#include +#include + +#define LOG_LEVEL LOG_LEVEL_DEBUG +#define LOG_MODULE "USB TMC" +#include "Log.h" static usbtmc_response_capabilities_t const tud_usbtmc_app_capabilities = { .USBTMC_status = USBTMC_STATUS_SUCCESS, @@ -96,15 +102,23 @@ extern "C" bool tud_usbtmc_msg_data_cb(void *data, size_t len, bool transfer_com } /* Ok, we're good, and we've received a message; go parse it. */ - ibuf[ibuf_len] = 0; + LOG_DEBUG("USB TMC: %s", ibuf); + /* different Siglent VNAs seem to use different cases for the commands, force everything to upper case */ + for(size_t i=0;i #include "tusb.h" #include "serial.h" +#include "main.h" #define USB_PID (0x4000 | _PID_MAP(CDC, 0) | _PID_MAP(MSC, 1) | _PID_MAP(HID, 2) | \ _PID_MAP(MIDI, 3) | _PID_MAP(VENDOR, 4) ) @@ -34,7 +35,7 @@ //--------------------------------------------------------------------+ // Device Descriptors //--------------------------------------------------------------------+ -tusb_desc_device_t const desc_device = +tusb_desc_device_t const desc_device_default = { .bLength = sizeof(tusb_desc_device_t), .bDescriptorType = TUSB_DESC_DEVICE, @@ -82,78 +83,64 @@ tusb_desc_device_t const desc_device_siglent = .bNumConfigurations = 0x01 }; - -static bool is_siglent = false; - -// emulate a Siglent eCal -void usb_is_siglent() { - is_siglent = true; -} - // Invoked when received GET DEVICE DESCRIPTOR // Application return pointer to descriptor uint8_t const * tud_descriptor_device_cb(void) { - return is_siglent ? (uint8_t const *) &desc_device_siglent : (uint8_t const *) &desc_device; + switch(getMode()) { + case MODE_DEFAULT: return (uint8_t const *) &desc_device_default; + case MODE_SIGLENT: return (uint8_t const *) &desc_device_siglent; + } } //--------------------------------------------------------------------+ // Configuration Descriptor //--------------------------------------------------------------------+ +// Default mode enum { - ITF_NUM_CDC = 0, - ITF_NUM_CDC_DATA, - ITF_NUM_VENDOR, - ITF_NUM_MSC, - ITF_NUM_TOTAL + ITF_DEF_NUM_CDC = 0, + ITF_DEF_NUM_CDC_DATA, + ITF_DEF_NUM_VENDOR, + ITF_DEF_NUM_MSC, + ITF_DEF_NUM_TOTAL }; -#define ITF_NUM_USBTMC ITF_NUM_MSC - - -#if CFG_TUSB_MCU == OPT_MCU_LPC175X_6X || CFG_TUSB_MCU == OPT_MCU_LPC177X_8X || CFG_TUSB_MCU == OPT_MCU_LPC40XX - // LPC 17xx and 40xx endpoint type (bulk/interrupt/iso) are fixed by its number - // 0 control, 1 In, 2 Bulk, 3 Iso, 4 In etc ... - #define EPNUM_CDC_IN 2 - #define EPNUM_CDC_OUT 2 - #define EPNUM_VENDOR_IN 5 - #define EPNUM_VENDOR_OUT 5 -#elif CFG_TUSB_MCU == OPT_MCU_SAMG || CFG_TUSB_MCU == OPT_MCU_SAMX7X - // SAMG & SAME70 don't support a same endpoint number with different direction IN and OUT - // e.g EP1 OUT & EP1 IN cannot exist together - #define EPNUM_CDC_IN 2 - #define EPNUM_CDC_OUT 3 - #define EPNUM_VENDOR_IN 4 - #define EPNUM_VENDOR_OUT 5 -#else - #define EPNUM_CDC_IN 2 - #define EPNUM_CDC_OUT 2 - #define EPNUM_VENDOR_IN 3 - #define EPNUM_VENDOR_OUT 3 - #define EPNUM_MSC_IN 4 - #define EPNUM_MSC_OUT 4 - #define EPNUM_MSC2_IN 5 - #define EPNUM_MSC2_OUT 5 - #define EPNUM_TMC_IN 6 - #define EPNUM_TMC_OUT 6 -#endif +// Endpoints for default mode +#define EPNUM_CDC_IN 2 +#define EPNUM_CDC_OUT 2 +#define EPNUM_VENDOR_IN 3 +#define EPNUM_VENDOR_OUT 3 +#define EPNUM_MSC_IN 4 +#define EPNUM_MSC_OUT 4 +#define EPNUM_MSC2_IN 5 +#define EPNUM_MSC2_OUT 5 + +// Siglent mode +enum +{ + ITF_SIGLENT_TMC = 0, + ITF_SGILENT_NUM_TOTAL +}; +// Endpoints for Siglent mode +#define EPNUM_TMC_IN 2 +#define EPNUM_TMC_OUT 2 #define CONFIG_TOTAL_LEN (TUD_CONFIG_DESC_LEN + TUD_CDC_DESC_LEN + TUD_VENDOR_DESC_LEN + TUD_MSC_DESC_LEN) -uint8_t const desc_configuration[] = +uint8_t const desc_configuration_default[] = { // Config number, interface count, string index, total length, attribute, power in mA - TUD_CONFIG_DESCRIPTOR(1, ITF_NUM_TOTAL, 0, CONFIG_TOTAL_LEN, 0x00, 100), + TUD_CONFIG_DESCRIPTOR(1, ITF_DEF_NUM_TOTAL, 0, CONFIG_TOTAL_LEN, 0x00, 100), // Interface number, string index, EP notification address and size, EP data address (out, in) and size. - TUD_CDC_DESCRIPTOR(ITF_NUM_CDC, 4, 0x81, 8, EPNUM_CDC_OUT, 0x80 | EPNUM_CDC_IN, TUD_OPT_HIGH_SPEED ? 512 : 64), + TUD_CDC_DESCRIPTOR(ITF_DEF_NUM_CDC, 4, 0x81, 8, EPNUM_CDC_OUT, 0x80 | EPNUM_CDC_IN, TUD_OPT_HIGH_SPEED ? 512 : 64), // Interface number, string index, EP Out & IN address, EP size - TUD_VENDOR_DESCRIPTOR(ITF_NUM_VENDOR, 5, EPNUM_VENDOR_OUT, 0x80 | EPNUM_VENDOR_IN, TUD_OPT_HIGH_SPEED ? 512 : 64), + TUD_VENDOR_DESCRIPTOR(ITF_DEF_NUM_VENDOR, 5, EPNUM_VENDOR_OUT, 0x80 | EPNUM_VENDOR_IN, TUD_OPT_HIGH_SPEED ? 512 : 64), // Interface number, string index, EP Out & EP In address, EP size - TUD_MSC_DESCRIPTOR(ITF_NUM_MSC, 6, EPNUM_MSC_OUT, 0x80 | EPNUM_MSC_IN, 64), + TUD_MSC_DESCRIPTOR(ITF_DEF_NUM_MSC, 6, EPNUM_MSC_OUT, 0x80 | EPNUM_MSC_IN, 64), }; /* We don't want a MSC device to confuse the UI and attempt to save files to @@ -161,20 +148,20 @@ uint8_t const desc_configuration[] = * MSC interface. */ -#define CONFIG_TOTAL_LEN_SIGLENT (TUD_CONFIG_DESC_LEN + TUD_CDC_DESC_LEN + TUD_VENDOR_DESC_LEN + TUD_USBTMC_IF_DESCRIPTOR_LEN + TUD_USBTMC_BULK_DESCRIPTORS_LEN) +#define CONFIG_TOTAL_LEN_SIGLENT (TUD_CONFIG_DESC_LEN + TUD_USBTMC_IF_DESCRIPTOR_LEN + TUD_USBTMC_BULK_DESCRIPTORS_LEN) uint8_t const desc_configuration_siglent[] = { // Config number, interface count, string index, total length, attribute, power in mA - TUD_CONFIG_DESCRIPTOR(1, ITF_NUM_TOTAL, 0, CONFIG_TOTAL_LEN_SIGLENT, 0x00, 100), - - // Interface number, string index, EP notification address and size, EP data address (out, in) and size. - TUD_CDC_DESCRIPTOR(ITF_NUM_CDC, 4, 0x81, 8, EPNUM_CDC_OUT, 0x80 | EPNUM_CDC_IN, TUD_OPT_HIGH_SPEED ? 512 : 64), + TUD_CONFIG_DESCRIPTOR(1, ITF_SGILENT_NUM_TOTAL, 0, CONFIG_TOTAL_LEN_SIGLENT, 0x00, 100), - // Interface number, string index, EP Out & IN address, EP size - TUD_VENDOR_DESCRIPTOR(ITF_NUM_VENDOR, 5, EPNUM_VENDOR_OUT, 0x80 | EPNUM_VENDOR_IN, TUD_OPT_HIGH_SPEED ? 512 : 64), +// // Interface number, string index, EP notification address and size, EP data address (out, in) and size. +// TUD_CDC_DESCRIPTOR(ITF_NUM_CDC, 4, 0x81, 8, EPNUM_CDC_OUT, 0x80 | EPNUM_CDC_IN, TUD_OPT_HIGH_SPEED ? 512 : 64), +// +// // Interface number, string index, EP Out & IN address, EP size +// TUD_VENDOR_DESCRIPTOR(ITF_NUM_VENDOR, 5, EPNUM_VENDOR_OUT, 0x80 | EPNUM_VENDOR_IN, TUD_OPT_HIGH_SPEED ? 512 : 64), - TUD_USBTMC_IF_DESCRIPTOR(ITF_NUM_USBTMC, /* _bNumEndpoints = */ 2u, /*_stridx = */ 7u, 0 /* no subclass */), + TUD_USBTMC_IF_DESCRIPTOR(ITF_SIGLENT_TMC, /* _bNumEndpoints = */ 2u, /*_stridx = */ 7u, 0 /* no subclass */), TUD_USBTMC_BULK_DESCRIPTORS(/* OUT = */ EPNUM_TMC_OUT, /* IN = */ 0x80 | EPNUM_TMC_IN, /* packet size = */ 64), }; @@ -184,7 +171,10 @@ uint8_t const desc_configuration_siglent[] = uint8_t const * tud_descriptor_configuration_cb(uint8_t index) { (void) index; // for multiple configurations - return is_siglent ? desc_configuration_siglent : desc_configuration; + switch(getMode()) { + case MODE_DEFAULT: return desc_configuration_default; + case MODE_SIGLENT: return desc_configuration_siglent; + } } //--------------------------------------------------------------------+ @@ -234,7 +224,7 @@ uint8_t const desc_ms_os_20[] = U16_TO_U8S_LE(0x0008), U16_TO_U8S_LE(MS_OS_20_SUBSET_HEADER_CONFIGURATION), 0, 0, U16_TO_U8S_LE(MS_OS_20_DESC_LEN-0x0A), // Function Subset header: length, type, first interface, reserved, subset length - U16_TO_U8S_LE(0x0008), U16_TO_U8S_LE(MS_OS_20_SUBSET_HEADER_FUNCTION), ITF_NUM_VENDOR, 0, U16_TO_U8S_LE(MS_OS_20_DESC_LEN-0x0A-0x08), + U16_TO_U8S_LE(0x0008), U16_TO_U8S_LE(MS_OS_20_SUBSET_HEADER_FUNCTION), ITF_DEF_NUM_VENDOR, 0, U16_TO_U8S_LE(MS_OS_20_DESC_LEN-0x0A-0x08), // MS OS 2.0 Compatible ID descriptor: length, type, compatible ID, sub compatible ID U16_TO_U8S_LE(0x0014), U16_TO_U8S_LE(MS_OS_20_FEATURE_COMPATBLE_ID), 'W', 'I', 'N', 'U', 'S', 'B', 0x00, 0x00, diff --git a/Software/LibreCAL/src/UserInterface.cpp b/Software/LibreCAL/src/UserInterface.cpp index 9cce8f3..fa1aac4 100644 --- a/Software/LibreCAL/src/UserInterface.cpp +++ b/Software/LibreCAL/src/UserInterface.cpp @@ -6,6 +6,8 @@ #include "task.h" #include "pico/stdlib.h" #include +#include "Log.h" +#include "main.h" namespace UserInterface { @@ -36,6 +38,11 @@ constexpr uint32_t ledBlinkPeriod = 200; constexpr uint32_t ledBlinkOnTime = 100; void setLED(LED led, bool on) { +#ifdef ENABLE_UART + if(LEDpins[(int) led] == LOG_UART_PIN) { + return; + } +#endif gpio_put(LEDpins[(int) led], !on); } @@ -94,11 +101,14 @@ void Task(void*) { } // update LEDs + + // wait/ready are solid on when in default mode and blink when in any other mode + bool on = (getMode() == MODE_DEFAULT) || (xTaskGetTickCount() % ledBlinkPeriod > ledBlinkOnTime); if(Heater::IsStable()) { setLED(LED::WAIT, false); - setLED(LED::READY, true); + setLED(LED::READY, on); } else { - setLED(LED::WAIT, true); + setLED(LED::WAIT, on); setLED(LED::READY, false); } if(editing) { @@ -138,6 +148,11 @@ void Task(void*) { void Init() { // Initialize pins for(uint8_t i=0;i +#include +#include #include #include #include @@ -18,6 +20,10 @@ #include "UserInterface.hpp" #include "Heater.hpp" +#define LOG_LEVEL LOG_LEVEL_INFO +#define LOG_MODULE "App" +#include "Log.h" + const char* date = __DATE__; const char* time = __TIME__; @@ -58,6 +64,12 @@ static xTaskHandle handle; Flash flash(spi0, FLASH_CLK_PIN, FLASH_MOSI_PIN, FLASH_MISO_PIN, FLASH_CS_PIN); +static ecal_mode_t mode = MODE_DEFAULT; + +ecal_mode_t getMode() { + return mode; +} + static void usb_rx(const uint8_t *buf, uint16_t len, usb_interface_t i) { if(len > sizeof(usb_buffer)) { // line too long @@ -129,14 +141,19 @@ static void defaultTask(void* ptr) { } } - if (UserInterface::IsFunctionHeld() && !f_open(&fil, "0:siglent/info.dat", FA_OPEN_EXISTING | FA_READ)) { - /* someone has given us Siglent parameters, enter Siglent - * mode. maybe in the future we should compute a default - * siglent/info.dat to put in the RO partition, but that - * would require a zip engine onboard; punt on that for now - */ - usb_is_siglent(); - f_close(&fil); + // Figure out which mode we should be in. The default mode is the normal LibreCAL mode. + // You can always force that mode by pressing the function button when power is applied. + // If that button is not pressed, the LibreCAL may emulate other electronic calibration + // units it the necessary files are available + if(UserInterface::IsFunctionHeld()) { + mode = MODE_DEFAULT; + } else { + // default mode is not forced, check files + if(!f_open(&fil, "0:siglent/info.dat", FA_OPEN_EXISTING | FA_READ)) { + // we have data in Siglent format + mode = MODE_SIGLENT; + f_close(&fil); + } } usb_init(usb_rx); @@ -150,6 +167,10 @@ static void defaultTask(void* ptr) { } int main(void) { +#ifdef ENABLE_UART + Log_Init(); + LOG_INFO("Start"); +#endif spi_init(spi0, 20000 * 1000); gpio_set_function(FLASH_CLK_PIN, GPIO_FUNC_SPI); gpio_set_function(FLASH_MOSI_PIN, GPIO_FUNC_SPI); diff --git a/Software/LibreCAL/src/main.h b/Software/LibreCAL/src/main.h new file mode 100644 index 0000000..bef6b68 --- /dev/null +++ b/Software/LibreCAL/src/main.h @@ -0,0 +1,16 @@ +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +typedef enum { + MODE_DEFAULT, + MODE_SIGLENT, +} ecal_mode_t; + +ecal_mode_t getMode(); + +#ifdef __cplusplus +} +#endif diff --git a/Software/Scripts/convert_siglent.py b/Software/Scripts/convert_siglent.py index 5e92407..7a1e241 100755 --- a/Software/Scripts/convert_siglent.py +++ b/Software/Scripts/convert_siglent.py @@ -1,24 +1,17 @@ #!/usr/bin/env python3 """ - Converts LibreCAL Touchstone files to a format that a Siglent VNA can use. - Usage: - $ python3 convert_siglent.py /Volumes/LIBRECAL_R /Volumes/LIBRECAL_RW - (or your paths or drive letters, as appropriate) - This will create a "siglent" directory in your LIBRECAL_RW volume. Then, plug in your LibreCAL while holding down the FUNCTION button, which will activate Siglent eCal compatibility mode, and calibrate your VNA as if you were using a SEM5000A (or other Siglent eCal). - Since I only have a two port VNA, I have only tested calibrating with port 1 and port 2 on my LibreCAL connected to SVA1032X ports 1 and 2. If you have a fancier VNA, please let me know if it works. - """ __author__ = "Joshua Wise" @@ -53,11 +46,15 @@ def say_open(name, *args): with say_open(indir / "info.txt", "r") as inf: for l in inf.readlines(): outf.write(f"! {l.strip()}\n") - outf.write("#HZ,A,B,C,D,T_AB,T_AC,T_AD,T_BC,T_BD,T_CD\n") - + outf.write("#HZ,A,B,C,D,T_AB,T_AC,T_AD,T_BC,T_BD,T_CD,CF_AB,CF_AC,CF_AD,CF_BC,CF_BD,CF_CD\n") + + # Some early LibreCALs have a truncated P34_THROUGH.s2p file. The siglent format only supports + # identical number of points for all standards. Keep track of shortest parameter list and truncate + # all parameters to that value below + shortest_axis = 9999999 freqs = [] # will get populated below axes = [freqs] - + for port in ["1", "2", "3", "4"]: for col in "OPEN", "SHORT", "LOAD": with say_open(indir / f"P{port}_{col}.s1p", "r") as inf: @@ -70,13 +67,15 @@ def say_open(name, *args): freqs.append(freq * 1e9) ax_r.append(r) ax_i.append(i) + if len(ax_r) < shortest_axis: + shortest_axis = len(ax_r) axes.append(ax_r) axes.append(ax_i) freqs = [] # hope they're all the same! axes.append([0 for _ in axes[0]]) axes.append([0 for _ in axes[0]]) - for port in ["12", "13", "14", "23", "24", "34"]: + for port in ["12", "13", "14", "23", "24", "34"] * 2: with say_open(indir / f"P{port}_THROUGH.s2p", "r") as inf: snps = [ [] for _ in range(8) ] for l in inf.readlines(): @@ -85,9 +84,15 @@ def say_open(name, *args): snp_line = [float(x) for x in l.split(" ")][1:] for k,v in enumerate([float(x) for x in l.split(" ")][1:]): snps[k].append(v) + if len(snps[0]) < shortest_axis: + shortest_axis = len(snps[0]) for l in snps: axes.append(l) + # Truncate all axes to the same length + for i in range(len(axes)): + axes[i] = axes[i][:shortest_axis] + ar = np.vstack(axes, dtype=np.float64).transpose() print(f"compressing {ar.shape[0]} points with {ar.shape[1]} columns") np.savetxt(outf, ar, delimiter=",") @@ -100,14 +105,14 @@ def say_open(name, *args): VENDOR = "LibreCAL" PRODUCT = "LibreCAL" SERIAL = info['Serial'] -BYTE_0x4E = 0 # What are these? +BYTE_0x4E = 4 # Not sure but best guess is that these bytes represent the number of ports on the eCal BYTE_0x4F = 0 header = struct.pack("30s16s16s16sBB64s", b"", VENDOR.encode(), PRODUCT.encode(), SERIAL.encode(), BYTE_0x4E, BYTE_0x4F, b"") header += f"""Connector:SMA Module:Factory Desc:{ziphash} -Frequency:9000,8500000000,1201 +Frequency:{str(int(axes[0][0]))},{str(int(axes[0][-1]))},{str(len(axes[0]))} Data:0,{len(zipbuf.getvalue())},{ziphash} Date:{strftime('%Y-%m-%d', caldate)} """.encode() From 47724f44f0fbf458174eb583d87bd75a4b4ba15f Mon Sep 17 00:00:00 2001 From: Joshua Wise Date: Fri, 30 May 2025 16:35:50 -0700 Subject: [PATCH 07/10] scripts/convert_siglent: make some Pythonic cleanups Signed-off-by: Joshua Wise --- Software/Scripts/convert_siglent.py | 32 +++++++++++++++-------------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/Software/Scripts/convert_siglent.py b/Software/Scripts/convert_siglent.py index 7a1e241..9a81a34 100755 --- a/Software/Scripts/convert_siglent.py +++ b/Software/Scripts/convert_siglent.py @@ -2,16 +2,19 @@ """ Converts LibreCAL Touchstone files to a format that a Siglent VNA can use. + Usage: + $ python3 convert_siglent.py /Volumes/LIBRECAL_R /Volumes/LIBRECAL_RW + (or your paths or drive letters, as appropriate) + This will create a "siglent" directory in your LIBRECAL_RW volume. Then, plug in your LibreCAL while holding down the FUNCTION button, which will activate Siglent eCal compatibility mode, and calibrate your VNA as if you were using a SEM5000A (or other Siglent eCal). -Since I only have a two port VNA, I have only tested calibrating with port 1 -and port 2 on my LibreCAL connected to SVA1032X ports 1 and 2. If you have -a fancier VNA, please let me know if it works. + +Tested on SVA1032X and SNA5000A. """ __author__ = "Joshua Wise" @@ -48,10 +51,6 @@ def say_open(name, *args): outf.write(f"! {l.strip()}\n") outf.write("#HZ,A,B,C,D,T_AB,T_AC,T_AD,T_BC,T_BD,T_CD,CF_AB,CF_AC,CF_AD,CF_BC,CF_BD,CF_CD\n") - # Some early LibreCALs have a truncated P34_THROUGH.s2p file. The siglent format only supports - # identical number of points for all standards. Keep track of shortest parameter list and truncate - # all parameters to that value below - shortest_axis = 9999999 freqs = [] # will get populated below axes = [freqs] @@ -67,14 +66,14 @@ def say_open(name, *args): freqs.append(freq * 1e9) ax_r.append(r) ax_i.append(i) - if len(ax_r) < shortest_axis: - shortest_axis = len(ax_r) axes.append(ax_r) axes.append(ax_i) freqs = [] # hope they're all the same! axes.append([0 for _ in axes[0]]) axes.append([0 for _ in axes[0]]) + # We don't have an actual confidence check thru standard on LibreCAL, so + # we use the THROUGH standard for that. for port in ["12", "13", "14", "23", "24", "34"] * 2: with say_open(indir / f"P{port}_THROUGH.s2p", "r") as inf: snps = [ [] for _ in range(8) ] @@ -84,12 +83,15 @@ def say_open(name, *args): snp_line = [float(x) for x in l.split(" ")][1:] for k,v in enumerate([float(x) for x in l.split(" ")][1:]): snps[k].append(v) - if len(snps[0]) < shortest_axis: - shortest_axis = len(snps[0]) for l in snps: axes.append(l) - # Truncate all axes to the same length + # Some early LibreCALs have a truncated P34_THROUGH.s2p file. The + # Siglent format only supports identical number of points for all + # standards. Keep track of shortest parameter list and truncate all + # parameters to that value. + shortest_axis = min(len(ax) for ax in axes) + for i in range(len(axes)): axes[i] = axes[i][:shortest_axis] @@ -102,7 +104,7 @@ def say_open(name, *args): caldate = gmtime((indir / "P1_OPEN.s1p").stat().st_ctime) info = { k: v for k,v in (line.split(": ") for line in open(indir / "info.txt", "r").read().split("\n")) } -VENDOR = "LibreCAL" +VENDOR = "LibreVNA" PRODUCT = "LibreCAL" SERIAL = info['Serial'] BYTE_0x4E = 4 # Not sure but best guess is that these bytes represent the number of ports on the eCal @@ -112,7 +114,7 @@ def say_open(name, *args): header += f"""Connector:SMA Module:Factory Desc:{ziphash} -Frequency:{str(int(axes[0][0]))},{str(int(axes[0][-1]))},{str(len(axes[0]))} +Frequency:{int(axes[0][0])},{int(axes[0][-1])},{len(axes[0])} Data:0,{len(zipbuf.getvalue())},{ziphash} Date:{strftime('%Y-%m-%d', caldate)} """.encode() @@ -126,7 +128,7 @@ def say_open(name, *args): (outdir / "siglent").mkdir(exist_ok = True) zipname = outdir / "siglent/data0.zip" -print(f"writing {zipname}", file=sys.stderr) +print(f"writing {zipname} ({len(zipbuf.getvalue())} bytes)", file=sys.stderr) with open(zipname, "wb") as f: f.write(zipbuf.getvalue()) From daf7da188c80f52a30d5562c65fbc9170eb4e5de Mon Sep 17 00:00:00 2001 From: Joshua Wise Date: Fri, 30 May 2025 16:36:58 -0700 Subject: [PATCH 08/10] sw/main: be more specific in the name of getUsbMode() Signed-off-by: Joshua Wise --- Software/LibreCAL/src/USB/usb_descriptors.c | 4 ++-- Software/LibreCAL/src/UserInterface.cpp | 2 +- Software/LibreCAL/src/main.cpp | 4 ++-- Software/LibreCAL/src/main.h | 4 ++-- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/Software/LibreCAL/src/USB/usb_descriptors.c b/Software/LibreCAL/src/USB/usb_descriptors.c index c60df39..0508bdd 100644 --- a/Software/LibreCAL/src/USB/usb_descriptors.c +++ b/Software/LibreCAL/src/USB/usb_descriptors.c @@ -87,7 +87,7 @@ tusb_desc_device_t const desc_device_siglent = // Application return pointer to descriptor uint8_t const * tud_descriptor_device_cb(void) { - switch(getMode()) { + switch(getUsbMode()) { case MODE_DEFAULT: return (uint8_t const *) &desc_device_default; case MODE_SIGLENT: return (uint8_t const *) &desc_device_siglent; } @@ -171,7 +171,7 @@ uint8_t const desc_configuration_siglent[] = uint8_t const * tud_descriptor_configuration_cb(uint8_t index) { (void) index; // for multiple configurations - switch(getMode()) { + switch(getUsbMode()) { case MODE_DEFAULT: return desc_configuration_default; case MODE_SIGLENT: return desc_configuration_siglent; } diff --git a/Software/LibreCAL/src/UserInterface.cpp b/Software/LibreCAL/src/UserInterface.cpp index fa1aac4..b3d1986 100644 --- a/Software/LibreCAL/src/UserInterface.cpp +++ b/Software/LibreCAL/src/UserInterface.cpp @@ -103,7 +103,7 @@ void Task(void*) { // update LEDs // wait/ready are solid on when in default mode and blink when in any other mode - bool on = (getMode() == MODE_DEFAULT) || (xTaskGetTickCount() % ledBlinkPeriod > ledBlinkOnTime); + bool on = (getUsbMode() == MODE_DEFAULT) || (xTaskGetTickCount() % ledBlinkPeriod > ledBlinkOnTime); if(Heater::IsStable()) { setLED(LED::WAIT, false); setLED(LED::READY, on); diff --git a/Software/LibreCAL/src/main.cpp b/Software/LibreCAL/src/main.cpp index be6795c..6caa033 100644 --- a/Software/LibreCAL/src/main.cpp +++ b/Software/LibreCAL/src/main.cpp @@ -64,9 +64,9 @@ static xTaskHandle handle; Flash flash(spi0, FLASH_CLK_PIN, FLASH_MOSI_PIN, FLASH_MISO_PIN, FLASH_CS_PIN); -static ecal_mode_t mode = MODE_DEFAULT; +static ecal_usb_mode_t mode = MODE_DEFAULT; -ecal_mode_t getMode() { +ecal_usb_mode_t getUsbMode() { return mode; } diff --git a/Software/LibreCAL/src/main.h b/Software/LibreCAL/src/main.h index bef6b68..7175b99 100644 --- a/Software/LibreCAL/src/main.h +++ b/Software/LibreCAL/src/main.h @@ -7,9 +7,9 @@ extern "C" { typedef enum { MODE_DEFAULT, MODE_SIGLENT, -} ecal_mode_t; +} ecal_usb_mode_t; -ecal_mode_t getMode(); +ecal_usb_mode_t getUsbMode(); #ifdef __cplusplus } From 63d5a4c3ae8d07fdb6e07e15dfb2499ffe181a3a Mon Sep 17 00:00:00 2001 From: Joshua Wise Date: Fri, 30 May 2025 16:37:58 -0700 Subject: [PATCH 09/10] sw/siglent_tmc: add support for ATT mode; C style cleanups Signed-off-by: Joshua Wise --- Software/LibreCAL/src/USB/siglent_tmc.cpp | 70 +++++++---------------- 1 file changed, 21 insertions(+), 49 deletions(-) diff --git a/Software/LibreCAL/src/USB/siglent_tmc.cpp b/Software/LibreCAL/src/USB/siglent_tmc.cpp index aa6e933..ff10eb4 100644 --- a/Software/LibreCAL/src/USB/siglent_tmc.cpp +++ b/Software/LibreCAL/src/USB/siglent_tmc.cpp @@ -105,52 +105,33 @@ extern "C" bool tud_usbtmc_msg_data_cb(void *data, size_t len, bool transfer_com ibuf[ibuf_len] = 0; LOG_DEBUG("USB TMC: %s", ibuf); - /* different Siglent VNAs seem to use different cases for the commands, force everything to upper case */ - for(size_t i=0;i Date: Fri, 30 May 2025 16:40:37 -0700 Subject: [PATCH 10/10] sw/usb_descriptors: support CDC in Siglent mode to make it possible to kick the device into bootloader mode without pressing buttons Signed-off-by: Joshua Wise --- Software/LibreCAL/src/USB/usb_descriptors.c | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/Software/LibreCAL/src/USB/usb_descriptors.c b/Software/LibreCAL/src/USB/usb_descriptors.c index 0508bdd..78e319c 100644 --- a/Software/LibreCAL/src/USB/usb_descriptors.c +++ b/Software/LibreCAL/src/USB/usb_descriptors.c @@ -119,12 +119,14 @@ enum // Siglent mode enum { - ITF_SIGLENT_TMC = 0, - ITF_SGILENT_NUM_TOTAL + ITF_SIGLENT_NUM_CDC = 0, + ITF_SIGLENT_NUM_CDC_DATA, + ITF_SIGLENT_NUM_TMC, + ITF_SIGLENT_NUM_TOTAL }; // Endpoints for Siglent mode -#define EPNUM_TMC_IN 2 -#define EPNUM_TMC_OUT 2 +#define EPNUM_TMC_IN 3 +#define EPNUM_TMC_OUT 3 #define CONFIG_TOTAL_LEN (TUD_CONFIG_DESC_LEN + TUD_CDC_DESC_LEN + TUD_VENDOR_DESC_LEN + TUD_MSC_DESC_LEN) @@ -148,20 +150,20 @@ uint8_t const desc_configuration_default[] = * MSC interface. */ -#define CONFIG_TOTAL_LEN_SIGLENT (TUD_CONFIG_DESC_LEN + TUD_USBTMC_IF_DESCRIPTOR_LEN + TUD_USBTMC_BULK_DESCRIPTORS_LEN) +#define CONFIG_TOTAL_LEN_SIGLENT (TUD_CONFIG_DESC_LEN + TUD_CDC_DESC_LEN + TUD_USBTMC_IF_DESCRIPTOR_LEN + TUD_USBTMC_BULK_DESCRIPTORS_LEN) uint8_t const desc_configuration_siglent[] = { // Config number, interface count, string index, total length, attribute, power in mA - TUD_CONFIG_DESCRIPTOR(1, ITF_SGILENT_NUM_TOTAL, 0, CONFIG_TOTAL_LEN_SIGLENT, 0x00, 100), + TUD_CONFIG_DESCRIPTOR(1, ITF_SIGLENT_NUM_TOTAL, 0, CONFIG_TOTAL_LEN_SIGLENT, 0x00, 100), -// // Interface number, string index, EP notification address and size, EP data address (out, in) and size. -// TUD_CDC_DESCRIPTOR(ITF_NUM_CDC, 4, 0x81, 8, EPNUM_CDC_OUT, 0x80 | EPNUM_CDC_IN, TUD_OPT_HIGH_SPEED ? 512 : 64), + // Interface number, string index, EP notification address and size, EP data address (out, in) and size. + TUD_CDC_DESCRIPTOR(ITF_SIGLENT_NUM_CDC, 4, 0x81, 8, EPNUM_CDC_OUT, 0x80 | EPNUM_CDC_IN, TUD_OPT_HIGH_SPEED ? 512 : 64), // // // Interface number, string index, EP Out & IN address, EP size // TUD_VENDOR_DESCRIPTOR(ITF_NUM_VENDOR, 5, EPNUM_VENDOR_OUT, 0x80 | EPNUM_VENDOR_IN, TUD_OPT_HIGH_SPEED ? 512 : 64), - TUD_USBTMC_IF_DESCRIPTOR(ITF_SIGLENT_TMC, /* _bNumEndpoints = */ 2u, /*_stridx = */ 7u, 0 /* no subclass */), + TUD_USBTMC_IF_DESCRIPTOR(ITF_SIGLENT_NUM_TMC, /* _bNumEndpoints = */ 2u, /*_stridx = */ 7u, 0 /* no subclass */), TUD_USBTMC_BULK_DESCRIPTORS(/* OUT = */ EPNUM_TMC_OUT, /* IN = */ 0x80 | EPNUM_TMC_IN, /* packet size = */ 64), };