Getting Stimulation Electrode Impedances

Hi all,

As we pilot the PG4 for stimulation studies, I was wondering if there was a way to send back stimulation electrode impedance measures. I’ve dug through the NNP-API, and can’t find a direct way to get this. I think I would just need impedances at a single frequency to track over time. Thank you!

Thanks for the question, Brandon! The PG4 does not have this feature; however, there are indirect ways to determine electrode connectivity and impedance.

Current Regulation Read Out

First you can look at whether or not pulses were delivered “In Regulation.”

Using the profiler(nnp) app

There is a checkbox at the bottom left labeled “Regulation”. If checked, you can click the Update Status button to check if the pulses are regulated (i.e. if the compliance voltage is sufficient to source the current requested through the tissue load). You can see changes in this view by conducting stimulation with a resistor array (1: In Regulation) and without a resistor array (0: Out of Regulation) at the tissue load component (JP601 on the frame board of the current development kit).

Using NNP-API in command line

Read the PG4 Object Dictionary entry at index 3210.5 with nnp.read(node, '3210', 5). This returns a 4 byte array indicating whether the last pulse on each channel was delivered in regulation.

0 = out of regulation
1 = in regulation
2 = invalid (pulse is < 10us so regulation cannot be determined).
Note: It is recommended to set the value to a number other than 0-2 (e.g. 3) prior to stimulating and reading to make sure that it changes. Use nnp.write(node, '3210', 5, [3 3 3 3]) to change all 4 channel values to 3.

The compliance voltage (VOS) maintained at the output stage of the PG4 module is stored in the Object Dictionary at indices 3210.3 and 3210.4. You can read the values with nnp.read(node, '3210', <3/4>, 'uint16') and change the values with nnp.write(node, '3210', <3/4>, value, 'uint16'). It is recommended to set them both to the same value. The default is the maximum of 2040 which represents 34V (multiplied by 60). Regardless of how low VOS is set, it cannot functionally be lower than VIN, the voltage recovered from the network. Reminder: VIN will be slightly lower than the network voltage (VNET).

Estimating Impedance

To estimate impedance, you can reduce the module VOS until the pulse is Out of Regulation to determine a “minimum compliance voltage”. If you just want a relative measure of change of electrode-tissue interface over time, you can track if the minimum compliance voltage changes over time.

If you want an absolute measurement in ohms, you should take into account that the effective compliance voltage is actually 4.6V lower than the specified compliance voltage because of the Zener diodes in the output stage.

Note: Whether or not the pulses are in regulation depends on the amplitude and tissue load obviously, but it will also depend several other parameters. Therefore, if you are using the minimum compliance voltage as an indication of tissue health, electrode integrity, encapsulation, etc., make sure that you keep as many parameters fixed as possible, i.e. fixed PW, PA, freq, network voltage, single channel stimulating. If the network voltage is lower or the stimulation demand is high, the boost converter attempting to maintain the compliance voltage will struggle more and the effective compliance may sag below what is set.

I was thinking through how a MATLAB script could be written to find the minimum compliance voltage and then estimate an impedance. It would iteratively (1) step down the compliance voltage of your stimulation channel in mind, (2) conduct stimulation at the channel, and then (3) check the “Regulation” index each iteration until you return a 0 for Out of Regulation stimulation. This could be used to determine your minimum compliance voltage for a session and which then feeds into an estimated impedance calculation. It considers the voltage drop across the Zener diode and the capacitor in the channel output. Check this out this draft of a script and let me know what you think. I am noticing an offset of about 250 Ω that I have not figured out yet fyi.

On the dev board, this requires either having the resistor array by the LEDs removed or having every connection made (at the bottom of this picture). You can also add additional load at the electrodes connectors (at the top of this picture). This routes the current only through the main output and not through the LEDs.

% measureImpedance.m
% Estimates electrode impedance on PG4 channel 1 by stepping VOS down
% until the pulse is Out of Regulation, then applies Ohm's Law.
%
% Reference: 
% https://community.cosmiic.org/t/getting-stimulation-electrode-impedances/14/2
% https://docs.cosmiic.org/Software/
%
% USAGE
%   Run after creating and connecting an NNPHELPERS object, e.g.:
%     nnp = NNPHELPERS('COM3');
%     run measureImpedance

% =========================================================================
%  USER SETTINGS
% =========================================================================
node        = 3;      % PG4 node ID
channel     = 1;      % Channel to test (1–4); subindex into reg status array
PA          = 140;    % Pulse amplitude (PA×10, so 160 = 16.0 mA)
PW          = 255;    % Pulse width (µs)
freq        = 25;     % Frequency (Hz)

