Continously sampling via ADC, how to start

General discussions about V-USB, our firmware-only implementation of a low speed USB device on Atmel's AVR microcontrollers
roger
Posts: 12
Joined: Sat Dec 20, 2008 5:27 pm

Continously sampling via ADC, how to start

Post by roger » Wed Dec 24, 2008 9:02 pm

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

roger
Posts: 12
Joined: Sat Dec 20, 2008 5:27 pm

Post by roger » Mon Dec 29, 2008 9:50 pm

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

christian
Objective Development
Objective Development
Posts: 1443
Joined: Thu Nov 09, 2006 11:46 am

Post by christian » Sun Jan 04, 2009 7:04 pm

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.

roger
Posts: 12
Joined: Sat Dec 20, 2008 5:27 pm

Post by roger » Sun Jan 04, 2009 7:10 pm

Thanks, I got it working using a circular buffer and using usbFunctionRead() to send the data.

psc
Rank 1
Rank 1
Posts: 32
Joined: Sat Nov 15, 2008 9:51 pm

Post by psc » Thu Jan 15, 2009 7:04 am

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

roger
Posts: 12
Joined: Sat Dec 20, 2008 5:27 pm

Post by roger » Thu Jan 15, 2009 5:13 pm

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

psc
Rank 1
Rank 1
Posts: 32
Joined: Sat Nov 15, 2008 9:51 pm

Post by psc » Thu Jan 15, 2009 8:37 pm

Roger,

Really nice of you! It would be very helpful to have the entire code, including the pyUSB.

Cheers!
Pat

roger
Posts: 12
Joined: Sat Dec 20, 2008 5:27 pm

Post by roger » Mon Jan 19, 2009 7:43 am

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 :)

Guest

Post by Guest » Mon Jan 19, 2009 5:20 pm

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

epsilon_da
Rank 1
Rank 1
Posts: 29
Joined: Mon Oct 13, 2008 7:11 pm

Post by epsilon_da » Tue Jan 20, 2009 10:09 pm

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.

OldSkull
Posts: 2
Joined: Sat Nov 15, 2008 12:28 am
Location: Pozna&#324; (PL)

Post by OldSkull » Tue Jan 20, 2009 10:26 pm

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?

roger
Posts: 12
Joined: Sat Dec 20, 2008 5:27 pm

Post by roger » Wed Jan 21, 2009 10:13 pm

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.

Image
Image
Image
Image
Image

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:
Image
Image

epsilon_da
Rank 1
Rank 1
Posts: 29
Joined: Mon Oct 13, 2008 7:11 pm

Post by epsilon_da » Thu Jan 22, 2009 6:39 pm

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.

roger
Posts: 12
Joined: Sat Dec 20, 2008 5:27 pm

Post by roger » Thu Jan 22, 2009 6:49 pm

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?

epsilon_da
Rank 1
Rank 1
Posts: 29
Joined: Mon Oct 13, 2008 7:11 pm

Post by epsilon_da » Thu Jan 22, 2009 7:22 pm

You are correct. My mistake.

:oops: 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.

Post Reply