Could be possible to use the ADC in free running mode?

Discussion about the ZBasic language including the System Library. If you're not sure where to post your message, do it here. However, do not make test posts here; that's the purpose of the Sandbox.
dkinzer
Site Admin
Posts: 3120
Joined: 03 September 2005, 13:53 PM
Location: Portland, OR

Post by dkinzer »

Another optimization is to eliminate the need for the sign extension code by setting the ADLAR bit (&H20) in the ADMUX register. With this bit set, the 10-bit conversion value is left-justified in the 16-bit result so the sign bit is right where it needs to be. Of course, the resulting value is 64 times the actual value so this would need to be taken into account in the code that converts to a voltage value. The division by 64 may best be implemented using the Shr() function to shift the value right by 6 bits.

Even with the value scaled by 64, a Long variable can still accumulate 512 readings without overflow since 511 * 64 * 512 < 2^31 - 1.
Last edited by dkinzer on 18 October 2007, 22:22 PM, edited 1 time in total.
- Don Kinzer
dkinzer
Site Admin
Posts: 3120
Joined: 03 September 2005, 13:53 PM
Location: Portland, OR

Post by dkinzer »

spamiam wrote:How much faster is it to read an ADC value from the free-running ACD than it is to use the built-in ZBasic function?
Using the test program below, I measured 300uS per iteration using GetADC() versus 68uS per iteration using direct reads from Register.ADC.

Note, however, that when using the 128 prescaler it takes 113uS (13 ADC cycles) to perform a conversion. An iteration time faster than the conversion time will result in duplicate readings.

If you can get by with lower ADC resolution you can use a smaller prescaler (faster ADC clock) yielding a faster conversion time.

Of course, the question is moot with respect to the original poster's needs since a differential conversion is needed anyway.

Code: Select all

#define USE_FREE_RUNNING

Dim uval as UnsignedInteger 
Dim ival as Integer 

Sub Main&#40;&#41; 
  Dim i as Long
  Dim t as Single

#ifdef USE_FREE_RUNNING
  Call initADC&#40;0&#41;
#endif

  t = Timer&#40;&#41;
  For i = 1 to 1000
#ifdef USE_FREE_RUNNING
    uval = Register.ADC 
#else
	ival = GetADC&#40;20&#41;
#endif
  Next i
  t = Timer&#40;&#41; - t
  Debug.Print "Avg iteration time is "; Fmt&#40;t * 1000.0, 1&#41;; " uS"
End Sub 

#ifdef USE_FREE_RUNNING
' 
'' initADC 
' 
' Initialize the ADC to operate in free-running mode, sampling 
' the specified channel. 
' 
Private Sub initADC&#40;ByVal chan as Byte&#41; 
  ' bit values for the ADCSRA register 
  Const ADEN as Byte = &H80 
  Const ADSC as Byte = &H40 
  Const ADATE as Byte = &H20 
  Const ADIF as Byte = &H10 
  Const ADPS2 as Byte = &H04 
  Const ADPS1 as Byte = &H02 
  Const ADPS0 as Byte = &H01 
  
  ' bit values for the ADCSRB register 
  Const ADTS2 as Byte = &H04 
  Const ADTS1 as Byte = &H02 
  Const ADTS0 as Byte = &H01 

  ' bit values for the ADMUX register 
  Const REFS1 as Byte = &H80 
  Const REFS0 as Byte = &H40 
  Const CHAN_MASK as Byte = &H07 

  ' disable the ADC during configuration 
  Call SetBits&#40;Register.ADCSRA, ADEN, 0&#41; 
  
  ' select the free-running trigger mode 
  Call SetBits&#40;Register.ADCSRB, ADTS2 Or ADTS1 Or ADTS0, 0&#41; 

  ' select the AVcc reference and the desired channel 
  Call SetBits&#40;Register.ADMUX, REFS1 Or REFS0 Or CHAN_MASK, REFS0 Or &#40;chan And CHAN_MASK&#41;&#41; 

  ' enable auto-trigger mode, select the 128 prescaler and clear the completion flag 
  Call SetBits&#40;Register.ADCSRA, ADATE Or ADIF Or ADPS2 Or ADPS1 Or ADPS0, _ 
      ADATE Or ADIF Or ADPS2 Or ADPS1 Or ADPS0&#41; 

  ' enable the ADC and start the first conversion 
  Call SetBits&#40;Register.ADCSRA, ADEN Or ADSC, ADEN Or ADSC&#41;

  ' wait for a conversion to be completed 
  Do Until &#40;CBool&#40;Register.ADCSRA And ADIF&#41;&#41; 
  Loop 

