<?xml version="1.0" encoding="UTF-8" ?>
<?xml-stylesheet type="text/xsl" href="https://community.element14.com/cfs-file/__key/system/syndication/rss.xsl" media="screen"?><rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/" xmlns:wfw="http://wellformedweb.org/CommentAPI/"><channel><title>Embedded and Microcontrollers</title><link>https://community.element14.com/technologies/embedded/</link><description>Embedded systems programming, design and products; also microcontrollers, programmable logic, memory, DSP and controllers. </description><dc:language>en-US</dc:language><generator>Telligent Community 12</generator><item><title /><link>https://community.element14.com/technologies/embedded/b/blog/posts/using-webusb-to-facilitate-functional-circuit-test-data-capture?CommentId=ed7584da-6af4-4b79-812a-e2223c7116b6</link><pubDate>Thu, 25 Jun 2026 23:42:00 GMT</pubDate><guid isPermaLink="false">93d5dcb4-84c2-446f-b2cb-99731719e767:ed7584da-6af4-4b79-812a-e2223c7116b6</guid><dc:creator>kmikemoo</dc:creator><description>Great post! Awesome application!</description></item><item><title /><link>https://community.element14.com/technologies/embedded/b/blog/posts/using-webusb-to-facilitate-functional-circuit-test-data-capture?CommentId=d73abd28-1f04-4214-8261-ea11285980fb</link><pubDate>Thu, 25 Jun 2026 21:50:00 GMT</pubDate><guid isPermaLink="false">93d5dcb4-84c2-446f-b2cb-99731719e767:d73abd28-1f04-4214-8261-ea11285980fb</guid><dc:creator>BigG</dc:creator><description>Thanks Shabaz. Oh yes, I forgot to mention that. That would be very handy for field trials/tests.</description></item><item><title /><link>https://community.element14.com/technologies/embedded/b/blog/posts/using-webusb-to-facilitate-functional-circuit-test-data-capture?CommentId=26663ba9-48b2-4b87-969a-ae2e44b7ca63</link><pubDate>Thu, 25 Jun 2026 21:48:00 GMT</pubDate><guid isPermaLink="false">93d5dcb4-84c2-446f-b2cb-99731719e767:26663ba9-48b2-4b87-969a-ae2e44b7ca63</guid><dc:creator>BigG</dc:creator><description>Thanks</description></item><item><title /><link>https://community.element14.com/technologies/embedded/b/blog/posts/using-webusb-to-facilitate-functional-circuit-test-data-capture?CommentId=1840a526-4986-4021-9f43-479773360f66</link><pubDate>Thu, 25 Jun 2026 20:03:00 GMT</pubDate><guid isPermaLink="false">93d5dcb4-84c2-446f-b2cb-99731719e767:1840a526-4986-4021-9f43-479773360f66</guid><dc:creator>DAB</dc:creator><description>Very good post.</description></item><item><title /><link>https://community.element14.com/technologies/embedded/b/blog/posts/using-webusb-to-facilitate-functional-circuit-test-data-capture?CommentId=6e532e70-4444-407b-bf3b-8d5bbeb6ff62</link><pubDate>Wed, 24 Jun 2026 16:26:00 GMT</pubDate><guid isPermaLink="false">93d5dcb4-84c2-446f-b2cb-99731719e767:6e532e70-4444-407b-bf3b-8d5bbeb6ff62</guid><dc:creator>shabaz</dc:creator><description>Nice work! Also, bonus, WebUSB is supported on Android Chrome, hence it&amp;#39;s a simple way to attach Pi Pico to a mobile, and the HTML/JavaScript will run just fine.</description></item><item><title>Blog Post: Using WebUSB to facilitate Functional Circuit Test (FCT) Data Capture</title><link>https://community.element14.com/technologies/embedded/b/blog/posts/using-webusb-to-facilitate-functional-circuit-test-data-capture</link><pubDate>Wed, 24 Jun 2026 15:40:00 GMT</pubDate><guid isPermaLink="false">93d5dcb4-84c2-446f-b2cb-99731719e767:f2ce516f-8ecd-4160-8ce1-c8da9353814d</guid><dc:creator>BigG</dc:creator><description>Table of Contents 1. Introduction 2. Using existing USB to TTL Converters with a Python desktop application 3. Using WebUSB and a browser webpage 3.1 Overview of WebUSB 3.1.1 Embedded Device / Firmware Perspective 3.1.2 Web API Perspective 3.2. Practical Application (Functional Circuit Test Interface) 3.2.1 Embedded Device Firmware 3.2.2 Web application (Javascript) 3.2.3 Demo 4. Summary 1. Introduction Transitioning from a completed prototype to a reproducible, high quality manufacturable product requires thorough validation that the assembled PCB’s coming off the production line do what they are supposed to do. This is known as a Functional Circuit Test (FCT). However, orchestrating such tests in a controlled fashion, capturing device responses and observable outcomes, plus abstracting human operator inputs can be quite a challenge and is often a hefty project in its own right, especially if trying to automate. This is not a new problem and there are existing solutions out there from custom open-source desktop applications through to more powerful industry based solutions such as labVIEW. No doubt, the DIY route will be considered, especially at the early stages of the product lifecycle. This blog captures the early stage experience, from developing a custom desktop application in Python, which uses the standard USB to TTL converter, through to developing a web browser application which uses WebUSB. The blog is about hardware and software interface issues. It is not about FCT methodology. 2. Using existing USB to TTL Converters with a Python desktop application community.element14.com/.../5633.USB_5F00_TTL.mp4 The above video simply shows the UART debug log from an existing PSRAM example for the Metro RP2350 board. Here I’m mimicking the scenario where the board is powered separately (i.e. not from a USB cable) and you are accessing the debug serial port to ascertain if the board’s PSRAM is functioning or not. Note that I do not care about the actual values. Here I am merely concerned about efficient data capture. As you can see, the above methodology works fine if this evaluation was a once off for a single board. The challenge comes when you have to repeat this test multiple times for hundreds of boards, especially if you want to record the multiple pass/fail results. The data storage could simply be on the local computer or it might need to be pushed to a remote server somewhere for real-time or off-line review. Either way, you are now pushed into the realm of desktop application developer or you purchase commercial software for this purpose. To solve this as a DIY solution, one method would be to write up a Python script to capture the data on your computer. In the code below, I went a step further and published data to an online MQTT broker for offline analysis (note this script was done by AI): import serial import sys import paho.mqtt.client as mqtt # --- Configuration --- # Serial Settings PORT = &amp;#39;/dev/ttyUSB0&amp;#39; BAUDRATE = 115200 TIMEOUT = 1 # MQTT Settings (Updated for Shiftr.io) MQTT_BROKER = &amp;quot;--enter_instance_identifier_here---.cloud.shiftr.io&amp;quot; # Hostname only (no mqtt://) MQTT_PORT = 1883 MQTT_USER = &amp;quot;--- enter authorised user name here ---&amp;quot; MQTT_PASS = &amp;quot;--- enter token here ----&amp;quot; MQTT_TOPIC = &amp;quot;done_counter&amp;quot; # Shiftr.io visualizes topics as paths # KEYWORD TO SEARCH FOR KEYWORD = &amp;quot;done&amp;quot; # --------------------- def on_connect(client, userdata, flags, rc, properties=None): &amp;quot;&amp;quot;&amp;quot;Callback for when the MQTT client connects to the broker.&amp;quot;&amp;quot;&amp;quot; if rc == 0: print(f&amp;quot;--- Successfully connected to Shiftr.io ({MQTT_BROKER}) ---&amp;quot;) else: print(f&amp;quot;--- MQTT Connection failed with code {rc} ---&amp;quot;) def main(): # 1. Initialize and configure the MQTT Client mqtt_client = mqtt.Client(callback_api_version=mqtt.CallbackAPIVersion.VERSION2) mqtt_client.on_connect = on_connect # Apply your username and password credentials mqtt_client.username_pw_set(username=MQTT_USER, password=MQTT_PASS) try: print(f&amp;quot;--- Connecting to Shiftr.io Broker: {MQTT_BROKER}... ---&amp;quot;) mqtt_client.connect(MQTT_BROKER, MQTT_PORT, 60) # Start the background network loop mqtt_client.loop_start() except Exception as e: print(f&amp;quot;[MQTT Error]: Could not connect to broker. {e}&amp;quot;, file=sys.stderr) sys.exit(1) # 2. Initialize Serial Port &amp;amp; Counter done_counter = 0 print(f&amp;quot;--- Attempting to connect to {PORT} at {BAUDRATE} baud ---&amp;quot;) try: with serial.Serial(PORT, BAUDRATE, timeout=TIMEOUT) as ser: print(f&amp;quot;--- Successfully connected to {PORT} ---&amp;quot;) print(f&amp;quot;--- Watching for keyword: &amp;#39;{KEYWORD}&amp;#39; ---&amp;quot;) print(&amp;quot;--- Press Ctrl+C to stop ---\n&amp;quot;) ser.reset_input_buffer() while True: if ser.in_waiting &amp;gt; 0: line = ser.readline() try: decoded_line = line.decode(&amp;#39;utf-8&amp;#39;, errors=&amp;#39;ignore&amp;#39;).strip() print(f&amp;quot;[Serial In]: {decoded_line}&amp;quot;) # Check if keyword is in the line if KEYWORD in decoded_line.lower(): done_counter += 1 print(f&amp;quot; -&amp;gt; Found &amp;#39;{KEYWORD}&amp;#39;! Current Count: {done_counter}&amp;quot;) # Publish the counter value result = mqtt_client.publish(MQTT_TOPIC, payload=str(done_counter), qos=1) if result.rc == mqtt.MQTT_ERR_SUCCESS: print(f&amp;quot; -&amp;gt; Published &amp;#39;{done_counter}&amp;#39; to &amp;#39;{MQTT_TOPIC}&amp;#39;&amp;quot;) else: print(f&amp;quot; -&amp;gt; [MQTT Pub Error]: Failed to queue message.&amp;quot;) except Exception as decode_error: print(f&amp;quot;[Decode Error]: {decode_error}&amp;quot;, file=sys.stderr) except serial.SerialException as e: print(f&amp;quot;\n[Serial Error]: Could not open port {PORT}. Details: {e}&amp;quot;, file=sys.stderr) except KeyboardInterrupt: print(&amp;quot;\n--- Script stopped by user. Cleaning up... ---&amp;quot;) finally: # 3. Clean up connections safely print(&amp;quot;--- Disconnecting MQTT Client... ---&amp;quot;) mqtt_client.loop_stop() mqtt_client.disconnect() print(&amp;quot;--- Exited safely. ---&amp;quot;) if __name__ == &amp;#39;__main__&amp;#39;: main() So, thanks to Python (and AI!) this option has become much simpler that having to develop these type of applications in C / C++ / C# etc. Still, the challenge with this approach is that using this code is not very flexible (in Linux, USB interface can be ACMx or USBx) or portable, especially if moving from say a LinuxOS computer to a WindowsOS computer, for example, and you need access to the connected serial comms port. This is where WebUSB can help. 3. Using WebUSB and a browser webpage 3.1 Overview of WebUSB WebUSB is a web platform API that enables direct, low-level communication between browser-based JavaScript applications and USB devices. It allows web applications to interact with custom or vendor-specific USB hardware without requiring native drivers, browser extensions, or platform-specific software. It was introduced in 2017 and is maintained as a W3C Community Group specification. It has a similar status as Web Bluetooth, which is also not an official, universal web standard. 3.1.1 Embedded Device / Firmware Perspective Every USB device identifies its function to the computer using an industry-standard identifier called a Class. For example, a keyboard uses the Human Interface Device (HID) class, while a flash drive uses the Mass Storage class. When the operating system sees these codes, it automatically locks down the device using its own built-in drivers. If the OS claims the device, the browser can&amp;#39;t touch it. To bypass this, WebUSB relies on a built-in loophole: the Vendor-Specific Class . By setting your device&amp;#39;s interface class to Vendor-Specific, you are telling the operating system: &amp;quot;There is no standard driver for this hardware. Hands off.&amp;quot; Because the OS leaves the connection open, the browser is free to step in, claim the raw USB endpoints, and handle the communication directly via JavaScript. In other words, on the device side, WebUSB does not define a new USB class but instead provides a discovery and announcement mechanism layered on standard USB transfers. Thus the embedded device firmware needs to handle two main things: Discovery (telling the OS/browser it supports WebUSB) and Communication (handling the actual data). The Discovery Mechanism (BOS &amp;amp; Descriptors): When you plug the device in, the host operating system queries the Binary Object Store (BOS). To support WebUSB, your firmware must return a specific Platform Capability Descriptor inside this store (think of this as a cryptographic handshake and a billboard): The GUID : Your BOS descriptor must include a specific, hardcoded WebUSB identifier ( 3408b638-09a9-47a0-8bfd-a0768815b665 ). This tells the browser, &amp;quot;Yes, I speak WebUSB.&amp;quot; The Landing Page URL : The descriptor also points to a URL descriptor. When plugged in, the OS can parse this and throw a native desktop notification (e.g., &amp;quot;Click here to connect to this device&amp;quot;), sending the user straight to your web app. Control Requests &amp;amp; Data Transfers: Once discovered, the browser communicates with your firmware using two types of USB requests: Control Transfers (Setup Stage) : The browser sends vendor-specific control requests (using custom bRequest codes you define) to read the landing page URL or request landing page landing landing capabilities. Bulk or Interrupt Transfers (Data Stage) : Once the browser claims the interface, the wire protocol is entirely up to you. WebUSB doesn&amp;#39;t care if you stream raw JSON, packed binary structs, or AT commands over your bulk IN/OUT endpoints. In reality, Modern USB device stacks such as TinyUSB, simplify adoption, as it offers cross-MCU support (e.g., nRF52, SAMD, STM32), while Zephyr RTOS includes a dedicated WebUSB sample (board-specific due to peripheral variations) - the demonstration below uses Zephyr RTOS. The device protocol itself remains application-defined over the raw transfers. 3.1.2 Web API Perspective From the browser side, WebUSB exposes the USB protocol stack (control, bulk, interrupt, and isochronous transfers) to sandboxed JavaScript running in secure contexts (HTTPS). Once a USBDevice is obtained (i.e. paired and connected), applications can open the device, select configurations, claim interfaces exclusively, and perform raw USB data transfers. Unfortunately, there’s no support for it in Firefox and Safari, and is currently only supported in Chromium-based browsers (such as Chrome, Edge, etc.). The best way to explain is by way of example. 3.2. Practical Application (Functional Circuit Test Interface) 3.2.1 Embedded Device Firmware If your PCB undergoing a FCT includes a microcontroller then it&amp;#39;s quite likely that you&amp;#39;ll use test firmware to handle various test scenarios. In my demonstration below, I used an Adafruit Metro RP2350 as my DUT and the test firmware was developed on the Arduino IDE. In my Arduino Code I simply used keywords to control the different tests. The two test cases included were copies of existing Arduino library examples. /* PSRAM Test This section of code tests the onboard ram of RP2350 based boards with external PSRAM. This example code is in the public domain. */ // NeoPixel Ring simple sketch (c) 2013 Shae Erisson // Released under the GPLv3 license to match the rest of the // Adafruit NeoPixel library #if !defined(RP2350_PSRAM_CS) void setup() { Serial1.begin(115200); Serial1.println(&amp;quot;This example needs an RP2350 with PSRAM attached&amp;quot;); } void loop() { } #else #include // Device ID #define DEVICEID &amp;quot;AdafruitMetro2350-20260622-17h00m00&amp;quot; // NeoPixels pin and number pixels #define PIN 25 #define NUMPIXELS 1 // Time (in milliseconds) to pause between pixels #define DELAYVAL 500 #define CHUNK_SIZE 131072 #define PMALLOCSIZE (CHUNK_SIZE * 13) Adafruit_NeoPixel pixels(NUMPIXELS, PIN, NEO_GRB + NEO_KHZ800); uint32_t t_now = 0L; uint8_t tmp[CHUNK_SIZE]; uint8_t mems[1024 * 1024 * 1] PSRAM; uint8_t *mem = mems; uint8_t *pmem = (uint8_t *)pmalloc(PMALLOCSIZE); String inputString = &amp;quot;&amp;quot;; // a String to hold incoming data bool stringComplete = false; // whether the string is complete int FunctionTestNo = 0; int ColCount = 0; bool debugging = false; bool test1delay = false; void setup() { // reserve 200 bytes for the inputString: inputString.reserve(128); Serial1.begin(115200); // INITIALIZE NeoPixel strip object (REQUIRED) pixels.begin(); pixels.clear(); // Set all pixel colors to &amp;#39;off&amp;#39; } void loop() { if (Serial1.available()) { delay(1); while (Serial1.available()) { // get the new byte: char inChar = (char)Serial1.read(); // add it to the inputString: if (inChar != &amp;#39;\r&amp;#39;) inputString += inChar; // if the incoming character is a newline, set a flag so the main loop can // do something about it: if (inChar == &amp;#39;\n&amp;#39;) { stringComplete = true; } } } // print the string when a newline arrives: if (stringComplete) { // check if the content matches keywords: if (inputString.startsWith(&amp;quot;deviceid&amp;quot;)) { Serial1.println(DEVICEID); Serial1.printf(&amp;quot;PSRAM Size: %d\r\n&amp;quot;, rp2040.getPSRAMSize()); } else if (inputString.startsWith(&amp;quot;test1&amp;quot;)) { FunctionTestNo = 1; } else if (inputString.startsWith(&amp;quot;test2&amp;quot;)) { FunctionTestNo = 2; } else if (inputString.startsWith(&amp;quot;debug&amp;quot;)) { debugging = !debugging; //toggle boolean flag } else if (inputString.startsWith(&amp;quot;stop&amp;quot;)) { for(int i=0; i 1000) { test1delay = false; t_now = 0; } } else { if ((millis() - t_now) &amp;gt; 3000) { mem = pmem; for (size_t m = 0; m DELAYVAL) { t_now = 0; } } } } #endif The focus of this blog is on the WebUSB Bridge Device. This code was developed using an existing Zephyr RTOS webUSB example. I modified this code to act as a bridging device. It was also tailored for a Seeed Studio Xiao RP2040 dev board. Here I used PIO to create a new UART1 interface. I kept the existing UART0 interface for debugging purposes. I typically start my Zephyr applications by devising the device tree for any hardware interfaces/peripherals. I this case I needed a new UART1 port (here you can use the generic &amp;quot;app.overlay&amp;quot; for this purpose): / { aliases { uart1 = &amp;amp;pio1_uart1; }; }; &amp;amp;pinctrl { pio1_uart1_default: pio1_uart1_default { rx_pins { pinmux = ; input-enable; bias-pull-up; }; tx_pins { pinmux = ; }; }; }; &amp;amp;pio1 { status = &amp;quot;okay&amp;quot;; pio1_uart1: uart1 { pinctrl-0 = ; pinctrl-names = &amp;quot;default&amp;quot;; compatible = &amp;quot;raspberrypi,pico-uart-pio&amp;quot;; current-speed = ; status = &amp;quot;okay&amp;quot;; }; }; &amp;amp;spi0 { status = &amp;quot;disabled&amp;quot;; }; The standard Zephyr RTOS only allows for PIO RX polling. This is not particularly suitable for my application, so I added in Interrupt functionality. This required a custom &amp;quot;Kconfig&amp;quot; to tell the compiler that I plan to use some custom definitions in my firmware. # Copyright (c) 2023 Nordic Semiconductor ASA # SPDX-License-Identifier: Apache-2.0 # Source common USB sample options used to initialize new experimental USB # device stack. The scope of these options is limited to USB samples in project # tree, you cannot use them in your own application. source &amp;quot;samples/subsys/usb/common/Kconfig.sample_usbd&amp;quot; config UART_RPI_PICO_PIO_INTERRUPT_DRIVEN bool &amp;quot;Raspberry Pi Pico PIO UART interrupt support&amp;quot; help Enable interrupt-driven API for UART PIO driver. source &amp;quot;Kconfig.zephyr&amp;quot; Then I needed to create my specific project config file (prj.conf). This is an essential requirement for all Zephyr projects. This is where you can modify the USB specifics, such as giving the USB device a user friendly name. CONFIG_USB_DEVICE_STACK_NEXT=y CONFIG_CDC_ACM_SERIAL_INITIALIZE_AT_BOOT=n CONFIG_LOG=y CONFIG_USBD_LOG_LEVEL_WRN=y CONFIG_UDC_DRIVER_LOG_LEVEL_WRN=y CONFIG_SAMPLE_USBD_PID=0x000A CONFIG_SAMPLE_USBD_20_EXTENSION_DESC=y CONFIG_SAMPLE_USBD_MANUFACTURER=&amp;quot;RPI PICO&amp;quot; CONFIG_SAMPLE_USBD_PRODUCT=&amp;quot;e14 USB FCT&amp;quot; CONFIG_UART_INTERRUPT_DRIVEN=y CONFIG_RING_BUFFER=y CONFIG_MAIN_STACK_SIZE=4096 CONFIG_SYSTEM_WORKQUEUE_STACK_SIZE=4096 CONFIG_HEAP_MEM_POOL_SIZE=16384 CONFIG_LOG_BUFFER_SIZE=8192 # Custom PIO UART Driver configs CONFIG_UART_RPI_PICO_PIO=n CONFIG_UART_RPI_PICO_PIO_INTERRUPT_DRIVEN=y CONFIG_DYNAMIC_INTERRUPTS=y Then it was time to modify the existing webUSB code: /* * Copyright (c) 2023-2024 Nordic Semiconductor ASA * * SPDX-License-Identifier: Apache-2.0 */ #include #include #include #include #include #include #include #include #include #include #include #include LOG_MODULE_REGISTER(main, LOG_LEVEL_INF); /* * There are three BOS descriptors used in the sample, a USB 2.0 EXTENSION from * the USB samples common code, a Microsoft OS 2.0 platform capability * descriptor, and a WebUSB platform capability descriptor. */ #include &amp;quot;webusb.h&amp;quot; #include &amp;quot;msosv2.h&amp;quot; #include &amp;quot;sfunc.h&amp;quot; const struct device *uart1 = DEVICE_DT_GET(DT_ALIAS(uart1)); /* Define a ring buffer to handle UART polling */ RING_BUF_DECLARE(uart_ringbuf, 4096); /* Semaphore to wake up the main loop when a newline is received */ K_SEM_DEFINE(uart_rx_sem, 0, K_SEM_MAX_LIMIT); static volatile uint32_t newlines = 0; /* Interrupt Service Routine (ISR) fired whenever the UART has data */ static void uart_rx_isr(const struct device *dev, void *user_data) { uint8_t c; if (!uart_irq_update(dev)) { return; } if (!uart_irq_rx_ready(dev)) { return; } while (uart_fifo_read(dev, &amp;amp;c, 1) == 1) { if (ring_buf_put(&amp;amp;uart_ringbuf, &amp;amp;c, 1) == 0) { LOG_WRN(&amp;quot;Ring buffer full, dropping byte&amp;quot;); } else if (c == &amp;#39;\n&amp;#39;) { newlines++; k_sem_give(&amp;amp;uart_rx_sem); } } } /* Helper to filter out bootloader and OS startup logs */ static bool is_boot_log(const uint8_t *buf, size_t len) { const char *prefixes[] = { &amp;quot;ESP-ROM:&amp;quot;, &amp;quot;Build:&amp;quot;, &amp;quot;rst:&amp;quot;, &amp;quot;SPIWP:&amp;quot;, &amp;quot;mode:&amp;quot;, &amp;quot;load:&amp;quot;, &amp;quot;SHA-256&amp;quot;, &amp;quot;Calculated:&amp;quot;, &amp;quot;Expected:&amp;quot;, &amp;quot;Attempting to boot&amp;quot;, &amp;quot;entry &amp;quot;, &amp;quot;I (&amp;quot;, &amp;quot;W (&amp;quot;, &amp;quot;*** Booting Zephyr OS&amp;quot; }; for (int i = 0; i type)); if (usbd_can_detect_vbus(usbd_ctx)) { if (msg-&amp;gt;type == USBD_MSG_VBUS_READY) { if (usbd_enable(usbd_ctx)) { LOG_ERR(&amp;quot;Failed to enable device support&amp;quot;); } } if (msg-&amp;gt;type == USBD_MSG_VBUS_REMOVED) { if (usbd_disable(usbd_ctx)) { LOG_ERR(&amp;quot;Failed to disable device support&amp;quot;); } } } } int main(void) { struct usbd_context *medlitest_usbd; int ret; if (!device_is_ready(uart1)) { LOG_ERR(&amp;quot;UART1 device not ready&amp;quot;); return -ENODEV; } sfunc_set_uart_dev(uart1); medlitest_usbd = sample_usbd_setup_device(msg_cb); if (medlitest_usbd == NULL) { LOG_ERR(&amp;quot;Failed to setup USB device&amp;quot;); return -ENODEV; } ret = usbd_add_descriptor(medlitest_usbd, &amp;amp;bos_vreq_msosv2); if (ret) { LOG_ERR(&amp;quot;Failed to add MSOSv2 capability descriptor&amp;quot;); return ret; } ret = usbd_add_descriptor(medlitest_usbd, &amp;amp;bos_vreq_webusb); if (ret) { LOG_ERR(&amp;quot;Failed to add WebUSB capability descriptor&amp;quot;); return ret; } ret = usbd_init(medlitest_usbd); if (ret) { LOG_ERR(&amp;quot;Failed to initialize device support&amp;quot;); return ret; } if (!usbd_can_detect_vbus(medlitest_usbd)) { ret = usbd_enable(medlitest_usbd); if (ret) { LOG_ERR(&amp;quot;Failed to enable device support&amp;quot;); return ret; } } /* Configure and enable the UART interrupt */ uart_irq_callback_user_data_set(uart1, uart_rx_isr, NULL); uart_irq_rx_enable(uart1); uint8_t c; uint8_t buf[512]; uint32_t len; /* Main processing loop */ while (1) { /* Put the main thread to sleep until the ISR signals a newline */ k_sem_take(&amp;amp;uart_rx_sem, K_FOREVER); /* Process queued data from the ring buffer */ while (newlines &amp;gt; 0) { len = 0; while (!ring_buf_is_empty(&amp;amp;uart_ringbuf) &amp;amp;&amp;amp; len 0) { if (is_boot_log(buf, len)) { LOG_DBG(&amp;quot;Filtered boot log: %.*s&amp;quot;, len, buf); } else { LOG_INF(&amp;quot;UART1 received %d bytes: %.*s&amp;quot;, len, len, buf); if (sfunc_send_to_usb(buf, len) != 0) { LOG_WRN(&amp;quot;USB buffer full, dropped %d bytes&amp;quot;, len); } } } } } } The other files were pretty much as per the existing example. The complete code base can be found on my Github repository: https://github.com/Gerriko/webUSB_bridge 3.2.2 Web application (Javascript) The web application consists of two parts. The html code (index.html), which makes the UI look pretty, and then the Javascript code, which handles the webUSB functionality. This code is also included in the Github repo. var serial = {}; let port = null; (function() { &amp;#39;use strict&amp;#39;; serial.getPorts = function() { return navigator.usb.getDevices().then(devices =&amp;gt; { return devices.map(device =&amp;gt; new serial.Port(device)); }); }; serial.requestPort = function() { const filters = [ { &amp;#39;vendorId&amp;#39;: 0x2fe3, &amp;#39;productId&amp;#39;: 0x0100 }, { &amp;#39;vendorId&amp;#39;: 0x2fe3, &amp;#39;productId&amp;#39;: 0x00a }, { &amp;#39;vendorId&amp;#39;: 0x8086, &amp;#39;productId&amp;#39;: 0xF8A1 }, ]; return navigator.usb.requestDevice({ &amp;#39;filters&amp;#39;: filters }).then( device =&amp;gt; new serial.Port(device) ); } serial.Port = function(device) { this.device_ = device; this.isDisconnecting = false; // Flag to trace requested disconnects this.interfaceNumber_ = 0; // Stored interface number to claim }; serial.Port.prototype.connect = function() { let readLoop = () =&amp;gt; { // DEBUG: Verify interfaces exist before reading if (!this.device_.configuration || this.device_.configuration.interfaces.length === 0) { if (this.onReceiveError) { this.onReceiveError(new Error(&amp;quot;No USB interfaces found on configuration.&amp;quot;)); } return; } const currentInterface = this.device_.configuration.interfaces.find(i =&amp;gt; i.interfaceNumber === this.interfaceNumber_); if (!currentInterface) { if (this.onReceiveError) { this.onReceiveError(new Error(`Interface #${this.interfaceNumber_} not found.`)); } return; } // Auto-detect the first IN endpoint on this interface, falling back to index 0 const readEp = currentInterface.alternate.endpoints.find(e =&amp;gt; e.direction === &amp;#39;in&amp;#39;) || currentInterface.alternate.endpoints[0]; if (!readEp) { if (this.onReceiveError) { this.onReceiveError(new Error(`No read endpoint found on interface #${this.interfaceNumber_}.`)); } return; } this.device_.transferIn(readEp.endpointNumber, 64).then(result =&amp;gt; { if (this.onReceive) { try { this.onReceive(result.data); } catch (e) { console.error(&amp;quot;Error in onReceive callback:&amp;quot;, e); if (this.onReceiveError) { this.onReceiveError(e); } } } readLoop(); }, error =&amp;gt; { if (this.onReceiveError) { this.onReceiveError(error); } }); }; return this.device_.open() .then(() =&amp;gt; { if (this.device_.configuration === null) { return this.device_.selectConfiguration(1); } }) .then(() =&amp;gt; { // AUTO-DETECT: Find interface that has BULK endpoints (typically CDC ACM Data interface) let targetInterfaceNumber = 0; const config = this.device_.configuration; if (config &amp;amp;&amp;amp; config.interfaces.length &amp;gt; 0) { for (const iface of config.interfaces) { const hasBulk = iface.alternate.endpoints.some(ep =&amp;gt; ep.type === &amp;#39;bulk&amp;#39;); if (hasBulk) { targetInterfaceNumber = iface.interfaceNumber; break; } } } this.interfaceNumber_ = targetInterfaceNumber; console.log(`[USB Auto-Detect] Claiming Interface #${this.interfaceNumber_}`); return this.device_.claimInterface(this.interfaceNumber_); }) .then(() =&amp;gt; { readLoop(); }); }; serial.Port.prototype.disconnect = function() { this.isDisconnecting = true; // Silences the cancellation transferIn error return this.device_.close(); }; serial.Port.prototype.send = function(data) { if (!this.device_.configuration || this.device_.configuration.interfaces.length === 0) { return Promise.reject(new Error(&amp;quot;Device has no configurations loaded.&amp;quot;)); } const currentInterface = this.device_.configuration.interfaces.find(i =&amp;gt; i.interfaceNumber === this.interfaceNumber_); if (!currentInterface) { return Promise.reject(new Error(`Active interface #${this.interfaceNumber_} not found.`)); } // Auto-detect the first OUT endpoint on this interface, falling back to index 1 const writeEp = currentInterface.alternate.endpoints.find(e =&amp;gt; e.direction === &amp;#39;out&amp;#39;) || currentInterface.alternate.endpoints[1]; if (!writeEp) { return Promise.reject(new Error(`No write endpoint found on claimed interface #${this.interfaceNumber_}.`)); } return this.device_.transferOut(writeEp.endpointNumber, data); }; })(); // Detailed USB Descriptor Debug Logger function debugUSBDevice(device, activeInterfaceNumber) { let log = `\n--- USB HARDWARE PROFILE &amp;amp; DEBUG INFO ---\n`; log += `[DEVICE] Vendor ID: 0x${device.vendorId.toString(16).toUpperCase().padStart(4, &amp;#39;0&amp;#39;)}\n`; log += `[DEVICE] Product ID: 0x${device.productId.toString(16).toUpperCase().padStart(4, &amp;#39;0&amp;#39;)}\n`; log += `[DEVICE] Class: ${device.deviceClass} | Subclass: ${device.deviceSubclass} | Protocol: ${device.deviceProtocol}\n`; log += `[DEVICE] Manufacturer: ${device.manufacturerName || &amp;#39;N/A&amp;#39;}\n`; log += `[DEVICE] Product: ${device.productName || &amp;#39;N/A&amp;#39;}\n`; log += `[DEVICE] Serial Number: ${device.serialNumber || &amp;#39;N/A&amp;#39;}\n`; if (device.configuration) { const config = device.configuration; log += `[CONFIG] Value: ${config.configurationValue} (Active)\n`; config.interfaces.forEach((iface) =&amp;gt; { const isClaimed = iface.interfaceNumber === activeInterfaceNumber; log += ` └─ Interface #${iface.interfaceNumber} (Claimed/Active: ${isClaimed})\n`; const alt = iface.alternate; log += ` ├─ Class: 0x${alt.interfaceClass.toString(16).toUpperCase().padStart(2, &amp;#39;0&amp;#39;)}\n`; log += ` ├─ Subclass: 0x${alt.interfaceSubclass.toString(16).toUpperCase().padStart(2, &amp;#39;0&amp;#39;)}\n`; log += ` └─ Endpoints (${alt.endpoints.length}):\n`; alt.endpoints.forEach((ep) =&amp;gt; { log += ` ├─ EP #${ep.endpointNumber} | Dir: ${ep.direction.toUpperCase()} | Type: ${ep.type.toUpperCase()} | Size: ${ep.packetSize}B\n`; }); }); // Log the endpoint verification const activeIface = config.interfaces.find(i =&amp;gt; i.interfaceNumber === activeInterfaceNumber) || config.interfaces[0]; if (activeIface) { const alt = activeIface.alternate; const readEp = alt.endpoints.find(e =&amp;gt; e.direction === &amp;#39;in&amp;#39;) || alt.endpoints[0]; const writeEp = alt.endpoints.find(e =&amp;gt; e.direction === &amp;#39;out&amp;#39;) || alt.endpoints[1]; log += `[SOFTWARE] Active Claimed Interface: #${activeInterfaceNumber}\n`; log += `[SOFTWARE] Active Read EP: ${readEp ? &amp;#39;#&amp;#39; + readEp.endpointNumber + &amp;#39; (&amp;#39; + readEp.direction.toUpperCase() + &amp;#39;)&amp;#39; : &amp;#39;None (Error!)&amp;#39;}\n`; log += `[SOFTWARE] Active Write EP: ${writeEp ? &amp;#39;#&amp;#39; + writeEp.endpointNumber + &amp;#39; (&amp;#39; + writeEp.direction.toUpperCase() + &amp;#39;)&amp;#39; : &amp;#39;None (Error!)&amp;#39;}\n`; } } else { log += `[CONFIG] No configuration currently selected.\n`; } log += `-----------------------------------------\n\n`; return log; } // Connection Handler function connect() { if (!port) return; // Reset intentional disconnect trace port.isDisconnecting = false; // FIX: Set listeners BEFORE connect to prevent race conditions on immediate incoming data port.onReceive = data =&amp;gt; { try { // Get format type const formatSelect = document.getElementById(&amp;#39;format-select&amp;#39;); const format = formatSelect ? formatSelect.value : &amp;#39;text&amp;#39;; let displayString = &amp;#39;&amp;#39;; // Safe conversion from DataView / ArrayBufferView to Uint8Array let uint8; if (data &amp;amp;&amp;amp; data.buffer) { uint8 = new Uint8Array(data.buffer, data.byteOffset, data.byteLength); } else { uint8 = new Uint8Array(data || []); } if (format === &amp;#39;hex&amp;#39;) { // Space-separated Hex representation for (let i = 0; i { // Ignore the cancellation transferIn error if we are intentionally disconnecting if (port &amp;amp;&amp;amp; port.isDisconnecting) { return; } console.error(&amp;quot;WebUSB Receive error:&amp;quot;, error); logToTerminal(`\n[SYSTEM ERROR] Connection lost: ${error.message || error}\n`); handleDisconnectUI(); }; logToTerminal(&amp;quot;[SYSTEM] Opening USB connection and claiming interface...\n&amp;quot;); port.connect().then(() =&amp;gt; { console.log(&amp;quot;Connected successfully!&amp;quot;); logToTerminal(&amp;quot;[SYSTEM] Connected successfully!\n&amp;quot;); // Print the USB configuration hardware descriptors const debugInfo = debugUSBDevice(port.device_, port.interfaceNumber_); logToTerminal(debugInfo); handleConnectUI(); }).catch(err =&amp;gt; { console.error(&amp;quot;Connection failed:&amp;quot;, err); logToTerminal(`[SYSTEM ERROR] Connection failed: ${err.message || err}\n`); handleDisconnectUI(); }); } function disconnect() { if (port) { port.isDisconnecting = true; // Signal we are intentionally shutting down the pipe logToTerminal(&amp;quot;\n[SYSTEM] Disconnecting from device...\n&amp;quot;); port.disconnect().then(() =&amp;gt; { logToTerminal(&amp;quot;[SYSTEM] Disconnected.\n&amp;quot;); handleDisconnectUI(); }).catch(err =&amp;gt; { console.error(&amp;quot;Disconnect error:&amp;quot;, err); logToTerminal(`[SYSTEM ERROR] Disconnect failed: ${err.message || err}\n`); handleDisconnectUI(); }); } } function send(string) { if (string.length === 0) return; console.log(&amp;quot;Sending to USB: &amp;quot; + string); // Output clean, printable characters to the terminal log (without raw trailing CRLF) const cleanLog = string.replace(/[\r\n]+$/, &amp;#39;&amp;#39;); logToTerminal(`&amp;gt; ${cleanLog}\n`); let view = new TextEncoder(&amp;#39;utf-8&amp;#39;).encode(string); if (port) { port.send(view).catch(err =&amp;gt; { console.error(&amp;quot;Send failed:&amp;quot;, err); logToTerminal(`[SYSTEM ERROR] Send failed: ${err.message || err}\n`); }); } else { logToTerminal(&amp;quot;[SYSTEM ERROR] Cannot send: device is not connected.\n&amp;quot;); } } function sendInput() { let sourceEl = document.getElementById(&amp;#39;input&amp;#39;); let source = sourceEl.value; if (source.length === 0) return; // Get selected line ending from dropdown const endingEl = document.getElementById(&amp;#39;line-ending&amp;#39;); const ending = endingEl ? endingEl.value : &amp;#39;crlf&amp;#39;; let terminator = &amp;#39;&amp;#39;; if (ending === &amp;#39;crlf&amp;#39;) { terminator = &amp;#39;\r\n&amp;#39;; } else if (ending === &amp;#39;lf&amp;#39;) { terminator = &amp;#39;\n&amp;#39;; } else if (ending === &amp;#39;cr&amp;#39;) { terminator = &amp;#39;\r&amp;#39;; } // Send command with selected line ending termination send(source + terminator); sourceEl.value = &amp;quot;&amp;quot;; // Clear input after send } function logToTerminal(message) { const outputEl = document.getElementById(&amp;#39;output&amp;#39;); if (outputEl) { outputEl.value += message; outputEl.scrollTop = outputEl.scrollHeight; } } // UI Updates function handleConnectUI() { const statusBadge = document.getElementById(&amp;#39;status-badge&amp;#39;); if (statusBadge) { statusBadge.className = &amp;quot;badge-status badge-connected&amp;quot;; statusBadge.innerHTML = &amp;#39; Connected&amp;#39;; } const connectBtn = document.getElementById(&amp;#39;connect-btn&amp;#39;); if (connectBtn) connectBtn.classList.add(&amp;#39;d-none&amp;#39;); const disconnectBtn = document.getElementById(&amp;#39;disconnect-btn&amp;#39;); if (disconnectBtn) disconnectBtn.classList.remove(&amp;#39;d-none&amp;#39;); const inputEl = document.getElementById(&amp;#39;input&amp;#39;); if (inputEl) inputEl.disabled = false; const submitBtn = document.getElementById(&amp;#39;submit-btn&amp;#39;); if (submitBtn) submitBtn.disabled = false; document.querySelectorAll(&amp;#39;.btn-quick-cmd&amp;#39;).forEach(btn =&amp;gt; btn.removeAttribute(&amp;#39;disabled&amp;#39;)); if (port &amp;amp;&amp;amp; port.device_) { const dev = port.device_; const vendorEl = document.getElementById(&amp;#39;info-vendor-id&amp;#39;); const productEl = document.getElementById(&amp;#39;info-product-id&amp;#39;); const mfgEl = document.getElementById(&amp;#39;info-mfg&amp;#39;); const prodEl = document.getElementById(&amp;#39;info-product&amp;#39;); const serialEl = document.getElementById(&amp;#39;info-serial&amp;#39;); if (vendorEl) vendorEl.innerText = &amp;#39;0x&amp;#39; + dev.vendorId.toString(16).padStart(4, &amp;#39;0&amp;#39;).toUpperCase(); if (productEl) productEl.innerText = &amp;#39;0x&amp;#39; + dev.productId.toString(16).padStart(4, &amp;#39;0&amp;#39;).toUpperCase(); if (mfgEl) mfgEl.innerText = dev.manufacturerName || &amp;#39;Unknown&amp;#39;; if (prodEl) prodEl.innerText = dev.productName || &amp;#39;Unknown&amp;#39;; if (serialEl) serialEl.innerText = dev.serialNumber || &amp;#39;N/A&amp;#39;; const cardEl = document.getElementById(&amp;#39;device-info-card&amp;#39;); if (cardEl) cardEl.classList.remove(&amp;#39;d-none&amp;#39;); } } function handleDisconnectUI() { const statusBadge = document.getElementById(&amp;#39;status-badge&amp;#39;); if (statusBadge) { statusBadge.className = &amp;quot;badge-status badge-disconnected&amp;quot;; statusBadge.innerHTML = &amp;#39; Disconnected&amp;#39;; } const connectBtn = document.getElementById(&amp;#39;connect-btn&amp;#39;); if (connectBtn) connectBtn.classList.remove(&amp;#39;d-none&amp;#39;); const disconnectBtn = document.getElementById(&amp;#39;disconnect-btn&amp;#39;); if (disconnectBtn) disconnectBtn.classList.add(&amp;#39;d-none&amp;#39;); const inputEl = document.getElementById(&amp;#39;input&amp;#39;); if (inputEl) inputEl.disabled = true; const submitBtn = document.getElementById(&amp;#39;submit-btn&amp;#39;); if (submitBtn) submitBtn.disabled = true; document.querySelectorAll(&amp;#39;.btn-quick-cmd&amp;#39;).forEach(btn =&amp;gt; btn.setAttribute(&amp;#39;disabled&amp;#39;, &amp;#39;true&amp;#39;)); const cardEl = document.getElementById(&amp;#39;device-info-card&amp;#39;); if (cardEl) cardEl.classList.add(&amp;#39;d-none&amp;#39;); port = null; } // Dom Event Listeners window.addEventListener(&amp;#39;DOMContentLoaded&amp;#39;, () =&amp;gt; { // Check browser support if (!navigator.usb) { const banner = document.getElementById(&amp;#39;support-warning&amp;#39;); if (banner) banner.classList.remove(&amp;#39;d-none&amp;#39;); const connectBtn = document.getElementById(&amp;#39;connect-btn&amp;#39;); if (connectBtn) connectBtn.disabled = true; logToTerminal(&amp;quot;[SYSTEM ERROR] WebUSB is not supported in this browser. Please use Chrome, Edge, Opera or another Chromium-based browser.\n&amp;quot;); } // Connect button click const connectBtn = document.getElementById(&amp;#39;connect-btn&amp;#39;); if (connectBtn) { connectBtn.onclick = function() { logToTerminal(&amp;quot;[SYSTEM] Requesting USB device from user...\r\n&amp;quot;); serial.requestPort().then(selectedPort =&amp;gt; { port = selectedPort; logToTerminal(`[SYSTEM] Device selected: ${port.device_.productName || &amp;#39;Unnamed Device&amp;#39;} (Vendor: 0x${port.device_.vendorId.toString(16).toUpperCase()}, Product: 0x${port.device_.productId.toString(16).toUpperCase()})\n`); connect(); }).catch(err =&amp;gt; { console.warn(&amp;quot;Device selection error:&amp;quot;, err); if (err.name === &amp;#39;NotFoundError&amp;#39;) { logToTerminal(&amp;quot;[SYSTEM] Device selection cancelled by user.\r\n&amp;quot;); } else { logToTerminal(`[SYSTEM ERROR] Device selection failed: ${err.message}\r\n`); } }); }; } // Disconnect button click const disconnectBtn = document.getElementById(&amp;#39;disconnect-btn&amp;#39;); if (disconnectBtn) { disconnectBtn.onclick = function() { disconnect(); }; } // Send button click const submitBtn = document.getElementById(&amp;#39;submit-btn&amp;#39;); if (submitBtn) { submitBtn.onclick = () =&amp;gt; { sendInput(); }; } // Input textbox keypress (Enter to send) const inputEl = document.getElementById(&amp;#39;input&amp;#39;); if (inputEl) { inputEl.addEventListener(&amp;#39;keydown&amp;#39;, (e) =&amp;gt; { if (e.key === &amp;#39;Enter&amp;#39;) { e.preventDefault(); sendInput(); } }); } // Clear output terminal const clearBtn = document.getElementById(&amp;#39;clear-btn&amp;#39;); if (clearBtn) { clearBtn.onclick = () =&amp;gt; { const outputEl = document.getElementById(&amp;#39;output&amp;#39;); if (outputEl) outputEl.value = &amp;quot;&amp;quot;; }; } // Copy terminal output to clipboard const copyBtn = document.getElementById(&amp;#39;copy-btn&amp;#39;); if (copyBtn) { copyBtn.onclick = () =&amp;gt; { const outputEl = document.getElementById(&amp;#39;output&amp;#39;); if (outputEl) { const outputText = outputEl.value; navigator.clipboard.writeText(outputText).then(() =&amp;gt; { const copyIcon = document.getElementById(&amp;#39;copy-icon&amp;#39;); if (copyIcon) { copyIcon.className = &amp;quot;fa-solid fa-check text-success&amp;quot;; setTimeout(() =&amp;gt; { copyIcon.className = &amp;quot;fa-solid fa-copy&amp;quot;; }, 1500); } }); } }; } // Format select dropdown change listener const formatSelect = document.getElementById(&amp;#39;format-select&amp;#39;); if (formatSelect) { formatSelect.addEventListener(&amp;#39;change&amp;#39;, (e) =&amp;gt; { logToTerminal(`[SYSTEM] Switched format to ${e.target.value.toUpperCase()}\r\n`); }); } // Quick Command buttons handler document.querySelectorAll(&amp;#39;.btn-quick-cmd&amp;#39;).forEach(btn =&amp;gt; { btn.onclick = () =&amp;gt; { const cmd = btn.getAttribute(&amp;#39;data-cmd&amp;#39;) + `\r\n`; send(cmd); }; }); }); 3.2.3 Demo community.element14.com/.../1184.webUSBdemo.mp4 4. Summary WebUSB serves as a lightweight yet powerful bridge between web applications and USB hardware. On the web side, it unlocks new classes of browser-based hardware applications under a strong consent and security model. On the embedded side, it demands only modest additions to an existing USB stack (primarily descriptors for polished integration) making it accessible to most MCU&amp;#39;s with USB device peripherals. This duality enables manufacturers to deliver seamless, cross-platform experiences while maintaining compatibility with traditional USB ecosystems. Hope you find this useful. Thanks for reading,</description><category domain="https://community.element14.com/technologies/embedded/tags/webusb">webusb</category><category domain="https://community.element14.com/technologies/embedded/tags/Zephyr%2bRTOS">Zephyr RTOS</category><category domain="https://community.element14.com/technologies/embedded/tags/diy">diy</category><category domain="https://community.element14.com/technologies/embedded/tags/usb">usb</category><category domain="https://community.element14.com/technologies/embedded/tags/embedded">embedded</category><category domain="https://community.element14.com/technologies/embedded/tags/javascript">javascript</category><category domain="https://community.element14.com/technologies/embedded/tags/rp2040">rp2040</category><category domain="https://community.element14.com/technologies/embedded/tags/functional%2bcircuit%2btesting">functional circuit testing</category><category domain="https://community.element14.com/technologies/embedded/tags/FCT">FCT</category></item><item><title>File: 1184.webUSBdemo</title><link>https://community.element14.com/technologies/embedded/m/managed-videos/151497</link><pubDate>Wed, 24 Jun 2026 13:38:00 GMT</pubDate><guid isPermaLink="false">93d5dcb4-84c2-446f-b2cb-99731719e767:ce297fde-10e8-4c0a-b9fe-e480f91c5566</guid><dc:creator>BigG</dc:creator><description /></item><item><title>File: 5633.USB_TTL</title><link>https://community.element14.com/technologies/embedded/m/managed-videos/151496</link><pubDate>Wed, 24 Jun 2026 13:37:00 GMT</pubDate><guid isPermaLink="false">93d5dcb4-84c2-446f-b2cb-99731719e767:b3243952-cf04-47e1-864c-0ec696bb383b</guid><dc:creator>BigG</dc:creator><description /></item><item><title /><link>https://community.element14.com/technologies/embedded/b/blog/posts/mspm0-analogue-front-end-general-purpose-amp-as-buffer-for-adc?CommentId=5e469937-9824-4e93-a37a-32eff7ea6e2e</link><pubDate>Wed, 24 Jun 2026 12:57:00 GMT</pubDate><guid isPermaLink="false">93d5dcb4-84c2-446f-b2cb-99731719e767:5e469937-9824-4e93-a37a-32eff7ea6e2e</guid><dc:creator>Jan Cumps</dc:creator><description>DAB , yes. They aren&amp;#39;t the only ones. Renesas has a very neat RX23-E controller. It has an impressive front end with programmable gain, precision ADC. And then digital filtering. Capable of doing load cell measurement, and thermocouple temperature measurements with compensation. Without external active components.</description></item><item><title /><link>https://community.element14.com/technologies/embedded/b/blog/posts/mspm0-analogue-front-end-general-purpose-amp-as-buffer-for-adc?CommentId=a39a7cfa-9c3e-4705-a823-c5641ab560af</link><pubDate>Tue, 23 Jun 2026 19:39:00 GMT</pubDate><guid isPermaLink="false">93d5dcb4-84c2-446f-b2cb-99731719e767:a39a7cfa-9c3e-4705-a823-c5641ab560af</guid><dc:creator>DAB</dc:creator><description>Nice post Jan. I did not know that this device included opamps withing the architecture. It looks like TI is copying capability from the PSOC.</description></item><item><title>Blog Post: MSPM0 analogue front-end: general purpose amp as buffer for ADC</title><link>https://community.element14.com/technologies/embedded/b/blog/posts/mspm0-analogue-front-end-general-purpose-amp-as-buffer-for-adc</link><pubDate>Mon, 22 Jun 2026 19:11:00 GMT</pubDate><guid isPermaLink="false">93d5dcb4-84c2-446f-b2cb-99731719e767:fb19334e-6fbf-4d9e-80d3-aeab3d5db6bf</guid><dc:creator>Jan Cumps</dc:creator><description>The MSPM0 has a general purpose amp (GPAMP). It&amp;#39;s an internal OpAmp that, for example, can buffer a signal for the ADC without external components. That&amp;#39;s an example that I&amp;#39;m trying out in this post. Roughly based on the Resource Explorer gpamp_buffer_to_adc example. Setup For this example, the GPAMP is configured with x1 gain (a buffer or input follower). And its output is internally routed to the ADC input. source: wikipedia all rights released (Public domain) GPAMP settings: The non-inverting input (+) is routed to pin PA26. The output is internally routed to the inverting input (+). The internal routing to the ADC is done by setting the ADC channel: The C code is unchanged from TI&amp;#39;s gpamp_buffer_to_adc example. ChatGPT info: Test The test source is a 2.5 V reference. The ADC reference is VDDA (3.3 V). I&amp;#39;ve done 3 tests: measure the ADC reference voltage, and the 2.5 V test signal. buffered by GPAMP, directly to the ADC with a Brymen BM235 DMM GPAMP (0 - 4095) ADC (0 – 4095) BM235 (mV) ADC ref 4085 4095 3290 input 3110 0.761321909424725 3097 0.756288156288156 2498 0.759270516717325 This will work for the EasyL1105 board and with an L1306 Launchpad. Thank you for reading. Related posts</description><category domain="https://community.element14.com/technologies/embedded/tags/MSPM0">MSPM0</category><category domain="https://community.element14.com/technologies/embedded/tags/easyL1105">easyL1105</category><category domain="https://community.element14.com/technologies/embedded/tags/texas_5F00_instruments">texas_instruments</category><category domain="https://community.element14.com/technologies/embedded/tags/ti">ti</category></item><item><title /><link>https://community.element14.com/technologies/embedded/b/blog/posts/how-to-work-with-mspm0-microcontrollers-and-build-a-peripheral-expander-processor-companion?CommentId=a0a1568c-56d4-47c4-b375-82f44c1b80b2</link><pubDate>Fri, 19 Jun 2026 07:56:00 GMT</pubDate><guid isPermaLink="false">93d5dcb4-84c2-446f-b2cb-99731719e767:a0a1568c-56d4-47c4-b375-82f44c1b80b2</guid><dc:creator>Jan Cumps</dc:creator><description /></item><item><title /><link>https://community.element14.com/technologies/embedded/b/blog/posts/how-to-work-with-mspm0-microcontrollers-and-build-a-peripheral-expander-processor-companion?CommentId=3515700a-3603-46d9-9341-cf23a8d801c7</link><pubDate>Fri, 19 Jun 2026 07:24:00 GMT</pubDate><guid isPermaLink="false">93d5dcb4-84c2-446f-b2cb-99731719e767:3515700a-3603-46d9-9341-cf23a8d801c7</guid><dc:creator>Jan Cumps</dc:creator><description>The MSPM0 devices have 1 or more OpAmp on silicon. The L1105 has a generic amp, the L1306 has a programmable gain amp. For both devices, they can be internally chained to the ADC, and can serve as buffer, or DC offset generator to get the signal mid-range of the ADC...</description></item><item><title /><link>https://community.element14.com/technologies/embedded/b/blog/posts/how-to-work-with-mspm0-microcontrollers-and-build-a-peripheral-expander-processor-companion?CommentId=a16880cd-35c3-4b87-91e5-b7b69c78a89a</link><pubDate>Thu, 18 Jun 2026 21:12:00 GMT</pubDate><guid isPermaLink="false">93d5dcb4-84c2-446f-b2cb-99731719e767:a16880cd-35c3-4b87-91e5-b7b69c78a89a</guid><dc:creator>shabaz</dc:creator><description>I&amp;#39;ve updated the code on GitHub with some improvements. Thanks to a nice pointer from Jan Cumps to check out this (+) 14 bit ADC oversample example for the EasyL1105 MSPM0 board - element14 Community I&amp;#39;m now getting 14-bit resolution. The frequency measurement seems to reliably detect 50/60Hz at around 10 mVp-p upward. The AC RMS seems very accurate too; I&amp;#39;m relying on the 3.3V from the testbed, which is generated from the Pi Pico, so it&amp;#39;s just an informal test. 10mVp-p sine wave input (not measured out of the sig-gen, so I don&amp;#39;t expect that to be very accurate either): 50mVp-p sine wave input: This will work great with a current transformer ( (+) Monitoring Mains Current: Experimenting with the SCT013 Current Clamp - element14 Community ). I&amp;#39;ve already tried that, and it can reliably measure the current from a LED desk-lamp (Sylvania 8W lamp) for instance.</description></item><item><title /><link>https://community.element14.com/technologies/embedded/b/blog/posts/how-to-work-with-mspm0-microcontrollers-and-build-a-peripheral-expander-processor-companion?CommentId=c250716f-c1c5-41f1-8543-f67eeb18f36d</link><pubDate>Tue, 16 Jun 2026 18:03:00 GMT</pubDate><guid isPermaLink="false">93d5dcb4-84c2-446f-b2cb-99731719e767:c250716f-c1c5-41f1-8543-f67eeb18f36d</guid><dc:creator>shabaz</dc:creator><description>I gave it a shot, the results seem OK but I need to verify with some other signals. (I applied a 1 kHz sine wave, 2Vp-p 1.65V offset signal). The algorithm samples at 4ksps, for one second, all the while accumulating the sums of the ADC measurements, and also simultaneously the sum of the square of the ADC measurement. After the acquisition, the offset and RMS are calculated. The frequency measurement is experimental, I&amp;#39;m still working on it. To use this function, its best to examine the Python file in the test_harness folder, but in brief the steps are: I2C_write(0x20, ACMEAS_START_REG) # 0x20 is the I2C device address, ACMEAS_START_REG is 0x50 I2C_write(0x20, ACMEAS_RESULT_REG) # 0x51 is the register ACMEAS_RESULT_REG I2C_read(0x20, 8) # read 8 bytes, which are four 16-bit integers (first three are in ADC counts, last is freq in Hz) This is the circuit (red arrow indicates where to apply the signal-to-be-measured):</description></item><item><title /><link>https://community.element14.com/technologies/embedded/b/blog/posts/how-to-work-with-mspm0-microcontrollers-and-build-a-peripheral-expander-processor-companion?CommentId=40fa79cb-1e00-4907-a6bc-a4308364d3bf</link><pubDate>Tue, 16 Jun 2026 16:37:00 GMT</pubDate><guid isPermaLink="false">93d5dcb4-84c2-446f-b2cb-99731719e767:40fa79cb-1e00-4907-a6bc-a4308364d3bf</guid><dc:creator>shabaz</dc:creator><description>Thanks!</description></item><item><title /><link>https://community.element14.com/technologies/embedded/b/blog/posts/how-to-work-with-mspm0-microcontrollers-and-build-a-peripheral-expander-processor-companion?CommentId=ab3cb3d2-4b8d-4ad6-8768-ae00ff8fd8a1</link><pubDate>Sun, 14 Jun 2026 13:45:00 GMT</pubDate><guid isPermaLink="false">93d5dcb4-84c2-446f-b2cb-99731719e767:ab3cb3d2-4b8d-4ad6-8768-ae00ff8fd8a1</guid><dc:creator>kk99</dc:creator><description>Nice video/write-up.</description></item><item><title /><link>https://community.element14.com/technologies/embedded/b/blog/posts/how-to-work-with-mspm0-microcontrollers-and-build-a-peripheral-expander-processor-companion?CommentId=583ac7d5-0bf4-454e-a6b0-20e57107997c</link><pubDate>Sun, 14 Jun 2026 11:32:00 GMT</pubDate><guid isPermaLink="false">93d5dcb4-84c2-446f-b2cb-99731719e767:583ac7d5-0bf4-454e-a6b0-20e57107997c</guid><dc:creator>shabaz</dc:creator><description>That&amp;#39;s very interesting! I was thinking the other day, that choice of interface would be very helpful. Now that I have my workflow sorted for developing with MSPM0, at some point it would be good to see what larger pin-count MSPM0 parts could be useful to try out with multiple comms-interfaces, and then maybe port some of the code across (and hopefully redo and make it cleaner, now that I know what mistakes I made the first time around).</description></item><item><title /><link>https://community.element14.com/technologies/embedded/b/blog/posts/how-to-work-with-mspm0-microcontrollers-and-build-a-peripheral-expander-processor-companion?CommentId=8a2ba386-5a41-4a8c-a963-5b7c76392ccd</link><pubDate>Sun, 14 Jun 2026 11:21:00 GMT</pubDate><guid isPermaLink="false">93d5dcb4-84c2-446f-b2cb-99731719e767:8a2ba386-5a41-4a8c-a963-5b7c76392ccd</guid><dc:creator>shabaz</dc:creator><description>Hi DAB, Thanks!</description></item><item><title /><link>https://community.element14.com/technologies/embedded/b/blog/posts/how-to-work-with-mspm0-microcontrollers-and-build-a-peripheral-expander-processor-companion?CommentId=7295aa3f-59b4-44a0-bb8a-b166e04c6c4a</link><pubDate>Sun, 14 Jun 2026 07:29:00 GMT</pubDate><guid isPermaLink="false">93d5dcb4-84c2-446f-b2cb-99731719e767:7295aa3f-59b4-44a0-bb8a-b166e04c6c4a</guid><dc:creator>Jan Cumps</dc:creator><description>TI published an application note that allows flex on the interface side: IO Expander With SPI, I2C, and UART . The example in your post focuses on one interface, and several expansion options (GPIO, ADC, PWM, UART). The application note sticks to one expansion type: GPIO. But it shows how to use different communication techniques to do that: I2C, SPI and UART.</description></item></channel></rss>