Page 1 of 2
Continously sampling via ADC, how to start
Posted: Wed Dec 24, 2008 9:02 pm
by roger
Hi,
I'm new to USB programming and would like some advice on how I should proceed.
I'm trying to write a program that will continuously (and preferably uniformly) sample a voltage via the integrated ADC on an AVR, and send the values over USB. I'll either use an ADC resolution of 8 or 10 bits and I'd like to sample as fast as possible, but at least 1 kHz. Is this even possible with the 16.5 MHz RC oscillator code (I could also use a 20 MHz crystal)?
I'm still a bit confused by "end points" and "device classes", will endpoint 0 be suitable or will I need another one? I'd like to be able to control the device as well, so the communication should be two way.
I noticed that the driver needs to be polled often via usbPoll(). How long does this function take to process and how frequently should it be called? Can I just set it up on a timer and then just ignore it?
Thanks,
Roger
Posted: Mon Dec 29, 2008 9:50 pm
by roger
I've made some progress and have something that works, but I still need to work out the best way to send the ADC output back to the host. So far I've only tried the default control endpoint, and I can get about 300 bytes/s sending back the ADC contents when requested by the host. FYI the code is something like this:
Code: Select all
switch(rq->bRequest)
{
case CMD_ADC_READ:
usbMsgPtr = (uchar *)&ADC;
return 2;
...
where the ADC is in free running mode (with no interrupts).
I've also played with sending 254 bytes at a time with usbFunctionRead() using dummy data, and I get much better performance (>7 kbytes/s, more than I need).
I still have my original problem though, how can I send the ADC output back to the host with the highest sample rate?
Polling for it byte by byte is too slow, and buffering and sending large chunks at once uses too much memory and there are timing issues (since I'd like the sampling to be uniform).
Would using interrupt and bulk-in endpoints help?
Thanks,
Roger
Posted: Sun Jan 04, 2009 7:04 pm
by christian
Some answers:
(1) You can't call usbPoll() from a timer interrupt. You must call it from the main code. usbPoll() usually returns within microseconds, but since your usbFunctionSetup(), usbFUnctionRead() and usbFunctionWrite() routines are called from there, the maximum duration depends on your routines.
(2) This can all be done on endpoint 0 with control transfers. The host polls with control-in and receives as many bytes as there are available on each read. Bulk endpoints are forbidden for low speed, interrupt endpoints would be possible, although polling might be too slow.
(3) You need some kind of buffering, preferably a ring buffer. The ADC may be in free-running mode and trigger an interrupt (which must be declared so that it re-enables interrupts immediately), or you poll it from the main loop as well.
(4) Each control-in (handled through usbFunctionSetup()) returns as many bytes from the ring buffer as there are available.
Posted: Sun Jan 04, 2009 7:10 pm
by roger
Thanks, I got it working using a circular buffer and using usbFunctionRead() to send the data.
Posted: Thu Jan 15, 2009 7:04 am
by psc
roger,
would it be possible to have a look at your solution. i am trying to achieve something similar but so far no luck.
let us know,
cheers,
pat
Posted: Thu Jan 15, 2009 5:13 pm
by roger
Sure, happy to share.
Here is the most relevant code:
Code: Select all
volatile uint8_t adcbuff[250]; // must be multiple of 5 for 10 bit mode
volatile uint8_t end = 0;
volatile uint8_t start = 0, bytes_ready;
volatile uint8_t adc_10bit_mode = 0;
#define ADCBufferSize() ((end - start + DAC_BUFF_SIZE) % DAC_BUFF_SIZE)
#define ADCBufferFull() ((end + 1) % DAC_BUFF_SIZE == start)
#define ADCBufferClear() end = 0; start = 0
ISR(ADC_vect, ISR_NOBLOCK)
{
uint8_t high_byte, shift_amount;
if(ADCBufferFull())
{
// throw away oldest samples if buffer is full
if(adc_10bit_mode)
start = (start + 5) % DAC_BUFF_SIZE;
else
start = (start + 1) % DAC_BUFF_SIZE;
}
if(adc_10bit_mode)
{
// pack four 10 bit samples into 5 bytes by using every 5th byte to store the 2 LSB's of the previous 4 samples
// skip every 5th byte
if((end + 1) % 5 == 0)
end = (end + 1) % DAC_BUFF_SIZE;
high_byte = end + 5 - ((end + 1) % 5); // find index of byte to store 2 LSB's
shift_amount = 6 - (end % 5)*2; // find bit position in byte to store the 2 bits
adcbuff[high_byte] = (adcbuff[high_byte] & ~(0b11000000 >> shift_amount)) | (ADCL >> shift_amount);
}
adcbuff[end] = ADCH;
end = (end + 1) % DAC_BUFF_SIZE;
TIFR |= (1 << OCF0A); // clear interrupt flag
}
Code: Select all
usbMsgLen_t usbFunctionSetup(uchar setupData[8])
{
usbRequest_t *rq = (void *)setupData;
static uchar buf;
switch(rq->bRequest)
{
// <more non essential commands here>
case CMD_ADC_BUFFER_READ:
bytes_ready = MIN(ADCBufferSize(), rq->wValue.bytes[0]);
if(adc_10bit_mode)
// need to send 5 bytes at a time in 10 bit mode
bytes_ready = (bytes_ready/5)*5;
return USB_NO_MSG;
case CMD_ADC_BUFFER_CLEAR:
ADCBufferClear();
return 0;
case CMD_ADC_SAMPLERATE_SET:
ADCSampleRateSet(rq->wValue.bytes[0]);
return 0;
}
Code: Select all
uchar usbFunctionRead(uchar *data, uchar len)
{
if(len > bytes_ready)
len = bytes_ready;
for(uint8_t i = 0; i < len; ++i)
data[i] = adcbuff[(i + start) % DAC_BUFF_SIZE]; // copy data to ADC buffer
start = (start + len) % DAC_BUFF_SIZE;
bytes_ready -= len;
return len;
}
I can sample up to about 6 kHz in 8 bit mode and a bit less in 10 bit mode with a 200 byte buffer. If you use a smaller one then you might run into trouble since it fills up too fast.
In 10 bit ADC mode the data is packed a bit weird to save space, instead of just using two bytes (and wasting 6 bits). The code could also be sped up by eliminating all the modulo calls, but it works good enough for me.
If you want the entire code, let me know. I also have a Python interface that uses pyUSB.
Good luck,
Roger
Posted: Thu Jan 15, 2009 8:37 pm
by psc
Roger,
Really nice of you! It would be very helpful to have the entire code, including the pyUSB.
Cheers!
Pat
Posted: Mon Jan 19, 2009 7:43 am
by roger
Sorry I haven't had a chance to post this until now or to clean up my code, but here it is:
http://www.megaupload.com/?d=EXUBE3WX
usbtest.c is the main program.
I can almost guarantee there are lots of bugs, so good luck
Posted: Mon Jan 19, 2009 5:20 pm
by Guest
thank you very much for taking the time to share your work. i will try to understand and if i came up with something i will let you know if you don't mind.
pat
Posted: Tue Jan 20, 2009 10:09 pm
by epsilon_da
Hi.
Nice done.
How have you messured the sample rate?
I need to fetch information from ADCs as fast as possible, but i may not need it to be circular.
What microcontroller are you using? Crystal oscillator? ADC prescaler settings? How many channels are you sending? etc.
How fast can AVR-USB transfer a buffer in your case?
You say that the sample rate are of about "6 khz", but sample rate are messured in k-samples per second. What do you really mean with 6 khz? I think that it is too much slow. With an ATmega32 at 16Mhz it is possible to get 76923 kSPS and 8 bits.
Thanks.
Posted: Tue Jan 20, 2009 10:26 pm
by OldSkull
I think that it's not the problem with ADC speed (or it's only partially a problem), but with speed of software-usb:
http://forums.obdev.at/viewtopic.php?t=19&start=15
up to 7kB/s? it means sending up to less than 7k samples (8bit) per second.
PS:
is there any Windows-based host for sending and receiving data from avr-usb device except gPowerSwitch?
Posted: Wed Jan 21, 2009 10:13 pm
by roger
Hi.
The sample rate is variable and is set by the host (from 1.1 kHz - >10 kHz), and from my testing it is quite accurate.
I built a small circuit using an ATTINY85 with a diode and BJT to reduce the 5V USB supply down to ~3.6 V. I used the 16.5 MHz RC module, which is good enough for me. My software only uses one channel, but it should be easy to expand that to several (I didn't have any free pins in my circuit).
My testing shows that I can reliably and continuously transfer up to about 7 kbps using AVR-USB and a large enough buffer. So in 8 bit mode that's up to 7 ksps or (4/5)*7 ksps in 10 bit mode.
Here are some test plots I made with various sample rates and varying input sine frequencies.
As you may see, there's very little distortion and the timing is quite accurate.
This is what happens when you try to sample too fast and the buffer can't keep up:
Posted: Thu Jan 22, 2009 6:39 pm
by epsilon_da
epsilon_da wrote:How fast can AVR-USB transfer a buffer?
I did some tests transferring 5000 times a buffer of 1800 bytes from the uC to PC doing most transfers in chuncks of 250 bytes.
The result was a transfer rate of about 55 kb/s.
The firmware does nothing more than blink a led each second and transfer the buffer.
I suspect that doing a realtime adquisition board up to 25 ksps in 10 bit mode, is doable.
Posted: Thu Jan 22, 2009 6:49 pm
by roger
That's a bit of a suspicious result I think.
According to
http://forums.obdev.at/viewtopic.php?t=19&postdays=0&postorder=asc&start=0, the theoretical limit is 24kbps and no one got above 7kbps.
Can you post more details and double check your results?
Posted: Thu Jan 22, 2009 7:22 pm
by epsilon_da
You are correct. My mistake.
too many errors in the code and in my interpretation.
I will try to get real results.
EDIT: The problem was a bad use of the "clock" function. The new result is 7182 bytes/s (only the buffer is counted, not protocol bytes). So good realtime adquisition is not possible.