I've been thinking like mad about how to get good ADC samples.
We all know about oversampling, where you accumulate bits from an ADC for some number of times longer than your desired sample size, then you divide the accumulated value by your oversampling factor. It works okay, but kills bandwidth.
In our case, I noticed that for any reading up to ~4k bits, the sum always had one of four adjacent values for a fixed voltage into the ADC. I realized that this was due to the lumpiness of the bit stream coming from the ADC. It cycles state at least once every 7 clocks. Just grabbing a run of those bits to make a conversion is often like picking it up by sharp ends, since you never know when these periodic rises and falls are going to occur in the data stream, introducing a pointy bias that's costly to oversample away.
So, what if you could oversample 32 times, but on the SAME data at slightly different offsets? To do this is really simple, it turns out. It's kind of a tapered filter. It works like this on a stream of N+32 bits:
(1) Clear sample accumulator.
(2) If bit 0 high, add 1 into accumulator. (This is used by 1 offset.)
(3) If bit 1 high, add 2 into accumulator. (This is used by 2 offsets.)
(4) If bit 2 high, add 3 into accumulator. (This is used by 3 offsets.)
(5) If bits 3..31 high, add 4..32 into accumulator. (These are used by 4..32 offsets.)
(6) If bits 32..N high, add 32 into accumulator. (These are used by all offsets.)
(7) If bit N+1 high, add 32 into accumulator. Now were tapering down.
(8) If bit N+2 high, add 31 into accumulator.
(9) If bit N+3 high, add 30 into accumulator.
(10) If bit N+4..N+32 high, add 29..1 into accumulator. Finish tapering down.
(11) Shift accumulator right by 5 bits to get final sample.
This worked a complete miracle on the ADCs.
I used the streamer to read the ADC pin in on each clock cycle and assemble bytes which got dumped into hub RAM. I could peruse that data and try out different ideas.
Here is 8-bit-quality sampling of a slowly-rising signal. This detail shows 12 steps in the ADC LSBs:
Now look at the same thing, but 12-bit-quality:
Here is 12-bit-quality sampling of a steady voltage. No wandering, anymore:
Now here's 13-bit-quality sampling of a 13mV span sawtooth. At 13 bits, 1/f noise starts to become an issue:
Here's my code that runs on the P2 silicon:
' ADC tapered-filter sampling
con p = 5 'ADC pin, adjacent DAC pin is p^1
m = 30 'monitoring DAC pin for watching LSBs of conversion
s = $200 'samples/32 per reading ($7FE max)
hubset ##%1_000001_0000011000_1111_10_00 'enable crystal+PLL, stay in 20MHz+ mode
waitx ##20_000_000/100 'wait ~10ms for crystal+PLL to stabilize
hubset ##%1_000001_0000011000_1111_10_11 'now switch to PLL running at 250MHz
wrpin dacmod,#p^1 'output test level on adjacent pin DAC for ADC input
dirh #m 'set output for what will be monitoring DAC
' Get tapered sample
loop callpa adcmodp,#getsamp 'get pin sample
mov x,#0 'reset sample accumulator
mov y,#1 'taper up from 1 sample to 32 samples
shr z,#1 wc
if_c add x,y
rep #4,##s-2 'count middle samples as 32
mov y,#32 'taper down from 32 samples to 1 sample
shr z,#1 wc
if_c add x,y
shr x,#5 'divide sample accumulator by 32 to make tapered average
' Output result
shl x,#2 'magnify of LSBs
sub x,#55 'add offset (must adjust to center waveform)
setbyte dacres,x,#1 'output conversion LSBs to monitoring DAC
incmod modctr,#0 wc 'update the DAC that feeds the ADC
if_c add dacval,#$01
jmp #loop 'loop
' Get sample
getsamp wrpin pa,#p 'set ADC mode
waitx adcpre 'allow ADC to aclimate
wrfast #0,buff 'ready to have streamer write ADC bits into buffer
xinit recmode,#0 'do streamer capture of ADC bits
jnxfi #$ 'wait for streamer command finished
_ret_ rdfast #0,buff 'ready to read sample longs (lsb=first sample, msb=last)
adcmodg long %100000_0000000_00_00000_0 'ADC gio
adcmodp long %100010_0000000_00_00000_0 'ADC pin adjacent
adcmodv long %100001_0000000_00_00000_0 'ADC vio
dacmod long %10110_00000000_01_00010_0 'DAC 16-bit with random dither
dacres long %10110_00000000_00_00000_0 'DAC 8-bit
recmode long $D<<28 + p<<17 + s<<5 + $20 'streamer single-pin record command
dacval long $8000+15 'DAC value for feeding ADC
adcpre long $20 'ADC acclimation period
modctr long 0
buff long $1000 'sample buffer address in hub
x res 1
y res 1
z res 1