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.
paserra
Posts: 10
Joined: 15 April 2007, 2:31 AM

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

Post by paserra »

Could be possible to use the ADC in free running mode?
I need to increase the speed of my ADC routine... Could be possible to have the ADC working "forever" while I read only the "last" result with my ADC routine?
Do you have an example to do that with ZBasic Micro?
Thank you,
Pier Andrea.
spamiam
Posts: 739
Joined: 13 November 2005, 6:39 AM

Post by spamiam »

There is no method for ZBasic to do that for you as far as I know.

But, ZBasic might not interfere with the normal ADC functioning of the AVR hardware if you access the hardware directly.

I have not tried this myself, but other direct hardware accesses do work, so this probably would too.

"Just" set up the hardware registers to start the ADC running autonomously. Then check the ADC hardware register for the result. I believe it will hold the last complete ACD reading.

I will look again at the AVR docs to remind myself how to do this.

-Tony
paserra
Posts: 10
Joined: 15 April 2007, 2:31 AM

Read last ADC value

Post by paserra »

Thank you Tony.
My (theorical) idea is to setup one ADC channel writing the rights values on ADMUX, ADSCRA and ADSCRB registers to operate in free running mode.
After that, I hope that I can obtain the last ADC value just (and only) reading ADCL and ADCH registers (my ZBasic code has to be a little more "slow" than ADCL/ADCH update)...
Could be I obtain an improvement in the ADC speed for embedded DSP (oversampling and decimation in particular)...
Regards, Pier Andrea.
spamiam
Posts: 739
Joined: 13 November 2005, 6:39 AM

Post by spamiam »

I just checked the AVR hardware docs and the ZBasic language reference. It looks as if you CAN access all the necessary registers to set-up the free running conversions.

You can access the ADCL, ADCH registers atomically in Zbasic with the via the 16 bit ZBasic register ADC.

I just wonder if ZBasic will have a problem with the ADC interrupt firing at 114KHz. Probably it will not be a problem at all.

As for speed enhancement, I would assume there IS going to be a speed improvement vs the usual ADC command.

The usual ZBasic ADC command probably will set up all the registers every time, then trigger a sample, then wait for the sample to be done, then get the sample.

With the free-running technique, all you need to do is get the value of the last sample. BUT you only get the sample from one channel. If you need to change channels, then you have as much overhead as the built-in command, AND by coding it in ZBasic, it will be slower than the built-in function.

I wonder if this is an opening for a new/enhanced ZBasic function(s). To set up and access continuous free-running conversions.


It would be interesting to see how much faster it is to do use free-running conversions, compared to the built-in single ADC conversion
-Tony
dkinzer
Site Admin
Posts: 3120
Joined: 03 September 2005, 13:53 PM
Location: Portland, OR

Re: Read last ADC value

Post by dkinzer »

paserra wrote:My (theorical) idea is to setup one ADC channel writing the rights values on ADMUX, ADSCRA and ADSCRB registers to operate in free running mode. After that, I hope that I can obtain the last ADC value just (and only) reading ADCL and ADCH registers [...]
I've written some sample code (see below) to show how to do this for a mega644-based ZX. It may need to be modified slightly for other ZX devices.

The simple driver sits in a loop calling the readADC() function and displaying the result. Note that the wait loop in readADC() is only necessary on the first conversion. After the first conversion has been completed there will always be a conversion value available. If desired, this wait loop could be moved to the initADC() subroutine so that it will not return until the first conversion is completed.

Code: Select all

'
' This sample code illustrates how to set up the ADC for
' continuous conversions.  The code is written specifically
' for the mega644-based ZX devices.  It may need to be
' modified for other ZX devices.
'
Sub Main()
  Dim val as UnsignedInteger

  Call initADC(0)
  Do
    val = readADC()
    Debug.Print CStr(val)
    Call Sleep(1.0)
  Loop
End Sub

'
'' initADC
'
' Initialize the ADC to operate in free-running mode, sampling
' the specified channel.
'
Private Sub initADC(ByVal chan as Byte)
  ' 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(Register.ADCSRA, ADEN, 0)
  
  ' select the free-running trigger mode
  Call SetBits(Register.ADCSRB, ADTS2 Or ADTS1 Or ADTS0, 0)

  ' select the AVcc reference and the desired channel
  Call SetBits(Register.ADMUX, REFS1 Or REFS0 Or CHAN_MASK, REFS0 Or (chan And CHAN_MASK))

  ' enable auto-trigger mode, select the 128 prescaler and clear the completion flag
  Call SetBits(Register.ADCSRA, ADATE Or ADIF Or ADPS2 Or ADPS1 Or ADPS0, _
      ADATE Or ADIF Or ADPS2 Or ADPS1 Or ADPS0)

  ' enable the ADC and start the first conversion
  Call SetBits(Register.ADCSRA, ADEN Or ADSC, ADEN Or ADSC)