End Sub 

#endif
- Don Kinzer
paserra
Posts: 10
Joined: 15 April 2007, 2:31 AM

Post by paserra »

Don,
your version of the DiffADC() works VERY WELL!!!
in my application I read and transmit a DiffADC() value every 200 ms and send it to a PC via serial port... I increased the oversampling value to 1023 without problems!!!! Each ADC sample is the oversampling/decimation of 1024 values every 200 ms (5 samples = 5120 values per second!)... I recorded the signal for 1 hour without problems!!!
About the noise.... IT IS VERY LOW.
Great ZBASIC, Thanks Don. Thanks to All.
Pier Andrea.
spamiam
Posts: 739
Joined: 13 November 2005, 6:39 AM

Post by spamiam »

dkinzer wrote:Of course, the question is moot with respect to the original poster's needs since a differential conversion is needed anyway.
Yes, it appears that the differential readings take the full 25 ADC clocks for each conversion (225.7uS).

At 68uS per iteration of reading the ADC value, you will get 3 or 4 duplicates of a single conversion.

Is there a way to tell if there is a NEW conversion available? IN the free-running mode, I am not sure if you can read the ADC interrupt flag, read the ADC if it is set, then clear it by writing 1 to it.

And if you do it that way, will you be losing most of the advantage of the speed enhancement over the built-in method?

-Tony
dkinzer
Site Admin
Posts: 3120
Joined: 03 September 2005, 13:53 PM
Location: Portland, OR

Post by dkinzer »

spamiam wrote:Is there a way to tell if there is a NEW conversion available?
Yes, it does work to reset the ADIF flag after each conversion result is read and then await the flag before the next reading as you surmised. Here is a modified readADC() function that implements that strategy.

Code: Select all

'
'' readADC
'
' Get a new ADC conversion result.
'
Function readADC&#40;&#41; as UnsignedInteger
  Const ADIF as Byte = &H10 

  ' await a new conversion value
  Do Until &#40;CBool&#40;Register.ADCSRA And ADIF&#41;&#41; 
  Loop
	
  ' read the conversion result
  readADC = Register.ADC
	
  ' reset the completion flag
  Call SetBits&#40;Register.ADCSRA, ADIF, ADIF&#41;
End Function
- Don Kinzer
paserra
Posts: 10
Joined: 15 April 2007, 2:31 AM

my last DiffADC() routine

Post by paserra »

After ADLAR bit = 1 (in the ADMUX register) this is my last DiffADC() WORKING routine:

Code: Select all

Private Function DiffADC&#40;&#41; As Single
   Dim acc as Long
   Dim adcRes as Integer
   Dim i as Integer

   ' initialize the accumulator
   acc = 0
   
   'OVERSAMPLING loop
   For i = 0 to 1023

      ' read the conversion result
    adcRes = CInt&#40;Register.ADC&#41;

      ' add the reading to the accumulator
    acc = acc + CLng&#40;adcRes&#41;

   Next i

   ' average the reading and convert to voltage value
   DiffADC = &#40;&#40;CSng&#40;Shr&#40;acc,6&#41; * 5&#41; * sign&#41; / 1024.0&#41; 
End Function
I think I can further optimize the following line of code:

Code: Select all

DiffADC = &#40;&#40;CSng&#40;Shr&#40;acc,6&#41; * 5&#41; * sign&#41; / 1024.0&#41; 
Thank you for your suggestions.
Pier Andrea.
dkinzer
Site Admin
Posts: 3120
Joined: 03 September 2005, 13:53 PM
Location: Portland, OR

Post by dkinzer »

The (untested) variation below may be somewhat faster. Note that the sampling loop may be too fast for your purposes - adding the same sample value to the accumulator multiple times is of no benefit. I would suggest adding some execution time measurement code around the sampling loop. If the total loop execution takes less than 116mS (1024 * 13 * 128 / 14.7456E6) then you're adding one or more conversion results to the accumulator multiple times. Conversely, you can determine how many unique conversion results you have in the accumulator by dividing the total loop execution time by multiplying that time by (14.7456E6 / 13 / 28).

You would be well advised to add a comment above the line of code that does the conversion to voltage showing the unoptimized equation. This will make it easier to modify in the future by clarifying how the optimized equation was derived.

Code: Select all

Private Function DiffADC&#40;&#41; As Single 
   Dim acc as Long 
   Dim i as Integer 

   ' initialize the accumulator 
   acc = 0 
    
   'OVERSAMPLING loop 
   For i = 0 to 1023 
      ' read the conversion result and add to the accumulator 
      acc = acc + CLng&#40;CInt&#40;Register.ADC&#41;&#41;
   Next i 

   ' average the reading and convert to voltage value 
   DiffADC = &#40;&#40;CSng&#40;acc * 5&#41; * sign&#41; / 65536.0&#41; 
End Function 
- Don Kinzer
paserra
Posts: 10
Joined: 15 April 2007, 2:31 AM

Thank You Don

Post by paserra »

Thank You Don, I'll try your code soon.
Regards.
Pier Andrea
sturgessb
Posts: 287
Joined: 25 April 2008, 6:34 AM
Location: Norwich, UK

Post by sturgessb »

Is there anyway to utilize this method for multiple channels. I need to try and speed up my getADC calls, as I have 6 of these and they are killing my loop speed.

Or is this method only for reading single channel?
dkinzer
Site Admin
Posts: 3120
Joined: 03 September 2005, 13:53 PM
Location: Portland, OR

Post by dkinzer »

sturgessb wrote:Is there anyway to utilize this method for multiple channels.
The AVR devices have just one analog-to-digital converter which is fed by an analog multiplexer. Consequently, you can have only one ADC conversion underway at any one time but you can switch channels (read the documentation carefully regarding the timing issues).

You may also want to look at using a faster conversion clock. The example code's initADC() routine sets the conversion clock at the highest rate that will produce 10-bits of resolution. You can select a faster clock if you're willing to give up resolution.
- Don Kinzer
sturgessb
Posts: 287
Joined: 25 April 2008, 6:34 AM
Location: Norwich, UK

Post by sturgessb »

Thanks Don

Ive tried the initADC, but im getting the following error on compile

Error: reference to undefined identifier "Register.ADCSRB"

is this register different for the zx-128ne ?
mikep
Posts: 796
Joined: 24 September 2005, 15:54 PM

Post by mikep »

