From 2e77f756f8ed9ab739fbbdd723a29eaed3e930b1 Mon Sep 17 00:00:00 2001 From: Tudor-Gansca_adi Date: Fri, 24 Jul 2026 11:49:03 +0300 Subject: [PATCH 1/5] learning/tools_for_ls: Fill in Matlab and Zephyr sections --- .../AD5592r_NPN_Curve_Tracer.png | 3 + .../tools_for_ls/matlab_example/index.rst | 417 ++++++++++++++++- .../tools_for_ls/zephyr_example/index.rst | 430 +++++++++++++++++- 3 files changed, 846 insertions(+), 4 deletions(-) create mode 100644 docs/learning/tools_for_ls/matlab_example/AD5592r_NPN_Curve_Tracer.png diff --git a/docs/learning/tools_for_ls/matlab_example/AD5592r_NPN_Curve_Tracer.png b/docs/learning/tools_for_ls/matlab_example/AD5592r_NPN_Curve_Tracer.png new file mode 100644 index 0000000000..5dd3e0cbf3 --- /dev/null +++ b/docs/learning/tools_for_ls/matlab_example/AD5592r_NPN_Curve_Tracer.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:81d53e54b1db0e9674be194840dc8f06883a0daa99c2401a04548c0a8d978968 +size 61304 diff --git a/docs/learning/tools_for_ls/matlab_example/index.rst b/docs/learning/tools_for_ls/matlab_example/index.rst index 76dfa4560e..393407c5c0 100644 --- a/docs/learning/tools_for_ls/matlab_example/index.rst +++ b/docs/learning/tools_for_ls/matlab_example/index.rst @@ -21,4 +21,419 @@ ADALM-LSMSPG over SPI and I2C, and controlled from a PC over USB serial. to Python for IIO device control, and using a bare-metal microcontroller (MAX32666FTHR) as a portable alternative to the Raspberry Pi Linux host. -<> \ No newline at end of file +Hardware Prerequisites +^^^^^^^^^^^^^^^^^^^^^^ + +In addition to the ADALM-LSMSPG board, you will need: + +- **MAX32666FTHR** Feather development board +- Micro USB cable for the MAX32666FTHR + +Software Prerequisites +^^^^^^^^^^^^^^^^^^^^^^ + +- **MATLAB** installed on your PC (base MATLAB is sufficient, no additional + toolboxes required) +- **ADI Precision Toolbox** for MATLAB — clone or download from + `GitHub `__ and add + to the MATLAB path +- **libiio** installed on your PC (provides the ``iio_info`` and ``iio_attr`` + command-line tools, and the shared library used by PrecisionToolbox) + + - Windows: download the installer from the + `libiio GitHub releases `__ + - Linux: ``sudo apt install libiio-utils`` + +Architecture Overview +^^^^^^^^^^^^^^^^^^^^^ + +The architecture differs from the Raspberry Pi setup. Instead of running +Python directly on the Linux host that is physically connected to the +AD5592r, we now have a two-part system: + +:: + + ┌────────────────┐ USB Serial ┌─────────────────────┐ + │ PC (MATLAB) │ ◄────────────► │ MAX32666FTHR │ + │ │ iio_attr/ │ (tinyiiod server) │ + │ curve_tracer.m │ libiio │ │ + │ script │ │ AD5592r ◄── SPI │ + └────────────────┘ │ AD5593r ◄── I2C │ + │ LM75 ◄── I2C │ + └─────────────────────┘ + +The MAX32666FTHR runs a **tinyiiod** firmware — a bare-metal IIO daemon that +exposes the AD5592r, AD5593r, and LM75 as standard IIO devices over USB +serial. The MATLAB script on the PC sends IIO commands through the serial +link, exactly as if it were talking to a Linux IIO device over the network. + +Step 1: Flash the MAX32666FTHR +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Make sure the MAX32625PICO DAPLink and the MAX32666FTHR are flashed with the +correct firmware for the ADALM-LSMSPG tinyiiod example — see the +`ADALM-LSMSPG documentation `__ +for step-by-step instructions. + +Step 2: Connect the Hardware +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +1. Mount the MAX32666FTHR onto the ADALM-LSMSPG board using the Feather + headers **P1** (16-pin) and **P28** (12-pin), with the Feather's + components facing downward. + +2. Connect the USB cable to the MAX32666FTHR. + +3. The **heartbeat LED** (DS1) on the ADALM-LSMSPG should begin blinking + with a double-pulse pattern, indicating the tinyiiod server is running + and waiting for connections. + +Step 3: Verify the Connection +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +First, identify the COM port assigned to the MAX32666FTHR. On Windows, open +**Device Manager** and look under **Ports (COM & LPT)** for the USB serial +device. + +Then verify the IIO context from a terminal: + +.. code-block:: bash + + iio_info -u serial:COM11,115200,8n1n + +Replace ``COM11`` with your actual COM port. You should see output listing +four IIO devices: + +- ``lm75`` — temperature sensor +- ``ad5592r`` — 8-channel ADC/DAC over SPI (used for NPN curve tracer) +- ``ad5593r`` — 8-channel ADC/DAC over I2C (used for PNP curve tracer) +- ``one-bit-adc-dac`` — GPIO channels mapped as 1-bit ADC/DAC + +Confirm that the ``ad5592r`` device shows channels ``voltage0`` (output), +``voltage1`` (input), ``voltage2`` (input and output), and that the scale +attribute reads approximately ``0.61035156`` mV/LSB. + +Step 4a: Curve Tracer with ADI Precision Toolbox (Recommended) +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +The recommended approach uses the +`ADI Precision Toolbox `__, +which calls libiio directly from MATLAB in-process. This is significantly +faster than spawning subprocesses and mirrors how pyadi-iio works in Python. + +The Precision Toolbox provides the ``adi.AD5592r.Rx`` class, which inherits +attribute read/write methods from the ``matlabshared.libiio.base`` class. +The key methods used are: + +- ``getAttributeDouble(channel, attribute, isOutput)`` — read a channel + attribute as a double (e.g., the ``scale`` factor) +- ``getAttributeRAW(channel, attribute, isOutput)`` — read a channel + attribute as a string (e.g., ADC ``raw`` readings) +- ``setAttributeRAW(channel, attribute, value, isOutput)`` — write a channel + attribute (e.g., set a DAC ``raw`` output value) + +The ``isOutput`` flag selects between input (ADC) and output (DAC) channels, +which is important because channels like ``voltage2`` exist as both. + +.. collapsible:: ad5592r_curve_tracer.m — Precision Toolbox version (click to expand) + + .. code-block:: matlab + + %% AD5592r NPN Curve Tracer using PrecisionToolbox + %% Sweeps base and collector voltages on the ADALM-LSMSPG board + %% and plots Ic vs Vc family of curves. + clc + clear + + % Instantiate the system object and connect + rx = adi.AD5592r.Rx(); + rx.uri = 'serial:COM11,115200,8n1n'; + rx.EnabledChannels = 2; + rx.SamplesPerFrame = 1; + rx(); + + % Circuit constants (NPN curve tracer on ADALM-LSMSPG) + Rsense = 47.0; % 47 Ohms - collector sense resistor + Rbase = 47.0e3; % 47 kOhms - base resistor + Vbe = 0.7; % Approximate base-emitter voltage + + % Read scale (mV per LSB) - identical for all AD5592r channels + mV_per_lsb = rx.getAttributeDouble('voltage0', 'scale', true); + fprintf('Scale: %.6f mV/LSB\n', mV_per_lsb); + + % Initialize DAC outputs to a safe starting point + rx.setAttributeRAW('voltage0', 'raw', num2str(round(500 / mV_per_lsb)), true); + rx.setAttributeRAW('voltage2', 'raw', num2str(round(500 / mV_per_lsb)), true); + + % Sweep parameters + base_mv = 499:500:2499; + coll_mv = 0:50:2450; + n_base = length(base_mv); + n_coll = length(coll_mv); + + % Preallocate results + curves_vc = zeros(n_base, n_coll); + curves_ic = zeros(n_base, n_coll); + labels = cell(1, n_base); + + for bi = 1:n_base + vb_raw = round(base_mv(bi) / mV_per_lsb); + rx.setAttributeRAW('voltage0', 'raw', num2str(vb_raw), true); + + ib = ((vb_raw * mV_per_lsb / 1000) - Vbe) / Rbase; + fprintf('Base Drive: %.3f V, %.1f uA\n', vb_raw * mV_per_lsb / 1000, ib * 1e6); + labels{bi} = sprintf('I_b = %.1f \\muA', ib * 1e6); + + for ci = 1:n_coll + vc_raw = round(coll_mv(ci) / mV_per_lsb); + rx.setAttributeRAW('voltage2', 'raw', num2str(vc_raw), true); + + vc_drive_raw = str2double(rx.getAttributeRAW('voltage2', 'raw', false)); + vc_sense_raw = str2double(rx.getAttributeRAW('voltage1', 'raw', false)); + + ic = (vc_drive_raw - vc_sense_raw) * mV_per_lsb / Rsense; + vc = vc_sense_raw * mV_per_lsb / 1000.0; + + curves_vc(bi, ci) = vc; + curves_ic(bi, ci) = ic; + end + end + + % Plot + figure('Name', 'AD5592r NPN Curve Tracer'); + hold on; + for bi = 1:n_base + plot(curves_vc(bi, :), curves_ic(bi, :), 'LineWidth', 1.5); + end + hold off; + title('ADALM-LSMSPG NPN Curve Tracer (MATLAB)'); + xlabel('Collector Voltage (V)'); + ylabel('Collector Current (mA)'); + legend(labels, 'Location', 'northwest'); + grid on; + + % Cleanup + release(rx); + +The script structure mirrors the Python version: + +1. **Connect** to the AD5592r by creating an ``adi.AD5592r.Rx`` object with + the serial URI and calling ``rx()`` to initialize the IIO context. + ``EnabledChannels = 2`` selects ``voltage1`` (an ADC input channel) for + context initialization. ``SamplesPerFrame = 1`` since we only need + single-value attribute access, not buffered streaming. +2. **Read the scale** attribute using ``getAttributeDouble`` to get the + mV-per-LSB conversion factor (approximately 0.610 mV/LSB for the AD5592r + with internal reference). +3. **Outer loop** — sweep the base drive voltage (``voltage0``, DAC output) + through 5 steps from 499 mV to 2499 mV using ``setAttributeRAW``, setting + a different base current for each curve. +4. **Inner loop** — for each base current, sweep the collector drive voltage + (``voltage2``, DAC output) from 0 to 2450 mV in 50 mV steps. At each + step, read back the collector drive (``voltage2``, ADC input) and collector + sense (``voltage1``, ADC input) using ``getAttributeRAW`` to compute the + collector current through the 47 Ohm sense resistor. +5. **Plot** the family of I-V curves and release the device. + +Step 4b: Curve Tracer with Command-Line Tools (Alternative) +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +As an alternative to the Precision Toolbox, you can use the ``iio_attr`` +command-line tool (provided by libiio) directly from MATLAB via the +``system()`` function. This approach is useful for quick debugging and +learning how IIO attributes work at the lowest level, but is significantly +slower because each read or write spawns a new subprocess. + +.. collapsible:: ad5592r_curve_tracer_cli.m — iio_attr version (click to expand) + + .. code-block:: matlab + + function ad5592r_curve_tracer_cli(uri) + if nargin < 1 + uri = 'serial:COM11,115200,8n1n'; + end + + device = 'ad5592r'; + fprintf('URI: %s\n', uri); + + % Circuit constants (NPN curve tracer on ADALM-LSMSPG) + Rsense = 47.0; % 47 Ohms - collector sense resistor + Rbase = 47.0e3; % 47 kOhms - base resistor + Vbe = 0.7; % Approximate base-emitter voltage + + % Read scale (mV per LSB) - identical for all AD5592r channels + mV_per_lsb = iio_read(uri, device, 'voltage0', true, 'scale'); + fprintf('Scale: %.6f mV/LSB\n', mV_per_lsb); + + % Initialize DAC outputs to a safe starting point + iio_write(uri, device, 'voltage0', true, 'raw', round(500 / mV_per_lsb)); + iio_write(uri, device, 'voltage2', true, 'raw', round(500 / mV_per_lsb)); + + % Sweep parameters (matching the Python example) + base_mv = 499:500:2499; + coll_mv = 0:50:2450; + n_base = length(base_mv); + n_coll = length(coll_mv); + + % Preallocate results + curves_vc = zeros(n_base, n_coll); + curves_ic = zeros(n_base, n_coll); + labels = cell(1, n_base); + + for bi = 1:n_base + vb_raw = round(base_mv(bi) / mV_per_lsb); + iio_write(uri, device, 'voltage0', true, 'raw', vb_raw); + + ib = ((vb_raw * mV_per_lsb / 1000) - Vbe) / Rbase; + fprintf('Base Drive: %.3f V, %.1f uA\n', ... + vb_raw * mV_per_lsb / 1000, ib * 1e6); + labels{bi} = sprintf('I_b = %.1f \\muA', ib * 1e6); + + for ci = 1:n_coll + vc_raw = round(coll_mv(ci) / mV_per_lsb); + iio_write(uri, device, 'voltage2', true, 'raw', vc_raw); + + vc_drive_raw = iio_read(uri, device, 'voltage2', false, 'raw'); + vc_sense_raw = iio_read(uri, device, 'voltage1', false, 'raw'); + + ic = (vc_drive_raw - vc_sense_raw) * mV_per_lsb / Rsense; + vc = vc_sense_raw * mV_per_lsb / 1000.0; + + curves_vc(bi, ci) = vc; + curves_ic(bi, ci) = ic; + end + end + + % Plot + figure('Name', 'AD5592r NPN Curve Tracer'); + hold on; + for bi = 1:n_base + plot(curves_vc(bi, :), curves_ic(bi, :), 'LineWidth', 1.5); + end + hold off; + title('ADALM-LSMSPG NPN Curve Tracer (MATLAB)'); + xlabel('Collector Voltage (V)'); + ylabel('Collector Current (mA)'); + legend(labels, 'Location', 'northwest'); + grid on; + end + + %% IIO helper functions using iio_attr command-line tool + + function val = iio_read(uri, device, channel, is_output, attr) + dir_flag = output_flag(is_output); + cmd = sprintf('iio_attr -u %s -c %s %s %s %s', ... + uri, dir_flag, device, channel, attr); + [status, result] = system(cmd); + if status ~= 0 + error('iio_attr read failed: %s', strtrim(result)); + end + tokens = regexp(result, '''([^'']+)''\s*$', 'tokens'); + if ~isempty(tokens) + val = str2double(tokens{1}{1}); + else + val = str2double(strtrim(result)); + end + if isnan(val) + error('Could not parse iio_attr output: %s', strtrim(result)); + end + end + + function iio_write(uri, device, channel, is_output, attr, value) + dir_flag = output_flag(is_output); + cmd = sprintf('iio_attr -u %s -c %s %s %s %s %d', ... + uri, dir_flag, device, channel, attr, value); + [status, result] = system(cmd); + if status ~= 0 + error('iio_attr write failed: %s', strtrim(result)); + end + end + + function flag = output_flag(is_output) + if is_output + flag = '-o'; + else + flag = '-i'; + end + end + +The helper functions ``iio_read`` and ``iio_write`` wrap calls to the +``iio_attr`` command-line tool. Each call spawns a subprocess that +connects to the tinyiiod server over serial, reads or writes the specified +channel attribute, and returns the result. + +.. note:: + + This approach is noticeably slower than the Precision Toolbox version + (Step 4a). The ``system()`` calls spawn a new ``iio_attr`` subprocess for + every read and write operation. For approximately 250 sweep points with + 4 IIO operations each, that is roughly 1000 subprocess launches. The + Precision Toolbox avoids this overhead by calling libiio directly + in-process, similar to how pyadi-iio wraps the libiio C library in Python. + +Step 5: Run the Curve Tracer +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +**Using the Precision Toolbox (Step 4a):** + +In MATLAB, ensure the PrecisionToolbox is on your path, update the URI in +the script to match your COM port, and run: + +.. code-block:: matlab + + ad5592r_curve_tracer + +**Using the command-line version (Step 4b):** + +.. code-block:: matlab + + ad5592r_curve_tracer_cli('serial:COM11,115200,8n1n') + +Replace ``COM11`` with your actual COM port. + +Both versions will: + +1. Read the scale from the AD5592r. +2. Sweep through 5 base voltage steps, each with 50 collector voltage steps. +3. Print progress to the MATLAB command window. +4. Display a figure with the NPN transistor I-V characteristic curves. + +The resulting plot should be comparable to the one produced by the Python +``ad5592r_curve_tracer.py`` script run on the Raspberry Pi. + +.. figure:: AD5592r_NPN_Curve_Tracer.png + :width: 500px + + AD5592r NPN Curve Tracer output from MATLAB. + +Comparison: Raspberry Pi vs. MAX32666FTHR +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Both platforms produce the same curve tracer results, but the underlying +architecture is different: + +.. list-table:: + :header-rows: 1 + :widths: 30 35 35 + + * - + - **Raspberry Pi (Linux)** + - **MAX32666FTHR (Bare Metal)** + * - OS + - ADI Kuiper Linux + - No-OS (tinyiiod firmware) + * - IIO Framework + - Linux kernel IIO drivers + - no-OS IIO + tinyiiod + * - Connection to AD5592r + - SPI via device tree overlay + - SPI via no-OS SPI driver + * - Host Communication + - Network (``ip:analog.local``) + - USB Serial (``serial:COMx,115200,8n1n``) + * - Script Runs On + - Raspberry Pi or remote PC + - Remote PC only + * - Power + - 5V USB-C wall adapter + - USB bus power from PC \ No newline at end of file diff --git a/docs/learning/tools_for_ls/zephyr_example/index.rst b/docs/learning/tools_for_ls/zephyr_example/index.rst index bf8c8ee25d..95d17f003b 100644 --- a/docs/learning/tools_for_ls/zephyr_example/index.rst +++ b/docs/learning/tools_for_ls/zephyr_example/index.rst @@ -9,6 +9,430 @@ RTOS Support: Zephyr -<> +The MATLAB example above used the MAX32666FTHR as a **tinyiiod server**, exposing +the ADALM-LSMSPG devices over serial for a host script to drive. In this section +we go a step further: the curve tracer application itself now runs **on the +MAX32666FTHR**, written against `Zephyr `__ — a +small, portable real-time OS. The PC becomes a passive terminal that only +displays results. + +.. note:: + + This exercise demonstrates using Zephyr as a fully embedded alternative to + Linux or bare-metal no-OS. Zephyr provides drivers for the shield's chips, + a POSIX-like shell over UART for interactive debugging, and a build system + that treats the ADALM-LSMSPG as a first-class device — no external host, + no IIO daemon, no Python or MATLAB. + +.. note:: + + The examples below target the **MAX32666FTHR**. The same steps apply to + the MAX32655FTHR (and any other Feather-form-factor Zephyr board) — swap + the ``-b max32666fthr/max32666/cpu0`` build target for the equivalent + board qualifier of your Feather. + +Hardware Prerequisites +^^^^^^^^^^^^^^^^^^^^^^ + +- **ADALM-LSMSPG** shield +- **MAX32666FTHR** Feather development board +- **MAX32625PICO** DAPLink debug adapter (comes with the FTHR kit) +- Two Micro USB cables — one for the FTHR (power), one for the DAPLink + (SWD + serial bridge) + +Software Prerequisites +^^^^^^^^^^^^^^^^^^^^^^ + +- **Analog Devices CodeFusion Studio (CFS)** 2.2.0 or later — ships an + integrated Zephyr 4.3.0 tree, the Zephyr SDK toolchain, and the ADI HAL + for the MAX32 family. Download from + `developer.analog.com `__. +- A serial terminal — **PuTTY**, `Tera Term `__, + or the built-in serial monitor of VS Code. +- (Optional) The full upstream Zephyr checkout via ``west init`` — for + users not on CFS. + +Architecture Overview +^^^^^^^^^^^^^^^^^^^^^ + +Unlike the tinyiiod approach, there is no host script and no IIO daemon. +Zephyr, the curve tracer logic, and the AD5592R / LM75 drivers all live in +a single firmware image running on the MAX32666FTHR: + +:: + + ┌────────────────┐ USB Serial ┌───────────────────────────┐ + │ PC (PuTTY) │ ◄──────────────► │ MAX32666FTHR │ + │ │ 115200 8N1 │ (Zephyr) │ + │ read-only │ │ │ + │ terminal │ │ Zephyr shell + main() │ + └────────────────┘ │ ┌────────────────────┐ │ + │ │ ad5592 curvetrace │ │ + │ └──────────┬─────────┘ │ + │ │ │ + │ Zephyr AD559X MFD │ + │ │ │ + │ SPI ◄──┴──► AD5592R │ + │ I2C ◄──────► AD5593R│ + │ I2C ◄──────► LM75 │ + └───────────────────────────┘ + +The **DAPLink** (MAX32625PICO) plays two roles at once: it programs the +FTHR over SWD, *and* it bridges the FTHR's UART1 to the PC as a USB CDC +serial port. That single COM port is where both flashing feedback and the +Zephyr shell prompt appear. + +Step 1: Create the Zephyr Project +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +From within CodeFusion Studio, generate a new project targeting +``max32666fthr / max32666 / cpu0``. Start from the **Blinky** template — it +gives us a working baseline (an LED that toggles) before we add anything. +The generated directory tree looks like: + +:: + + MAX32666_LSMSPG_Zephyr/ + └── m4-0/ + ├── CMakeLists.txt + ├── prj.conf + ├── boards/ + │ └── max32666fthr_max32666_cpu0.overlay + └── src/ + └── main.c + +The Blinky template targets the FTHR's on-board RGB LED via the standard +Zephyr ``led0`` alias — no shield references yet. + +Step 2: Configure the Drivers (``prj.conf``) +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Zephyr uses a Kconfig-based build. The board's ``defconfig`` already +enables serial console, GPIO, and ``printk``; the ``adi_lsmspg`` shield +already ``select``\ s the SPI, I²C, and MFD subsystems in its +``Kconfig.shield``. On top of those defaults we only need to opt in to +the interactive shell and the AD559X chip driver: + +.. code-block:: none + + # Interactive shell over the console UART + CONFIG_SHELL=y + CONFIG_DEVICE_SHELL=y + CONFIG_I2C_SHELL=y + + # AD5592R / AD5593R chip driver + CONFIG_MFD_AD559X=y + +Step 3: The Curve Tracer Application +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +The curve tracer is exposed as a Zephyr **shell command** — ``ad5592 +curvetrace`` — rather than running automatically at boot. This makes the +firmware into an interactive instrument: you can poke the DAC, read the +ADC, and rerun a sweep on demand from PuTTY. + +The C code mirrors the Python and MATLAB versions exactly: + +1. Sweep ``Vbase`` (CH0 DAC) from 499 mV to 2499 mV in 500 mV steps. +2. For each base voltage, sweep ``Vcollector`` (CH2 DAC) from 0 to 2450 mV + in 50 mV steps. +3. At each point, read back ``Vcdrive_meas`` (CH2 ADC) and ``Vcsense`` + (CH1 ADC), compute ``Ic = (Vcdrive_meas − Vcsense) / Rsense``, and + print one CSV-friendly line over UART. + +.. collapsible:: main.c — Zephyr curve tracer with shell integration (click to expand) + + .. code-block:: c + + /* + * AD5592R NPN BJT curve tracer on the ADALM-LSMSPG shield. + * Runs as a Zephyr shell command over the MAX32666FTHR's DAPLink + * USB-serial console. Uses the AD559X multi-function driver's + * raw write/read API to talk to the chip. + * + * ad5592 init One-time setup: enable ref + DAC pin mode. + * ad5592 dac Write 12-bit DAC code (0..4095). + * ad5592 adc Read one ADC channel. + * ad5592 curvetrace Run the NPN BJT curve trace. + * + * SPDX-License-Identifier: Apache-2.0 + */ + + #include + #include + #include + #include + #include + #include + #include + + #define VREF_MV 2500 + #define FS_CODE 4095U + + /* AD5592R 16-bit DAC write: [1 | ch[2:0] | data[11:0]] */ + #define AD559X_DAC_WR_MSB BIT(15) + #define AD559X_DAC_CH_SHIFT 12 + + /* Curve tracer circuit parameters (ADALM-LSMSPG NPN) */ + #define CT_RSENSE_OHM 47 + #define CT_RBASE_KOHM 47 + #define CT_VBE_MV 700 + #define CT_VB_START_MV 499 + #define CT_VB_STOP_MV 2500 + #define CT_VB_STEP_MV 500 + #define CT_VC_START_MV 0 + #define CT_VC_STOP_MV 2500 + #define CT_VC_STEP_MV 50 + + #define LED0_NODE DT_ALIAS(led0) + static const struct gpio_dt_spec led = + GPIO_DT_SPEC_GET(LED0_NODE, gpios); + + static const struct device *const mfd = + DEVICE_DT_GET(DT_PARENT(DT_NODELABEL(ad5592_dac))); + + static uint16_t dac_mask; + static uint16_t adc_mask; + static bool chip_initialized; + + static int ad5592_dac_write(uint8_t ch, uint16_t value) + { + uint16_t msg = sys_cpu_to_be16(AD559X_DAC_WR_MSB | + ((uint16_t)ch << AD559X_DAC_CH_SHIFT) | + (value & 0x0FFF)); + return mfd_ad559x_write_raw(mfd, + (uint8_t *)&msg, sizeof(msg)); + } + + static int ad5592_adc_read(uint8_t ch, uint16_t *out) + { + uint16_t raw = 0; + int ret = mfd_ad559x_read_reg(mfd, AD559X_REG_SEQ_ADC, + BIT(ch), &raw); + if (ret) return ret; + *out = raw & 0x0FFF; + return 0; + } + + static int cmd_init(const struct shell *sh, + size_t argc, char **argv) + { + int ret; + ret = mfd_ad559x_write_reg(mfd, AD559X_REG_PD_REF_CTRL, + AD559X_EN_REF); + if (ret) return ret; + dac_mask = 0xFF; + adc_mask = 0; + ret = mfd_ad559x_write_reg(mfd, AD559X_REG_LDAC_EN, + dac_mask); + if (ret) return ret; + chip_initialized = true; + shell_print(sh, "AD5592R initialized"); + return 0; + } + + static int cmd_curvetrace(const struct shell *sh, + size_t argc, char **argv) + { + if (!chip_initialized) { + shell_error(sh, "run 'ad5592 init' first"); + return -EINVAL; + } + adc_mask |= BIT(1) | BIT(2); + mfd_ad559x_write_reg(mfd, AD559X_REG_ADC_CONFIG, adc_mask); + + shell_print(sh, ""); + shell_print(sh, + "=== NPN Curve Tracer (Rsense=%dR Rbase=%dk) ===", + CT_RSENSE_OHM, CT_RBASE_KOHM); + + for (int vb = CT_VB_START_MV; vb < CT_VB_STOP_MV; + vb += CT_VB_STEP_MV) { + uint32_t vb_c = ((uint32_t)vb * FS_CODE) / VREF_MV; + ad5592_dac_write(0, vb_c); + k_msleep(50); + + int ib_ua = (vb > CT_VBE_MV) ? + (vb - CT_VBE_MV) / CT_RBASE_KOHM : 0; + shell_print(sh, + "\n-- Vb=%d mV, Ib~%d uA --", vb, ib_ua); + shell_print(sh, + "Vc_drive, Vc_actual, Ic (mV, mV, uA)"); + + for (int vc = CT_VC_START_MV; vc < CT_VC_STOP_MV; + vc += CT_VC_STEP_MV) { + uint32_t vc_c = ((uint32_t)vc * FS_CODE) / VREF_MV; + ad5592_dac_write(2, vc_c); + k_msleep(10); + + uint16_t vc_drive = 0, vc_sense = 0; + ad5592_adc_read(2, &vc_drive); + ad5592_adc_read(1, &vc_sense); + + int vc_drv_mv = (vc_drive * VREF_MV) / FS_CODE; + int vc_sns_mv = (vc_sense * VREF_MV) / FS_CODE; + int ic_ua = ((vc_drv_mv - vc_sns_mv) * 1000) / + CT_RSENSE_OHM; + + shell_print(sh, "%d, %d, %d", + vc, vc_sns_mv, ic_ua); + } + } + ad5592_dac_write(0, 0); + ad5592_dac_write(2, 0); + shell_print(sh, "\n=== done ==="); + return 0; + } + + SHELL_STATIC_SUBCMD_SET_CREATE(ad5592_cmds, + SHELL_CMD_ARG(init, NULL, "Init AD5592R", + cmd_init, 1, 0), + SHELL_CMD_ARG(curvetrace, NULL, "Run NPN curve trace", + cmd_curvetrace, 1, 0), + SHELL_SUBCMD_SET_END + ); + SHELL_CMD_REGISTER(ad5592, &ad5592_cmds, + "AD5592R shell commands", NULL); + + int main(void) + { + gpio_pin_configure_dt(&led, GPIO_OUTPUT_INACTIVE); + while (1) { + gpio_pin_toggle_dt(&led); + k_msleep(500); + } + } + +A few notes about the code: + +- ``main()`` only blinks the LED as a "firmware alive" heartbeat. All + real work happens in shell commands, which the Zephyr shell subsystem + runs on its own kernel thread. +- ``SHELL_CMD_REGISTER`` is Zephyr's macro for exposing a function as a + top-level shell command. ``SHELL_STATIC_SUBCMD_SET_CREATE`` groups + subcommands under a single command name (``ad5592``). +- The AD559X driver's ``mfd_ad559x_write_raw`` / ``mfd_ad559x_read_reg`` + functions handle SPI framing and chip-select for us. There is no + direct SPI code in the application. +- Output is CSV-shaped so you can copy the PuTTY buffer straight into + a spreadsheet for plotting. + +Step 4: Attach the Shield to the Build +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Zephyr ships an official shield definition for the ADALM-LSMSPG. To pull +its devicetree overlay into the build, pass ``--shield adi_lsmspg`` (as +a CMake variable ``-DSHIELD=adi_lsmspg``) to ``west build``. This binds +the AD5592R to the Feather SPI header and the AD5593R + LM75 to the +Feather I²C header automatically — no manual overlay edits required. + +.. note:: + + The shield definition lives at + ``/boards/shields/adi_lsmspg/``. Refer to the + `upstream Zephyr documentation + `__ + for its full description. + +Step 5: Build and Flash +^^^^^^^^^^^^^^^^^^^^^^^ + +From the project directory: + +.. code-block:: bash + + west build -p always -b max32666fthr/max32666/cpu0 . -- -DSHIELD=adi_lsmspg + west flash + +``west flash`` uses OpenOCD via the DAPLink to program the FTHR. If your +CFS install prefers drag-and-drop instead, the ``build/zephyr/zephyr.hex`` +file can be dragged onto the DAPLink mass-storage drive that appears in +Explorer. + +Step 6: Open the Serial Terminal +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Identify the COM port assigned to the DAPLink (Windows: **Device Manager +→ Ports (COM & LPT)** — look for the "USB Serial Device" entry that +appears when the DAPLink is plugged in). Open it in PuTTY at +**115200 8N1** with no flow control. + +Press the **reset** button on the FTHR. You should see: + +.. code-block:: none + + *** Booting Zephyr OS build v4.3.0 *** + + uart:~$ + +The ``uart:~$`` prompt is Zephyr's interactive shell. From here you can +type ``help`` for a list of built-in commands, ``device list`` to see +every device the kernel is aware of, or ``i2c scan i2c0@4001d000`` to +confirm the shield's I²C chips are alive. + +Step 7: Run the Curve Trace +^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +At the shell prompt: + +.. code-block:: none + + uart:~$ ad5592 init + AD5592R initialized + uart:~$ ad5592 curvetrace + + === NPN Curve Tracer (Rsense=47R Rbase=47k) === + + -- Vb=499 mV, Ib~0 uA -- + Vc_drive, Vc_actual, Ic (mV, mV, uA) + 0, 1, -21 + 50, 49, 21 + ... + -- Vb=999 mV, Ib~6 uA -- + ... + === done === + +Highlight the CSV block in PuTTY (Ctrl+A then right-click to copy on +most terminals) and paste it into a spreadsheet or the Python plotting +snippet from the Raspberry Pi section to render the I–V family. + +Comparison: MATLAB (tinyiiod) vs. Zephyr +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. list-table:: + :header-rows: 1 + :widths: 30 35 35 + + * - + - **MATLAB + tinyiiod** + - **Zephyr on FTHR** + * - OS on the FTHR + - no-OS (tinyiiod firmware) + - Zephyr RTOS + * - Where the sweep runs + - MATLAB on the PC + - On the FTHR itself + * - Host role + - Active — issues every I/O + - Passive — displays a shell + * - Framework used + - libiio + PrecisionToolbox + - Zephyr device drivers + * - Connection to devices + - IIO over USB serial + - Direct SPI / I²C on the FTHR + * - Latency per point + - ~ms (subprocess or in-proc) + - ~microseconds (direct SPI) + * - Extending the app + - Edit MATLAB, rerun + - Rebuild + reflash firmware + +.. note:: + + Zephyr trades the host-side flexibility of tinyiiod for **latency, + portability, and self-containedness**. A Zephyr firmware works with + only a serial terminal on the host — no libiio, no MATLAB, no Python. + That makes it a natural stepping-stone toward a *fully embedded* + product where the FTHR eventually drives a local display or wireless + link instead of a PC terminal. From a7e0a69728393bf8b047348bb56296c2a54b963e Mon Sep 17 00:00:00 2001 From: Jorge Marques Date: Sat, 25 Jul 2026 20:53:24 +0200 Subject: [PATCH 2/5] fixup! Quite a bit of restructuring: Add child pages for matlab, native c, standalone no-OS, tinyiiod. --- docs/learning/tools_for_ls/index.rst | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/learning/tools_for_ls/index.rst b/docs/learning/tools_for_ls/index.rst index 0f65d36d05..2bea26bf03 100644 --- a/docs/learning/tools_for_ls/index.rst +++ b/docs/learning/tools_for_ls/index.rst @@ -448,7 +448,7 @@ Of course we're not limited to Python! The libiio is written natively in C, so we can write a simple C program that runs natively in Linux, on the Raspberry Pi. In this section, we'll go through this process. -:doc:`Continue to C, C++ Tutorial ` +:ref:`Continue to C, C++ Tutorial ` .. toctree:: :hidden: @@ -462,7 +462,7 @@ The LM75, AD5592r, and AD5593r are supported in ADI's MATLAB precision toolbox. In this section, we'll work through porting the temperature sensor and curve tracer to MATLAB. -:doc:`Continue to MATLAB Tutorial ` +:ref:`Continue to MATLAB Tutorial ` .. toctree:: :hidden: @@ -495,7 +495,7 @@ previously talked to the Raspberry Pi, to talk to your actual embedded target. To see this in action: -:doc:`Continue to tinyiiod Tutorial ` +:ref:`Continue to tinyiiod Tutorial ` .. toctree:: :hidden: @@ -517,7 +517,7 @@ designed to be easily portable to other platforms as well. Let's now migrate the curve tracer logic that until now ran in Python on a remote host into the embedded target, replacing the tinyiiod server entirely. -:doc:`Continue to tinyiiod Tutorial ` +:ref:`Continue to tinyiiod Tutorial ` .. toctree:: :hidden: From 94012250bad6fe9633bec499d9ec240a30530e62 Mon Sep 17 00:00:00 2001 From: Jorge Marques Date: Sat, 25 Jul 2026 20:53:56 +0200 Subject: [PATCH 3/5] fixup! Add child page for Zephyr. --- docs/learning/tools_for_ls/index.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/learning/tools_for_ls/index.rst b/docs/learning/tools_for_ls/index.rst index 2bea26bf03..787246cacf 100644 --- a/docs/learning/tools_for_ls/index.rst +++ b/docs/learning/tools_for_ls/index.rst @@ -542,7 +542,7 @@ And the MAX32655FTHR: In the Zephyr section of this workshop, we will run the tinyiiod server, a few standalone examples, and the debug console. -:doc:`Continue to the Zephyr examples ` +:ref:`Continue to the Zephyr examples ` .. toctree:: :hidden: From 7dc233e28b650deacc7fc4fbff21df64c5e96536 Mon Sep 17 00:00:00 2001 From: Jorge Marques Date: Sat, 25 Jul 2026 21:01:12 +0200 Subject: [PATCH 4/5] fixup! Quite a bit of restructuring: Add child pages for matlab, native c, standalone no-OS, tinyiiod. --- docs/learning/tools_for_ls/index.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/learning/tools_for_ls/index.rst b/docs/learning/tools_for_ls/index.rst index 787246cacf..b5faa831c1 100644 --- a/docs/learning/tools_for_ls/index.rst +++ b/docs/learning/tools_for_ls/index.rst @@ -563,7 +563,7 @@ In this section, we will run a servo motor control demo that uses the ADALM-LSMSPG to generate position commands and read feedback, demonstrating how IIO devices can be integrated into a ROS2-based control system. -:doc:`Continue to ROS2 Integration Tutorial ` +:ref:`Continue to ROS2 Integration Tutorial ` .. toctree:: :hidden: From bf2688e02b49d0d32d1129d03b1cd25a812d2b18 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mr=2E=20Codepr=C3=BCfer?= Date: Sat, 25 Jul 2026 19:34:24 +0000 Subject: [PATCH 5/5] fixup! learning/tools_for_ls: Fill in Matlab and Zephyr sections MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Mr. Codeprüfer --- ...D5592r_NPN_Curve_Tracer.png => ad5592r_npn_curve_tracer.png} | 0 docs/learning/tools_for_ls/matlab_example/index.rst | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) rename docs/learning/tools_for_ls/matlab_example/{AD5592r_NPN_Curve_Tracer.png => ad5592r_npn_curve_tracer.png} (100%) diff --git a/docs/learning/tools_for_ls/matlab_example/AD5592r_NPN_Curve_Tracer.png b/docs/learning/tools_for_ls/matlab_example/ad5592r_npn_curve_tracer.png similarity index 100% rename from docs/learning/tools_for_ls/matlab_example/AD5592r_NPN_Curve_Tracer.png rename to docs/learning/tools_for_ls/matlab_example/ad5592r_npn_curve_tracer.png diff --git a/docs/learning/tools_for_ls/matlab_example/index.rst b/docs/learning/tools_for_ls/matlab_example/index.rst index 393407c5c0..d829e693e5 100644 --- a/docs/learning/tools_for_ls/matlab_example/index.rst +++ b/docs/learning/tools_for_ls/matlab_example/index.rst @@ -401,7 +401,7 @@ Both versions will: The resulting plot should be comparable to the one produced by the Python ``ad5592r_curve_tracer.py`` script run on the Raspberry Pi. -.. figure:: AD5592r_NPN_Curve_Tracer.png +.. figure:: ad5592r_npn_curve_tracer.png :width: 500px AD5592r NPN Curve Tracer output from MATLAB.