End Sub

'
'' readADC
'
' Get the lastest ADC conversion result.
'
Private Function readADC() as UnsignedInteger
  ' bit values for the ADCSRA register
  Const ADIF as Byte = &H10

  ' wait for a conversion to be completed 
  Do Until (CBool(Register.ADCSRA And ADIF)) 
  Loop 

  ' get the conversion value   
  readADC = Register.ADC
End Function 
Last edited by dkinzer on 05 May 2008, 14:45 PM, edited 1 time in total.
- Don Kinzer
paserra
Posts: 10
Joined: 15 April 2007, 2:31 AM

Great ADC solution!!!

Post by paserra »

Don,
fantastic work!!! I'll try the code immediately (I'm so curious about the performance improvement!!!!)
Thank you very much!.
Regards, Pier Andrea.

p.s. Thanks to Tony too for the suggestions!!!!
mikep
Posts: 796
Joined: 24 September 2005, 15:54 PM

Post by mikep »

spamiam wrote:I wonder if this is an opening for a new/enhanced ZBasic function(s). To set up and access continuous free-running conversions.
Don't forget the venerable ADCtoCOM1 function that already performs free-running conversions and sends the output to COM1.

See also the note in this function (and in the Atmel datasheet). Switching any of the digital I/Os on the same port will cause noise and possibly inaccurate results.
Mike Perks
spamiam
Posts: 739
Joined: 13 November 2005, 6:39 AM

Post by spamiam »

Don, this is EXACTLY what I had in mind. I would have put the wait for the first conversion to be complete in the initialization code. That way the reading of the ADC is as lean as possible because it is most likely that the reason this technique is being used is to get the FASTEST ADC reading possible.

I am not sure, but maybe the first ADC is so fast (even though it is half the speed of later conversions) that data will be ready before the next ZBasic instruction is ready to execute. The first conversion takes 25 clock cycles. I presume that reading of the ADC result would take longer than that even if it is the very next instruction (I.E. fetching, and processing the next instruction causes more than a 25 clock lag?)


Mike, good point re: noise on the ADC. Apparently lots of other processes can cause noise, but I would guess that other activity on the same port would be the worst.

-Tony
paserra
Posts: 10
Joined: 15 April 2007, 2:31 AM

Fantastic

Post by paserra »

Have look at this working code to read a differential signal ADC0/ADC1 with a gain of 1x or 10x and internal VRef = 2.56 volts
I have to optimize the DiffADC() routine but I can read an oversampled value (plus 4/5 bits of resolution) in a short period of time:

Code: Select all

const GAIN_1  as byte = &HD0
const GAIN_10 as byte = &HC9

dim sign as single

Private Sub InitADC(ByVal gain as Byte)

  Call SetBits(Register.ADCSRA, 128, 0)
  Call SetBits(Register.ADCSRB, 7, 0)
  
   if gain = 1 then 
   sign = 1.0
   Register.ADMUX = GAIN_1
   end if
   
   if gain = 10 then 
   sign = -1.0
   Register.ADMUX = GAIN_10
   end if
   
  Call SetBits(Register.ADCSRA, 63,63)
  Call SetBits(Register.ADCSRA, 192, 192)
  
  Do Until (CBool(Register.ADCSRA And 16))
  Loop
  
End Sub

Private Function DiffADC() As single
   Dim lowByte as Byte, highByte as Byte
   dim xvalue as single 
   dim i as integer
   dim value as integer
   
   for i = 0 to 511	'OVERSAMPLING
   lowByte = Register.ADCL
   highByte = Register.ADCH
   If (CBool(highByte And &H02)) Then 'SIGN
      highByte = highByte Or &HFC
   End If
   value = CInt(MakeWord(lowByte, highByte)) 
   xvalue = xvalue + CSng(value) 'ACCUMULATOR
 next i
 xvalue = (xvalue/512.0) * sign 'DECIMATION
 xvalue = (xvalue*2560.0)/512.0 'TO VOLTAGE

 DiffADC = xvalue
