On Timers & ADC

classic Classic list List threaded Threaded
3 messages Options
jbsnyder jbsnyder
Reply | Threaded
Open this post in threaded view
|

On Timers & ADC

So I've been thinking about how to accomplish burst sampling at a  
given rate (or background sampling).  The Stellaris ADCs are  
programmable in terms of selecting a sampling rate, but the range is  
pretty limited, and fairly fast (125 ksamples/s - 1 msample/s).  In  
order to do more arbitrary rates, I'll need to use a timer.  The issue  
I see is: what if I'm already using a timer, or multiple timers for  
some purpose?  In cases where something like this is needed should  
timers be reserved/locked?

I've not worked with all the other supported platforms, but from  
working with some other microcontrollers I think a timer will be  
needed.  In going with how things are already implemented  
(platform_uart_recv), I'm going to assume this is how I'll have to  
adjust things.  As such, I suppose the ADC API should be amended a  
bit, as follows:

void platform_adc_burst( unsigned id, u16* buf, unsigned count,  
unsigned timer_id, u32 frequency );

The current API then looks like this:

int platform_adc_exists( unsigned id );
u16 platform_adc_sample( unsigned id );
u16 platform_adc_maxval( unsigned id );
void platform_adc_start( unsigned id );
int platform_adc_is_done( unsigned id );
void platform_adc_set_mode( unsigned id, int mode );
void platform_adc_burst( unsigned id, u16* buf, unsigned count,  
unsigned timer_id, u32 frequency );

If continuous mode sampling is to run at a rate that isn't in the  
range of 125 ksamples to 1 msamples, we might either need to have the  
set_mode function accept a timer (which is less useful in the case of  
doing single-shot, but timers can be used that way :-)), or have  
another function like burst, but that isn't blocking, and has a  
corresponding stop function.

I'm also going to add a smoothing option, which in the context of  
single shot or continuous simply means that  enough samples must be  
averaged before the result is_done.  For burst, we could do this too,  
either as oversampling (i.e. each burst sample is actually 5 averaged  
values, that are available because we're actually sampling at 5 times  
the rate passed to adc_burst) or with a moving average filter.  For  
single shot, these would be the same, but they differ when we're  
talking about continuous or burst sampling.  One requires that  
filter_length samples be collected prior to each averaged sample being  
released, and the other runs at the sampling rate, as soon as  
filter_length samples are collected so that the moving average has  
enough data. Hmm...

The stellaris platform has hardware averaging, though it is designed  
for being used for oversampling, not just rolling averages.

This article does a decent job of outlining the issues:
http://www.zlgmcu.com/luminary/download/LM3SAPP_ADC_OverSample_Tech_en.pdf


I think for now I will implement smoothing as follows, using a moving  
average filter:
void platform_adc_set_smoothing( unsigned id, u8 length );

--
James Snyder
Biomedical Engineering
Northwestern University
jbsnyder at fanplastic.org
http://fanplastic.org/key.txt
ph: (847) 644-2322

-------------- next part --------------
An HTML attachment was scrubbed...
URL: https://lists.berlios.de/pipermail/elua-dev/attachments/20090122/4b055750/attachment.html 
-------------- next part --------------
A non-text attachment was scrubbed...
Name: PGP.sig
Type: application/pgp-signature
Size: 194 bytes
Desc: This is a digitally signed message part
Url : https://lists.berlios.de/pipermail/elua-dev/attachments/20090122/4b055750/attachment.pgp 

BogdanM BogdanM
Reply | Threaded
Open this post in threaded view
|

On Timers & ADC

> So I've been thinking about how to accomplish burst sampling at a given
rate
> (or background sampling).  The Stellaris ADCs are programmable in terms of
> selecting a sampling rate, but the range is pretty limited, and fairly
fast
> (125 ksamples/s - 1 msample/s).  In order to do more arbitrary rates, I'll
> need to use a timer.  The issue I see is: what if I'm already using a
timer,
> or multiple timers for some purpose?  In cases where something like this
is
> needed should timers be reserved/locked?

In the future, we'll most likely go with Mike's lock/unlock suggestion, so
this will be automatic. For now the user should manage the resources
himself.

> I've not worked with all the other supported platforms, but from working
> with some other microcontrollers I think a timer will be needed.  In going
> with how things are already implemented (platform_uart_recv), I'm going to
> assume this is how I'll have to adjust things.

Precisely :)

> As such, I suppose the ADC API should be amended a bit, as follows:
> void platform_adc_burst( unsigned id, u16* buf, unsigned
count, unsigned timer_id, u32 frequency );

Right.

