Ideas needed - fast ADC sampling
Andrey Demenev
Posts: 377
I have two 10-bit ADCs connected to propeller. There is a ring buffer in HUB memory. Imagine that we are given time interval Ts = 96 clocks. Within this interval, I need to find out what was maximum and minimum ADC output, for each of two ADCs separately, and store them in the buffer, then advance the buffer pointer, and start finding min and max again. The process must be interrupted when certain pin changes from 0 to 1. I need the samples (used to find min and max) to be taken as often as possible. I have 4 cogs for this. My goal is taking the samples each 16 clocks, but I am out of ideas how to achieve this ... And there is one thing that makes things even worse - ADC pins are arranged in a way that they are most easily connected to Prop in reverse order. First ADC's bits 0-9 are connected to Prop pins 9-0, second ADC's bits 0-9 are connected to Prop pins 20-11 (pin 10 is ADC clock). Ts of 96 cycles is given as an example, the smaller minimum possible Ts - the better. I can rearrange the pins to be in direct order, but that is not desirable from PCB routing POV.
Any ideas?
Any ideas?
Comments
You ought to be able to use the cog counters to generate the ADC clock
There are instructions to determine MAX and MIN in one instruction (each).
Do you mean Ts = 96 system clock cycles? That's about one sample every 1.2 us
You really have to give more detail on the timing and what you want to accomplish.
- You can probably have one cog dedicate to each ADC for reading and recording samples.
- If it is the same object for the ADC, then you may want to code the object in such a way that the pins can be defined easily.
- Of course to get identical code performance with minimal overhead both ADC should be connected in the same manner.
- You didn't mention the ADCs you are using but it sounds like they are parallel to the Prop, right?
- I am not sure what is required to take an ADC reading, but you can possible interleave two cogs on each ADC to get more samples. While one cog is writing to hub ram the other one is taking note of the reading from the ADC.
▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔Timothy D. Swieter, E.I.
www.brilldea.com - Prop Blade, LED Painter, RGB LEDs, 3.0" 16:9 LCD Composite video display, eProto for SunSPOT, PropNET, PolkaDOT-51
www.tdswieter.com
The task is to examine waveforms (kinda oscilloscope), and the problem I am trying to solve is usualy referred as "aliasing". Attachment illustrates the problem and possible improvement to sampling procedure.
Edit:· I re-read your first post, and I assume the problem is that the assembly takes too many cycles in one cog.· You should be able to use multiple cogs to do this.· The first cog reads the first 16 samples, the second cog reads the next 16 samples, and so on.· After reading 16 sample, a cog will then do the bit reversals and min and max computations.· It then stores the results in hub memory, and then starts reading another 16 samples.· The multiple cogs have to coordinate so they work on the proper samples.
Edit 2: I'm guessing you can do this with two cogs.· You could use a lock to synchronize them.
Post Edited (Dave Hein) : 4/1/2010 1:53:53 PM GMT
An alternative would be to trigger multiple acquisitions at a threshold value (like scope triggering) so the readings occur at the same point in each cycle.
In both cases it would probably be necessary to store some timing information with the adc data.
I'm with the other posters that a bit more info would help.
If the time between consecutive samples from one ADC is 96 clocks, that's 24 ASM instructions (as long as they are not hub instructions or wait or jmp). If you are able to read the 10bit sample (on 10 different pins I assume) in one instruction then reverse it as per Mike's suggestion it seems you should have time to put it into HUB memory and (maybe) compare it to the current Max and Min. If there isn't time to do this part, it seems like you have a free cog to do that.
1 cog for ADC 1
1 cog for ADC 2
1 cog for keeping track of Max/Min
Do you need to keep track of the max and min for each ADC channel or just the combined max/min.
Do you have a (commented) first draft of the code for us to look at?
Peter
After reading your post it's clear that you need to keep track of the max and min for each ADC. If a value is not a new max or min do you need to keep it?
Post Edited (pgbpsu) : 4/1/2010 3:40:24 PM GMT
Ts of 96 cycles is given just an example - in the following code you can see that it depends on num_cycles variable
This is the best draft I have so far:
Samples to calculate MIN/MAX are taken each 32 cycles. MIN/MAX is only calculated for one ADC. MIN/MAX is written to memory each (num_cycles * 160) cycles. When running this on 2 interleaved cogs, it would get samples each 16 cycles, and write to memory each Ts = (num_cycles * 80) cycles. The code for secong COG should be modified a bit, so samples are taken with 16 clock cycles delay relative to first COG (basically, that is moving WRLONG to a different place). And another 2 cogs would do the same for another ADC
The code has a problem though: last sample within each Ts interval is lost (the one labelled 139)
The main question is: is it possible to take samples at least as often as each 16 cycles? Remember they must be equally spaced
I promise I'm not trying to be boneheaded about this but I'm still not following. For the moment let's leave out the second ADC channel. Do you want:
1. to read the input from and ADC as fast as possible and only pass up to HUB new MAX/MIN values
OR
2. to read a certain number of samples from the ADC (say 16- this is determined by Ts) and pass up the max and min of those 16, then reset the max/min within the ADC cog and read another 16 evenly spaced samples? I assume that the delta T between last sample in first group and first sample in second group needs to match the delta between samples within a group. Am I starting to get the right picture?
It sounds like you want to do 2.
Is the MIN/MAX a successive approximation attempt to fine tune the result? Just trying to see the picture here.
▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
Beau Schwabe
IC Layout Engineer
Parallax, Inc.
ADC sample rate is let's say 1uSec.
Because of memory limitations, he stores every 16th sample as a display point. (16uSec rate)
Because the display points could get aliased, he stores the highest and lowest ADC sample for each display point. (2 values for each display point).
When he draws the display, instead of a single point he will draw a vertical line to show the lowest and highest sampled value for that display point (which consists of 16 ADC sample points).
He then uses only 1/8 the memory required had he stored ALL the ADC samples.
That is my best guess anyway. Neat idea.
Bean
▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Use BASIC on the Propeller with the speed of assembly language.
PropBASIC thread http://forums.parallax.com/showthread.php?p=867134
March 2010 Nuts and Volts article·http://www.parallax.com/Portals/0/Downloads/docs/cols/nv/prop/col/nvp5.pdf
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
There are two rules in life:
· 1) Never divulge all information
To keep all your sampling even, I think you'll need to use a waitcnt or two. Maybe something like the following:
This is a start. It doesn't deal with wrap around on your ring buffer and you'll have to do some math to figure out what cntsBtwSamps needs to be but this is how I'd start. I suppose you could keep maxValPtr and minValPtr updated by another cog. Then you'd have to insert a rdlong for each of those into this loop. If not, you'll need to do the addition here and wrap around. I think 3 more instructions would do it.
Is this even close?
Keep in mind that, unless you keep track of when your MAX and MIN samples arrived, you'll lose timing info. To put it another way, the spacing on the time axis no longer has to be even. You may capture amplitude information but at the expense of time. That's certainly a trade off worth making in some situations.
p
What is the datasheet on the 10-bit ADCs that you are using? I was thinking something along a pipeline architecture, but each stage would be a COG staggered in time.
The other idea with MIN/MAX would involve more intimate communication with the ADC in a successive approximation mode. There are about 3 different successive approximations architectures that I know of. linear ramping, binary ramping, and predictive ramping the latter heavily relying on MIN MAX values to determine the next 'pivot' point for a comparator.
▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
Beau Schwabe
IC Layout Engineer
Parallax, Inc.
··· mov samplea, ina
··· mov sampleb, samplea
··· rev samplea, #32-10
··· shr sampleb, #11
··· rev sampleb, #32-10
··· min maxvala, samplea
··· max minvala, samplea
··· min maxvalb, sampleb
··· max minvalb, sampleb
This is 36 cycles per samples, so you will need 3 cogs to achieve 16 cycles per sample.· There might be a way to squeeze this into two cogs, but I think the loop overhead and hub acceses will push you into 3 cogs.
You could interleave the three cogs on a sample-by-sample basis, which means that each cog would be 16 cyles behind the previous cog.· However, I think it would be easier if you interleaved them in groups of 6 samples.
A cog would grab 6 pairs of samples and perform part of the processing while acquiring samples, and then perform the rest of the processing after getting all 6 samples.· The next cog would start acquiring samples while the previous cog is working on the previous 6 samples.· The third cog would follow the second.· It would look something like the following:
loop
'·· Acquire and partially process 6 pairs of samples
··· mov samp01a, ina
··· mov samp01b, samp01a
··· rev samp01a, #32-10
··· shr samp01b, #11
··· mov samp02a, ina
··· mov samp02b, samp01a
··· rev samp02a, #32-10
··· shr samp02b, #11
··· mov samp03a, ina
··· mov samp03b, samp01a
··· rev samp03a, #32-10
··· shr samp03b, #11
··· mov samp04a, ina
··· mov samp04b, samp01a
··· rev samp04a, #32-10
··· shr samp04b, #11
··· mov samp05a, ina
··· mov samp05b, samp01a
··· rev samp05a, #32-10
··· shr samp05b, #11
··· mov samp06a, ina
··· mov samp06b, samp01a
··· rev samp06a, #32-10
··· shr samp06b, #11
'·· Complete processing of 6 pairs of samples
··· rev samp01b, #32-10
··· min maxvala, samp01a
··· max minvala, samp01a
··· min maxvalb, samp01b
··· max minvalb, samp01b
··· rev samp02b, #32-10
··· min maxvala, samp02a
··· max minvala, samp02a
··· min maxvalb, samp02b
··· max minvalb, samp02b
··· rev samp03b, #32-10
··· min maxvala, samp03a
··· max minvala, samp03a
··· min maxvalb, samp03b
··· max minvalb, samp03b
··· rev samp04b, #32-10
··· min maxvala, samp04a
··· max minvala, samp04a
··· min maxvalb, samp04b
··· max minvalb, samp04b
··· rev samp05b, #32-10
··· min maxvala, samp05a
··· max minvala, samp05a
··· min maxvalb, samp05b
··· max minvalb, samp05b
··· rev samp06b, #32-10
··· min maxvala, samp06a
··· max minvala, samp06a
··· min maxvalb, samp06b
··· max minvalb, samp06b
.·· Write the min and max to the output buffer
··· shl minmaxa, maxvala
··· or· minmaxa, minvala
··· wrlong minmaxa, bufptra
··· add bufptra, #4
··· shl minmaxb, maxvalb
··· or· minmaxb, minvalb
··· wrlong minmaxb, bufptrb
··· add bufptrb
··· jmp #loop
I didn't include the code that syncs to the ADC or the other two cogs.· There are various ways to handle that.
Dave
If my description is not very clear, read Bean's post - he describes the basic idea pretty well.
I'll try to re-phrase. We have 2 time intervals: sampling interval t and storing interval T. Each t, we take ADC sample. Each T we calculate min and max of samples aquired during this T, and store them into ring buffer. T/t is not constant, it should be configurable - see num_cycles in my example code. The smaller t - the better. The smaller minimum possible T/t - the better. "Sample as often as possible, store as often as buffer space permits"
I do not clear lower bits when finding min/max for ADC bits that are in higher bits - since this does not change final result. This saves few instructions though. Complete loop is 234 clock cycles max, and WAITCNT extends it to 256 clocks. When run on 4 COGs, this gives samples spaced exactly 16 clock cycles from each other, and memory write each (64*num_cycles_reload) clock cycles
Any comments? Would this work? Or I am missing something?
Post Edited (Andrey Demenev) : 4/2/2010 12:40:30 PM GMT
I'm confused about the definitions of T and t.· I assume they are the number of cycles between min/max values and the number of cycles between raw samples.· In your original post these were 96 and 16, which gives a ratio of T/t = 6.· In your latest code you show a T/t = 4.· You said you wanted this to be variable, but your latest code is fixed.· The only way to make it variable is to use self-modifying code.· You could use a base code image that handles the largest T/t you would use, and then overwrite it with jumps to reduce it to a smaller T/t.
Your code doesn't synchronize multiple cogs, or accurately synchronize to the ADC.· You should be able to use I/O pins and waitpeq/waitpne to do the synchronization.· Can you provide information on the ADC your using?· It would help to determine how you should synchronize to it.
Dave
Post Edited (Dave Hein) : 4/2/2010 1:38:35 PM GMT
T/t is variable. Note this:
and then lots of IF_Z - these do the trick. Although the min/max are written to hub memory, the buffer pointers are not advanced if --num_cycles != 0. After it becomes zero, pointers are advanced, min/max reset, and num_cycles reloaded.
This is just the part of code doint the actual sampling. Before the sampling starts, I will start counter in one of 4 COGs to generate the clock. Synchronization is not a problem - that is just a matter of entering the loop at right time. All 4 COGs will be waiting for 5th COG to write 1 co certain memory location, then waitcnt with "personal" delay for each COG will do the trick.
I was hoping someone can have an idea how to make this to sample each 12 clock cycles, keeping minimum T/t reasonable (6 at most), but looks like it is not possible ...
With only 4 cogs, I believe you'd have to do something like >= 10 samples in a block, to keep under your time budget. If you can cut down on the processing time per sample, that would lower the minimum number of samples you'd have to run in a block.
Note the NOP in the recording loop...this is a bit counter-intuitive, so maybe I messed it up...but, I think if you sample every 12 clocks instead of 16, then you need to target an average of 3 instructions per sample, which would require > 4 cogs to do.
Does that make sense, or am I missing something obvious?
Jonathan
▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
lonesock
Piranha are people too.
1. my code has lower minimum T/t of 4, while yours - 9 (and possibly 10, when ring buffer boundary and stop condition checks are added)
2. your code allows more high-grained control over T/t - in steps of 1, while mine - in steps of 4