Could be possible to use the ADC in free running mode?
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.
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
Using the test program below, I measured 300uS per iteration using GetADC() versus 68uS per iteration using direct reads from Register.ADC.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?
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()
Dim i as Long
Dim t as Single
#ifdef USE_FREE_RUNNING
Call initADC(0)
#endif
t = Timer()
For i = 1 to 1000
#ifdef USE_FREE_RUNNING
uval = Register.ADC
#else
ival = GetADC(20)
#endif
Next i
t = Timer() - t
Debug.Print "Avg iteration time is "; Fmt(t * 1000.0, 1); " uS"
End Sub
#ifdef USE_FREE_RUNNING
'
'' 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)
' wait for a conversion to be completed
Do Until (CBool(Register.ADCSRA And ADIF))
Loop
End Sub
#endif
- Don Kinzer
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.
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.
Yes, it appears that the differential readings take the full 25 ADC clocks for each conversion (225.7uS).dkinzer wrote:Of course, the question is moot with respect to the original poster's needs since a differential conversion is needed anyway.
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
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.spamiam wrote:Is there a way to tell if there is a NEW conversion available?
Code: Select all
'
'' readADC
'
' Get a new ADC conversion result.
'
Function readADC() as UnsignedInteger
Const ADIF as Byte = &H10
' await a new conversion value
Do Until (CBool(Register.ADCSRA And ADIF))
Loop
' read the conversion result
readADC = Register.ADC
' reset the completion flag
Call SetBits(Register.ADCSRA, ADIF, ADIF)
End Function
- Don Kinzer
my last DiffADC() routine
After ADLAR bit = 1 (in the ADMUX register) this is my last DiffADC() WORKING routine:
I think I can further optimize the following line of code:
Thank you for your suggestions.
Pier Andrea.
Code: Select all
Private Function DiffADC() 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(Register.ADC)
' add the reading to the accumulator
acc = acc + CLng(adcRes)
Next i
' average the reading and convert to voltage value
DiffADC = ((CSng(Shr(acc,6) * 5) * sign) / 1024.0)
End Function
Code: Select all
DiffADC = ((CSng(Shr(acc,6) * 5) * sign) / 1024.0)
Pier Andrea.
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.
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() 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(CInt(Register.ADC))
Next i
' average the reading and convert to voltage value
DiffADC = ((CSng(acc * 5) * sign) / 65536.0)
End Function
- Don Kinzer
Thank You Don
Thank You Don, I'll try your code soon.
Regards.
Pier Andrea
Regards.
Pier Andrea
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).sturgessb wrote:Is there anyway to utilize this method for multiple channels.
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
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):sturgessb wrote:is this register different for the zx-128ne ?
Code: Select all
Private Sub initADC(ByVal chan as Byte)
' 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(Register.ADCSRA, ADEN, 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, ADFR Or ADIF Or ADPS2 Or ADPS1 Or ADPS0, _
ADFR 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
Mike Perks
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.sturgessb wrote:maybe ill leave that be then, don't want to cock it up.
- Don Kinzer
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.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.
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