> The current API then looks like this:
> int platform_adc_exists( unsigned id );
> u16 platform_adc_sample( unsigned id );
> u16 platform_adc_maxval( unsigned id );
> void platform_adc_start( unsigned id );
> int platform_adc_is_done( unsigned id );
> void platform_adc_set_mode( unsigned id, int mode );
> void platform_adc_burst( unsigned id, u16*
buf, unsigned count, unsigned timer_id, u32 frequency );

The current approach is to have the "common" functions as separate function,
and the other grouped in a common function (see platform_timer_op,
platform_pwm_op ...) just to reduce the number of actual functions one needs
to implement for a given backend (this looks less scary for someone that
wants to port to a new backend :) ). In this case I'd make the exists,
sample, start and is_done function independent, and put everything else in a
platform_adc_op. It's not a requirement though, if this looks better to you
then go for it, I'll leave it up to you.

> If continuous mode sampling is to run at a rate that isn't in the range of
> 125 ksamples to 1 msamples, we might either need to have the set_mode
> function accept a timer (which is less useful in the case of doing
> single-shot, but timers can be used that way :-)), or have another
function
> like burst, but that isn't blocking, and has a corresponding stop
function.

What about this: make "set_mode" specify one more thing: blocking or
non-blocking. After that, both sample and burst would work in that mode.
This would eliminate the need for "start", and would also make "burst"
blocking when needed. And the timer would always be specified in "burst", so
we don't need as a parameter to "mode".
By the way, do we really need the "continuous" mode? Maybe we can specify
"0" as a parameter to platform_adc_burst (which would mean "do this at the
maximum speed") and forget about the continous mode altogether. And then
"set mode" would just set the blocking or non-blocking mode. Seems more
"natural" this way.

> I'm also going to add a smoothing option, which in the context of single
> shot or continuous simply means that  enough samples must be averaged
before
> the result is_done.  For burst, we could do this too, either as
oversampling
> (i.e. each burst sample is actually 5 averaged values, that are available
> because we're actually sampling at 5 times the rate passed to adc_burst)
or
> with a moving average filter.  For single shot, these would be the same,
but
> they differ when we're talking about continuous or burst sampling.  One
> requires that filter_length samples be collected prior to each averaged
> sample being released, and the other runs at the sampling rate, as soon as
> filter_length samples are collected so that the moving average has enough
> data. Hmm...

It shouldn't be diferent. Everything is oversampled by x, and every x sample
you return an actual result to the system. This makes more sense if we adopt
the frequency=0 convention I mentioned above.

> The stellaris platform has hardware averaging, though it is designed for
> being used for oversampling, not just rolling averages.

But isn't the functionality similar? Or they just do a simple averaging in
LM3S, instead of a moving average? (sorry, way too sleepy to check now).

> This article does a decent job of outlining the issues:
> http://www.zlgmcu.com/luminary/download/LM3SAPP_ADC_OverSample_Tech_en.pdf

I'll try to take a look at that. What I know for sure is that a 16x or even
8x moving average filter can work wonders, so we'd better have that, even if
it involves interrupts.
A little homework for you :): try to figure out a way to have adc_burst mode
work (optionally) in double buffering mode. It's really useful for lots of
applications.

> void platform_adc_set_smoothing( unsigned id, u8 length );
Agreed. And length should probably be a power of 2 to make things (like
modulo and division operations) faster.

Best,
Bogdan
-------------- next part --------------
An HTML attachment was scrubbed...
URL: https://lists.berlios.de/pipermail/elua-dev/attachments/20090123/311e93d1/attachment.html 

jbsnyder jbsnyder
Reply | Threaded
Open this post in threaded view
|

On Timers & ADC


On Jan 23, 2009, at 1:28 AM, Bogdan Marinescu wrote:

> > So I've been thinking about how to accomplish burst sampling at a  
> given rate
> > (or background sampling).  The Stellaris ADCs are programmable in  
> terms of
> > selecting a sampling rate, but the range is pretty limited, and  
> fairly fast
> > (125 ksamples/s - 1 msample/s).  In order to do more arbitrary  
> rates, I'll
> > need to use a timer.  The issue I see is: what if I'm already  
> using a timer,
> > or multiple timers for some purpose?  In cases where something  
> like this is
> > needed should timers be reserved/locked?
>
> In the future, we'll most likely go with Mike's lock/unlock  
> suggestion, so this will be automatic. For now the user should  
> manage the resources himself.
>
> > I've not worked with all the other supported platforms, but from  
> working
> > with some other microcontrollers I think a timer will be needed.  
> In going
> > with how things are already implemented (platform_uart_recv), I'm  
> going to
> > assume this is how I'll have to adjust things.
>
> Precisely :)
>
> > As such, I suppose the ADC API should be amended a bit, as follows:
> > void platform_adc_burst( unsigned id, u16* buf, unsigned count,  
> unsigned timer_id, u32 frequency );
>
> Right.
>
> > The current API then looks like this:
> > int platform_adc_exists( unsigned id );
> > u16 platform_adc_sample( unsigned id );
> > u16 platform_adc_maxval( unsigned id );
> > void platform_adc_start( unsigned id );
> > int platform_adc_is_done( unsigned id );
> > void platform_adc_set_mode( unsigned id, int mode );
> > void platform_adc_burst( unsigned id, u16* buf, unsigned count,  
> unsigned timer_id, u32 frequency );
>
> The current approach is to have the "common" functions as separate  
> function, and the other grouped in a common function (see  
> platform_timer_op, platform_pwm_op ...) just to reduce the number of  
> actual functions one needs to implement for a given backend (this  
> looks less scary for someone that wants to port to a new  
> backend :) ). In this case I'd make the exists, sample, start and  
> is_done function independent, and put everything else in a  
> platform_adc_op. It's not a requirement though, if this looks better  
> to you then go for it, I'll leave it up to you.