sturgessb wrote:is this register different for the zx-128ne ?
This register does not exist for the ATmega128. This is where playing with the low-level registers in the chip can get you into serious trouble and your code is no longer device independent. You need to know what you are doing, read the datasheet, and test carefully. Here is the code specific to the mega128 (which I haven't been able to test at this point):

Code: Select all

Private Sub initADC&#40;ByVal chan as Byte&#41;
  ' bit values for the ADCSRA register
  Const ADEN as Byte = &H80
  Const ADSC as Byte = &H40
  Const ADFR as Byte = &H20
  Const ADIF as Byte = &H10
  Const ADPS2 as Byte = &H04
  Const ADPS1 as Byte = &H02
  Const ADPS0 as Byte = &H01
 
  ' bit values for the ADMUX register
  Const REFS1 as Byte = &H80
  Const REFS0 as Byte = &H40
  Const CHAN_MASK as Byte = &H07

  ' disable the ADC during configuration
  Call SetBits&#40;Register.ADCSRA, ADEN, 0&#41;
 
  ' select the AVcc reference and the desired channel
  Call SetBits&#40;Register.ADMUX, REFS1 Or REFS0 Or CHAN_MASK, REFS0 Or &#40;chan And CHAN_MASK&#41;&#41;

  ' enable auto-trigger mode, select the 128 prescaler and clear the completion flag
  Call SetBits&#40;Register.ADCSRA, ADFR Or ADIF Or ADPS2 Or ADPS1 Or ADPS0, _
      ADFR Or ADIF Or ADPS2 Or ADPS1 Or ADPS0&#41;

  ' enable the ADC and start the first conversion
  Call SetBits&#40;Register.ADCSRA, ADEN Or ADSC, ADEN Or ADSC&#41;
End Sub
Did I mention you need to read the datasheet for the underlying AVR that you are using? See the datasheet download page on the Oak Micros website.
Mike Perks
sturgessb
Posts: 287
Joined: 25 April 2008, 6:34 AM
Location: Norwich, UK

Post by sturgessb »

maybe ill leave that be then, don't want to cock it up.

Could really do with those getADCs being a bit faster though.

hmmmm, decisions
dkinzer
Site Admin
Posts: 3120
Joined: 03 September 2005, 13:53 PM
Location: Portland, OR

Post by dkinzer »

sturgessb wrote:maybe ill leave that be then, don't want to cock it up.
I don't think that Mike intended to deter you from doing this. He was just pointing out that doing so requires a little more research and careful work compared to using System Library routines. Other than making sure you get the correct bits set/cleared, the other important point is to make sure you're not inadvertently enabling the ADC interrupt.
- Don Kinzer
mikep
Posts: 796
Joined: 24 September 2005, 15:54 PM

Post by mikep »

sturgessb wrote:maybe ill leave that be then, don't want to cock it up.

Could really do with those getADCs being a bit faster though.
Don is correct. I was not trying to deter, just point that this is not for beginners and you need to think through clearly what you want to do and the best way to achieve it. One of the good things about this forum is that we do not leave people hanging and always try to provide the best answer given the information in the post.

In the meantime I did a little more thinking. With a system clock of 14.7456Mhz, the ADC clock is 115.2KHz. This means that first conversion takes 25 cycles (217 us). This has been measured as 228.5 us including setup time using the timing program appended above. It turns out that using the ZBasic GetADC() routine is very similar to first calling the initADC() function and then retrieving the 16-bit value from Register.ADC.

Part of the setup time for a conversion is to configure the multiplexer (Register.ADMUX) to decide which I/O pin is connected to the single DAC in the AVR microcontroller. This thread has discussed free running conversions which works providing the same multiplexer configuration is kept and gives a conversion result in 13 cycles (113 us).

Ben's requirement is to retrieve ADC values from 6 channels (presumably one after another). That means that the multiplexer needs to be setup for each conversion. The simplest way to do this is to call the ZBasic GetADC() routine for each ADC pin. This means that a full set of values is available approximately every 1.37ms (or a rate of 729Hz).

This should be adequate for most purposes. What do you mean that the ADC is too slow? Is it the time taken to do a conversion or the fact that you cannot do anything while waiting for a conversion? Are you doing processing that needs to update some other value based on the ADC input more than 1000 times a second?

If conversions are taking too long then this could be improved using an external ADC that can simultaneously process multiple channels and is fast.

If you want to process values while waiting for other conversions to complete, then you should look into writing a native mode ISR for the ADC complete interrupt. The ISR would store the current channel result and change the ADMUX register for the next channel.
Mike Perks
Post Reply