End Function
... IN MAIN ROUTINE...

Code: Select all

dim value as single

Call InitADC(1) 'GAIN = 1X
or
Call InitADC(10) 'GAIN = 1X
then
value = DiffADC()
Regards,
Pier Andrea.

[Edit: wrapped the code with code tags for better readability]
dkinzer
Site Admin
Posts: 3120
Joined: 03 September 2005, 13:53 PM
Location: Portland, OR

Re: Fantastic

Post by dkinzer »

paserra wrote:I have to optimize the DiffADC() routine [...]
The execution time can be reduced by reading all 10 bits of the ADC result at once. Then, to handle the sign extension the value is manipulated byte-wise.

Also, note that the initialization of the accumulator was missing in your original code.

Code: Select all

Private Function DiffADC() As Single
   Dim adcRes as Integer 
   Dim adcResBytes() as Byte Alias adcRes
   Dim i as Integer 

   ' initialize the accumulator
   DiffADC = 0.0
   
   'OVERSAMPLING loop
   For i = 0 to 511
      ' read the conversion result
      adcRes = CInt(Register.ADC)
	  
      ' perform sign extension of the value, operating on the high byte only
      If (CBool(adcResBytes(2) And &H02)) Then
         adcResBytes(2) = adcResBytes(2) Or &HFC
      End If

      ' add the reading to the accumulator
      DiffADC = DiffADC + CSng(adcRes)
   Next i
   
   ' average the reading and convert to voltage value
   DiffADC = (((DiffADC / 512.0) * sign) * 2560.0) / 512.0 
End Function
Another possible optimization is to accumulate the ADC values as integral values. This allows the use of the faster integral math within the loop and for the first part of the final conversion operation.

In the final conversion, the multiplication by 5 replaces the division by 512 and subsequent multiplication by 2560. If the 'sign' variable were integral as well that would eliminate one more floating point multiplication. I'm not quite sure what the purpose of that is so I left it as it was.

Code: Select all

Private Function DiffADC() As Single
   Dim acc as Long
   Dim adcRes as Integer 
   Dim adcResBytes() as Byte Alias adcRes
   Dim i as Integer 

   ' initialize the accumulator
   acc = 0
   
   'OVERSAMPLING loop
   For i = 0 to 511
      ' read the conversion result
      adcRes = CInt(Register.ADC)
	  
      ' perform sign extension of the value, operating on the high byte only
      If (CBool(adcResBytes(2) And &H02)) Then
         adcResBytes(2) = adcResBytes(2) Or &HFC
      End If

      ' add the reading to the accumulator
      acc = acc + CLng(adcRes)
   Next i
   
   ' average the reading and convert to voltage value
   DiffADC = (CSng(acc * 5) * sign) / 512.0 
End Function
Last edited by dkinzer on 18 October 2007, 18:52 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:The first conversion takes 25 clock cycles.
Of course, that's 25 ADC clock cycles. Since the 128 prescaler is being used (in order to get the ADC clock below 200KHz), that's 3200 CPU clock cycles.
- Don Kinzer
spamiam
Posts: 739
Joined: 13 November 2005, 6:39 AM

Post by spamiam »

dkinzer wrote:
spamiam wrote:The first conversion takes 25 clock cycles.
Of course, that's 25 ADC clock cycles. Since the 128 prescaler is being used (in order to get the ADC clock below 200KHz), that's 3200 CPU clock cycles.
Oh, I did not know that! For some reason I had always thought that it was CPU clock cycles. Fortunately, I never needed to watch every stray clock cycle when using ADC. Plus, I always set it up as free-running, so I did not have to worry about it.

At 3200 cpu clock cycles, I would imagine you DO have to watch out for the need to wait for the first conversion to be done....

-Tony
paserra
Posts: 10
Joined: 15 April 2007, 2:31 AM

Post by paserra »

Thank you Don,
very good solution.
Thanks,
Pier Andrea.
dkinzer
Site Admin
Posts: 3120
Joined: 03 September 2005, 13:53 PM
Location: Portland, OR

Post by dkinzer »

paserra wrote:[That is a] very good solution.
Be aware that neither of the DiffADC() solutions that I proposed has been tested beyond verifying that they compile without errors.
- Don Kinzer
spamiam
Posts: 739
Joined: 13 November 2005, 6:39 AM

Post by spamiam »

So, 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? (not including the set-up time for the free-running version)

-Tony
Post Reply