Yeah, I was starting to feel that I might want to do that once the  
number of functions was growing...   I'll switch over to that for the  
next set of feature update.

>
>
> > If continuous mode sampling is to run at a rate that isn't in the  
> range of
> > 125 ksamples to 1 msamples, we might either need to have the  
> set_mode
> > function accept a timer (which is less useful in the case of doing
> > single-shot, but timers can be used that way :-)), or have another  
> function
> > like burst, but that isn't blocking, and has a corresponding stop  
> function.
>
> What about this: make "set_mode" specify one more thing: blocking or  
> non-blocking. After that, both sample and burst would work in that  
> mode. This would eliminate the need for "start", and would also make  
> "burst" blocking when needed. And the timer would always be  
> specified in "burst", so we don't need as a parameter to "mode".
> By the way, do we really need the "continuous" mode? Maybe we can  
> specify "0" as a parameter to platform_adc_burst (which would mean  
> "do this at the maximum speed") and forget about the continous mode  
> altogether. And then "set mode" would just set the blocking or non-
> blocking mode. Seems more "natural" this way.

That sounds good.

Regarding continuous mode, I have two thoughts.  As far as the "do  
this as fast as possible" burst mode is considered, I think that  
sounds good.  The other thought here is that "continuous" mode can  
also be thought of as "background sampling mode" where ADC conversions  
run at whatever rate you chose and dump a new sample into a buffer  
whenever ready.  This could be nice in situations where a sampling  
rate might not be terribly high, or if I want to drive an application  
to update something whenever new data is available.  Say, I have a  
scope, and I want to display the last 50 samples, or something  
similar.  I can update my display whenever I notice that there is a  
new sample to be added.  I suppose something similar could be done on  
the Lua side with a timer and sample being used in single-shot,  
though. I'm not sure how the overhead compares, but what you suggest I  
think might be more straightforward for an end user.  I'll think about  
this, and maybe make some benchmarks.

> > I'm also going to add a smoothing option, which in the context of  
> single
> > shot or continuous simply means that  enough samples must be  
> averaged before
> > the result is_done.  For burst, we could do this too, either as  
> oversampling
> > (i.e. each burst sample is actually 5 averaged values, that are  
> available
> > because we're actually sampling at 5 times the rate passed to  
> adc_burst) or
> > with a moving average filter.  For single shot, these would be the  
> same, but
> > they differ when we're talking about continuous or burst  
> sampling.  One
> > requires that filter_length samples be collected prior to each  
> averaged
> > sample being released, and the other runs at the sampling rate, as  
> soon as
> > filter_length samples are collected so that the moving average has  
> enough
> > data. Hmm...
>
> It shouldn't be diferent. Everything is oversampled by x, and every  
> x sample you return an actual result to the system. This makes more  
> sense if we adopt the frequency=0 convention I mentioned above.

Hmm.. so are you suggesting that smoothing do just oversampling or a  
moving average, or that we have options for both?

I think for lower sampling rates (not near the maximum rate) with  
short windows (x8, x16), oversampling makes a lot of sense, especially  
where there's a hardware averager ;-)  When it comes to other  
platforms, whether you do a moving average or oversampling, you still  
have to do the same amount of averaging whether you use one or the  
other. Assuming that the device has a single sample buffer for data,  
and no mechanism to write samples directly to RAM interrupt traffic  
might be about the same too.

