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