VOS_start   = 2040;   % Starting VOS raw value (2040 = 34 V, system max)
VOS_step    = 5;      % Step size in raw units (6 = 0.1 V)
VOS_min     = 60;     % Lower bound (60 = 1 V); loop will stop here if needed

ZENER_OFFSET_V = 4.6;

% Cap correction — one 1uF charge-balancing caps in series with CH1 path
% (C42 on CH1 output; see PG4 schematic pg8)
C_bal  = 1e-6;        % Capacitance per cap (F)
% =========================================================================

% --- Enter test stim mode and set stim parameters ------------------------
nnp.enterWaiting;
nnp.setSync(1000/freq);
nnp.networkOn;
nnp.enterTestStim;
fprintf('Entering test stim mode on node %d...\n', node);
pause(0.2);

nnp.write(node, '3212', channel, uint8([PW PA]));
fprintf('Stim set: Ch%d | PW=%dµs | PA=%.1fmA\n\n', channel, PW, PA/10);

% --- Initialize VOS to maximum -------------------------------------------
nnp.write(node, '3210', 3, VOS_start, 'uint16');
nnp.write(node, '3210', 4, VOS_start, 'uint16');
pause(0.1);

fprintf('  %-14s  %-12s  %s\n', 'VOS (raw)', 'Vcomp (V)', 'Regulation Ch1');
fprintf('  %s\n', repmat('-', 1, 44));

% --- Sweep loop ----------------------------------------------------------
VOS_current      = VOS_start;
VOS_last_in_reg  = NaN;

while VOS_current >= VOS_min

    % Reset regulation flag to sentinel (3) before stimulating
    nnp.write(node, '3210', 5, [3 3 3 3]);
    pause(0.05);

    % Allow one full pulse period for the flag to update after stimulating
    pause(0.1);

    % Read regulation status — 4-byte array, one value per channel
    reg_all = nnp.read(node, '3210', 5);
    reg_ch  = reg_all(channel);   % 0 = out of reg, 1 = in reg, 2 = invalid

    VOS_V = (VOS_current / 60);

    if reg_ch == 2
        reg_str = 'Invalid (<10µs)';
    elseif reg_ch == 1
        reg_str = 'In Regulation';
        VOS_last_in_reg = VOS_current;
    elseif reg_ch == 0
        reg_str = 'OUT OF REGULATION';
    else
        reg_str = sprintf('No update (%d)', reg_ch);  % sentinel did not change
    end

    fprintf('  %-14d  %-12.2f  %s\n', VOS_current, VOS_V, reg_str);

    % Stop once out of regulation
    if reg_ch == 0
        break;
    end

    % Step VOS down and apply to both OD entries
    VOS_current = VOS_current - VOS_step;
    nnp.write(node, '3210', 3, VOS_current, 'uint16');
    nnp.write(node, '3210', 4, VOS_current, 'uint16');
    pause(0.05);

end

% --- Stop stimulation ----------------------------------------------------
nnp.write(node, '3212', channel, uint8([0, 0]));
nnp.enterWaiting();

% Restore VOS to maximum
nnp.write(node, '3210', 3, VOS_start, 'uint16');
nnp.write(node, '3210', 4, VOS_start, 'uint16');

% --- Calculate impedance -------------------------------------------------
fprintf('\n');
if isnan(VOS_last_in_reg)
    warning('Channel never achieved regulation. Check connections or increase VOS_start.');
else
    PA_A       = (PA/10) / 1000;                         % Convert PA×10 units → Amps

    % Voltage accumulated on series charge-balancing caps at end of pulse
    V_cap = PA_A * (PW * 1e-6) / C_bal;
    
    VOS_output = VOS_last_in_reg / 60; %VOS in Volts
    Z = (VOS_output - V_cap - ZENER_OFFSET_V) / PA_A; % Effective compliance = (VOS_raw/60) - 4.6 V - V_cap

    fprintf('Minimum regulated VOS  : %d raw (%.2f V effective)\n', VOS_last_in_reg, VOS_output);
    fprintf('Programmed current     : %.2f mA\n', PA/10);
    fprintf('Cap voltage correction : %.3f V (%.0f Ω equivalent)\n', V_cap, V_cap/PA_A);
    fprintf('Estimated impedance    : %.0f Ω  (%.2f kΩ)\n', Z, Z/1000);
end