Depending on sample rate, with a moving average you might be able to  
have longer filters.  That said, these devices don't have much RAM, so  
this may not matter that much.

> > The stellaris platform has hardware averaging, though it is  
> designed for
> > being used for oversampling, not just rolling averages.
>
> But isn't the functionality similar? Or they just do a simple  
> averaging in LM3S, instead of a moving average? (sorry, way too  
> sleepy to check now).

I could be wrong, but they appear to do simple averaging, where one  
takes (output sample rate)*(oversampling order) samples, averages a  
unique set of samples for each one outputted.  I'll look if there's a  
way around this.  When they give an example of code for moving average  
in the app-note linked below, they do the averaging in software.

> > This article does a decent job of outlining the issues:
> > http://www.zlgmcu.com/luminary/download/LM3SAPP_ADC_OverSample_Tech_en.pdf
>
> I'll try to take a look at that. What I know for sure is that a 16x  
> or even 8x moving average filter can work wonders, so we'd better  
> have that, even if it involves interrupts.
> A little homework for you :): try to figure out a way to have  
> adc_burst mode work (optionally) in double buffering mode. It's  
> really useful for lots of applications.

Yeah, I can see why double buffering would be useful.  I'll keep that  
in mind when I'm putting together this implementation.

Oversampling on Stellaris doesn't necessarily need extra interrupt  
handlers for low numbers of samples (on the 16x or 8x order) for  
single-shot.

For non-single shot sampling I'll need to have an ISR to copy stuff  
from the ADC register to some separate buffer in order to prevent the  
ADC registers/fifos from overflowing.  To some extent this is  
avoidable for small numbers of samples, but I think I'll do a general  
implementation, since I don't think all the other ARM ADCs use FIFOs  
for buffering samples, and even on Stellaris the FIFOs vary in length  
depending on which sequencer you use.  So, I would consider their use  
an optimization that will add a bit of backend complexity.

The other thing I've noted is that when I tried

>
>
> > void platform_adc_set_smoothing( unsigned id, u8 length );
> Agreed. And length should probably be a power of 2 to make things  
> (like modulo and division operations) faster.

I agree.  I was originally going to do this so that it would be  
recognized that the length was a power of two, but to still support  
doing the slower division.  I suppose we could have users pass the  
exponent (2^<value passed>), but I feel like that would be strange


> On Fri, Jan 23, 2009 at 3:52 AM, James Snyder  
> <jbsnyder at fanplastic.org> wrote:
> I suppose one other option here is to use virtual timers instead of  
> dedicated ones?  I've looked at the virtual timer stuff that has  
> been checked in, but I have to admit that I've not yet quite figured  
> out how to make use of them.  It looks like they're using SysTick?
>
> They can use any timer interrupt, they just happen to use SysTick on  
> LM3S because that doesn't 'eat' one of the regular timers (the ones  
> we use in the tmr module). And it just increments some 32-bit  
> counters. Very simple stuff really, see src/common.c for details, I  
> think they're quite straightforward. You use them just like you use  
> the other timer functions, but you specify a virtual timer ID  
> instead of an actual timer ID (the magic that makes this work also  
> happens in src/common.c).

I'll take a look again, it was late when I was thinking about them.  
This could be one way to remove the need to pass a timer to things  
like the burst function?  Are there any major downsides to the virtual  
timers besides memory usage?  I assume there might be more CPU usage.

>
>
> I've seen another "virtual" timer implementation out there that was  
> posted on the LM boards (not sure how generalizable the code might  
> be for other platforms, nor what license the code is under), which  
> supposedly allows for setting up an "unlimited" number of timers,  
> events and delays.
>
> You can have unlimited timers here too. Provided, of course, you  
> also have unlimited memory :)

Ah, I had seen the number of virtual timers predefined, and I wasn't  
sure if that was a hard resource limitation or not :-)

I'll check things in when I have a few more stable features.

--
James Snyder
Biomedical Engineering
Northwestern University
jbsnyder at fanplastic.org
http://fanplastic.org/key.txt
ph: (847) 644-2322

-------------- next part --------------
An HTML attachment was scrubbed...
URL: https://lists.berlios.de/pipermail/elua-dev/attachments/20090123/f3826409/attachment.html 
-------------- next part --------------
A non-text attachment was scrubbed...
Name: PGP.sig
Type: application/pgp-signature
Size: 194 bytes
Desc: This is a digitally signed message part
Url : https://lists.berlios.de/pipermail/elua-dev/attachments/20090123/f3826409/attachment.pgp