What I'm building
A photodiode sensor array read through ADG726 16:1 analog multiplexers into an STM32F446ZE (ADC2, channel 10 / PB0).


Per channel: a photodiode current is converted and amplified by a transimpedance amplifier (TIA) and the resulting voltage is held on a capacitor. That stored analog voltage is the per-channel signal fed to a MUX input. The TIA output node (the point where the voltage spike appears) is labelled ASOA and connects directly to the MUX input channel. The MUX common output goes to the ADC.
Scan method: I step the MUX address, allow a settle time, then read the ADC, then move to the next channel.
Scale: 64 sensor inputs total → 4 × 16:1 MUX (2 MUXes per axis, X and Y). Every MUX shows the same pattern at the same channel position — a spike on one channel produces the same characteristic trend across the others, identically across all four MUXes. This repeatability is a key clue.
MUX control pins: EN, CSA, CSB, WR all tied to GND.
The problem — illustrated

Image 1 (green bars): Only channel 8 has an obstacle on it (covered → high). It should be the ONLY high bar; every other channel should sit at the low baseline shown by the blue bars at the far ends. Instead, channel 8 lifts the channels around and after it into a rising trend — many channels go high in a pattern, not just channel 8.

Image 2: Only the last channel (channel 15) has the spike. But the initial channels are also pulled up — the influence appears across positions, not just at the spiked channel. The yellow dashed line is the baseline; almost everything sits above it.
In both cases: one channel carrying a real high signal pulls many other channels up, in a repeatable, non-random pattern.
What I changed and how the trend moved
The one clue I have is that switching speed / switching pattern affects it. The worst-affected positions were channel 8 and channels 1/15 — i.e. the addresses where the MUX select lines flip the most bits at once. So I switched from binary order to Gray-code order (only one select-line bit changes per step). This shifted the trend to different channels — but did not remove it.
|
Change |
Result |
|---|---|
|
Binary switch order, direct register writes |
Trend present; worst at ch8, ch1, ch15 (most select-bit flips) |
|
Register-write method → HAL HAL_GPIO_WritePin |
Affected channels shifted |
|
Gray-code order (one select bit per step) |
Trend shifted again to different channels — still present |
|
Added settle delay (up to 1 second between switches) |
No change — trend still present |
Summary: changing scan order moves which channels are affected, but never removes the effect. Adding even a 1-second delay between switches changes nothing.
Extra test (only 2 channels enabled)
- Cover channel 1 → channel 2 rises ~400 counts.
- Cover channel 2 → channel 1 rises ~700 counts (asymmetric).
- Both persist with a full 1-second delay between switches.
Read code (simplified polling version)
This is the exact version the 1-second-delay test was run on. MUX address is set, a settle delay is applied, then a single ADC conversion is read by polling.
static inline void setMux(uint8_t n)
{
HAL_GPIO_WritePin(GPIOD, GPIO_PIN_10, (n & 0x01) ? GPIO_PIN_SET : GPIO_PIN_RESET);
HAL_GPIO_WritePin(GPIOD, GPIO_PIN_11, (n & 0x02) ? GPIO_PIN_SET : GPIO_PIN_RESET);
HAL_GPIO_WritePin(GPIOD, GPIO_PIN_12, (n & 0x04) ? GPIO_PIN_SET : GPIO_PIN_RESET);
HAL_GPIO_WritePin(GPIOD, GPIO_PIN_13, (n & 0x08) ? GPIO_PIN_SET : GPIO_PIN_RESET);
}
static uint16_t readADC(void)
{
HAL_ADC_Start(&hadc2);
HAL_ADC_PollForConversion(&hadc2, 10);
uint16_t v = (uint16_t)HAL_ADC_GetValue(&hadc2);
HAL_ADC_Stop(&hadc2);
return v;
}
/* main loop */
for(uint8_t i = 0; i < 16; i++)
{
setMux(i);
delay_us(MUX_SETTLE_US); /* tested 20us up to 1 second - no change */
adc[i] = readADC();
}
Steps to reproduce
- Power the board; let all channels settle at baseline (blue level).
- Place an opaque obstacle over exactly ONE channel (e.g. channel 8) so its stored voltage goes high.
- Scan all 16 channels through the MUX and plot the per-channel ADC values.
- Observe: instead of one isolated high bar, a rising trend lifts multiple channels around/after the covered one.
- Repeat with the obstacle on channel 1 and on the last channel — the trend shifts but persists.
- Increase the settle delay up to 1 second — no change. Switch binary → Gray-code order — trend shifts but persists.
What confuses me
- If it were sample/hold residue or settling, a 1-second delay should kill it. It doesn't.
- One channel pulls up many others in a pattern → looks like a shared node, not per-channel.
- Changing scan order moves which channels are affected → scan-order dependent, not physical-neighbour dependent.
- Worst positions are those with the most select-line bit flips → why Gray code changed (but didn't fix) the pattern.
My question
Is this a hardware problem (shared Vref/supply sag, shared MUX common-output node not discharging between channels, ground-return coupling, charge held on the storage cap with no discharge path) or a firmware/scan problem? One channel lifting all others in a repeatable, scan-order-dependent pattern points me toward a shared node, but I want to confirm before reworking the board.