ADC with internal 2.56v reference on -24x
ADC with internal 2.56v reference on -24x
For the life of me, I cannot get a good ADC value on a ZX-24x when using the internal 2.56v reference. Is there any reason why AN-217 code would not work on a -24x?
Tom
Tom
Tom
Re: ADC with internal 2.56v reference on -24x
The xmega devices don't have a 2.56V reference. The choices for the ADC reference voltage on an xmega are the internal 1.00V reference, the internal Vcc/1.6 reference, the AREFA pin and the AREFB pin (if available). The xmega32A4, on which the ZX-24x is based, has only AREFA (pin A.0, 20). The GetADC() routine for the xmega uses the Vcc/1.6 reference.GTBecker wrote:Is there any reason why AN-217 code would not work on a -24x?
The simple test program below may be useful to experiment with the ADC. Connect a variable voltage to the input pin (pin 14 for a ZX-24x) and then vary the voltage while observing the resulting output.
Code: Select all
' Connect a variable voltage source to pin indicated.
#if Option.TargetDevice = "ZX128a1"
Const pin as Byte = A.1
#else
Const pin as Byte = 14
#endif
' Set up the conversion parameters
#if Option.TargetFamily = "ATxmega"
Const vref as Single = 3.3 / 1.6
Const fs as UnsignedInteger = 4096
#else
Const vref as Single = 5.0
Const fs as UnsignedInteger = 1024
#endif
Sub Main()
Dim ival as Integer
Debug.Print Option.TargetFamily; ": vref="; Fmt(vref, 2); ", FS="; fs
Do
ival = GetADC(pin)
Debug.Print CStr(ival);" "; Fmt(CSng(ival) * vref / CSng(fs), 2);" volts"
Call Delay(0.5)
Loop
End Sub
- Don Kinzer
Re: ADC with internal 2.56v reference on -24x
I neglected to mention that although the techniques discussed in AN-217 will work for an xmega-based device, the registers for configuring the ADC are completely different. The ATxmega A Family datasheet gives the details.GTBecker wrote:Is there any reason why AN-217 code would not work on a -24x?
- Don Kinzer
ADC with internal 2.56v reference on -24x
> ... The xmega devices don't have a 2.56V reference...
Ah, yes. I was reading the wrong documentation. I am again amazed and
bewildered by the capability of these processors.
I'll need to use Vref=ArefA and read a pin and the internal bandgap
reference for both ratiometric and absolute inputs. I'll take a crack
at it using AN-217 as a template - with the A family docs.
Thanks again, Don.
Tom
Ah, yes. I was reading the wrong documentation. I am again amazed and
bewildered by the capability of these processors.
I'll need to use Vref=ArefA and read a pin and the internal bandgap
reference for both ratiometric and absolute inputs. I'll take a crack
at it using AN-217 as a template - with the A family docs.
Thanks again, Don.
Tom
Tom
Re: ADC with internal 2.56v reference on -24x
Here is code for a routine similar to that in AN-217 for the xmega but it uses the Vcc/1.6 reference. You may find it useful as a starting point.GTBecker wrote:I'll take a crack at it using AN-217 as a template - with the A family docs.
Code: Select all
'
'' readADC
'
' This function performs an A/D conversion on a specified channel (0-7
' corresponding to A.0 through A.7) using the xmega's internal Vcc/1.6
' reference. The return value is a Single value that approximates the
' voltage present on the specified pin.
'
' The ATxmega datasheet should be consulted to better understand the
' meaning of the registers and bits involved.
'
Function readADC(ByVal chan as Byte) as Single
Const ADC_PS_32 as Byte = 3 ' divide-by-32 prescaler selector
Const ADC_REF as Single = 3.3 / 1.6 ' reference voltage
Const ADC_FS as Single = 4096.0 ' full-scale reading
Dim adcVal as UnsignedInteger
' wait for the ADC to be available to use.
Do While CBool(Register.ADCA_CTRLA And &H01)
Sleep(1)
Loop
Call LockTask()
' set the prescaler to yield an ADC clock <= 800KHz
Register.ADCA_PRESCALER = ADC_PS_32
' select the desired channel
Register.ADCA_CH0_MUXCTRL = Shl(chan, 3) And &H38
' select single-ended input mode
Register.ADCA_CH0_CTRL = &H01
Register.ADCA_CTRLB = &H00
' select Vcc/1.6 reference
Register.ADCA_REFCTRL = &H10
' clear the "done" (interrupt) flag
Register.ADCA_CH0_INTFLAGS = &H01
' enable the ADC and allow for the needed "settling time"
Register.ADCA_CTRLA = &H01
Call Sleep(0.016)
' start the conversion on channel 0
Register.ADCA_CTRLA = &H05
' Wait for conversion completion.
Do While Not CBool(Register.ADCA_CH0_INTFLAGS And &H01)
Sleep(1)
Loop
' read the result
adcVal = Register.ADCA_CH0_RES
' disable the ADC, clear the "done" flag
Register.ADCA_CTRLA = &H00
Register.ADCA_CH0_INTFLAGS = &H01
Call UnlockTask()
' convert the ADC result to voltage
readADC = CSng(adcVal) * ADC_REF / ADC_FS
End Function
Last edited by dkinzer on 10 September 2013, 15:27 PM, edited 1 time in total.
- Don Kinzer
Re: ADC with internal 2.56v reference on -24x
The code that I originally posted was actually using the 1.0V internal reference. I changed line 25 in the previous post to have it correctly use the Vcc/1.6 reference.dkinzer wrote:Here is code for a routine similar to that in AN-217 for the xmega but it uses the Vcc/1.6 reference.
Note that the 1.0V internal reference in generated using the internal bandgap device. Since ZX devices are shipped with the brown-out detector enabled, the bandgap is already running. This means that the "BANDGAP" bit in the REFCTRL register is ignored.
- Don Kinzer
ADC with internal 2.56v reference on -24x
>... ' enable the ADC and allow for the needed "settling time"
> Register.ADCA_CTRLA = &H01
> Call Sleep(0.016)
Sixteen milliseconds?
All I found about ADC settling time is in Atmel app note AVR1300 - about the Bandgap Startup Time, on the order of a few uS; you said, though that the Bandgap is already operating so is even that period necessary?
I suspect I'll need to do the ADC read in C since I'm intending to sample at ~8kHz - and will enjoy faster if that's possible. Is that feasible?
> Register.ADCA_CTRLA = &H01
> Call Sleep(0.016)
Sixteen milliseconds?
All I found about ADC settling time is in Atmel app note AVR1300 - about the Bandgap Startup Time, on the order of a few uS; you said, though that the Bandgap is already operating so is even that period necessary?
I suspect I'll need to do the ADC read in C since I'm intending to sample at ~8kHz - and will enjoy faster if that's possible. Is that feasible?
Tom
Re: ADC with internal 2.56v reference on -24x
The "settling time" is required for the ADC to stabilize even though the bandgap has already stabilized. I've seen it recommended to do a conversion and discard the result as a means to allow the ADC to stabilize. In other places, I've seen Atmel recommend waiting 24 ADC clock cycles after enabling the ADC to allow for settling.GTBecker wrote:[...] though that the Bandgap is already operating so is even that period necessary?
You are right, of course, that this time is on the order of microseconds not milliseconds as I had indicated.
- Don Kinzer
Re: ADC with internal 2.56v reference on -24x
I have little to worry about. A minimal loop - in ZBasic - reads an ADC pin at 165kHz on a -24x! Even fattened up, I expect the read loop will be plenty fast - and 12 bits. I am amazed.GTBecker wrote:... intending to sample at ~8kHz - and will enjoy faster if that's possible...
Tom
I am puzzled by the behavior of a simple task. The Main code below starts a task that spins, reading the ADC into a local variable inside the task sub, ival, and displays the apparent task spin rate once per second. If ival is a local var the spin rate is very fast, ~165kHz; if ival is defined at module level, however - or if ival is copied to a module-level var, jvar, the spin rate becomes very low, ~150Hz. Why?
Code: Select all
' Connect a variable voltage source to pin indicated.
#if Option.TargetDevice = "ZX128a1"
Const pin as Byte = A.1
#else
Const pin as Byte = 14
#endif
' Set up the conversion parameters
#if Option.TargetFamily = "ATxmega"
Const vref as Single = 3.3 / 1.6
Const fs as UnsignedInteger = 4096
#else
Const vref as Single = 5.0
Const fs as UnsignedInteger = 1024
#endif
dim StartCount as long, StopCount as long, SpinCount as long, jval as integer = 0
dim ReadSpinTaskStack(1 to 200) as byte
Sub Main()
Debug.Print Option.TargetFamily; ": vref="; Fmt(vref, 3); ", FS="; fs
CallTask ReadSpinTask, ReadSpinTaskStack
do
Startcount = SpinCount
Call Sleep(1.0)
StopCount = SpinCount
Debug.Print "StartCount= "; CStr(StartCount); " Loops/Sec="; CStr(StopCount - StartCount); " jval= "; cstr(jval)
loop
End Sub
Sub ReadSpinTask()
Dim ival as Integer ' try this definition at module level
Do
ival = GetADC(pin)
'jval = ival ' or copy to a module-level var
SpinCount = SpinCount + 1
Call Sleep(0)
Loop
End Sub
Tom
I don't know for certain but I suspect it is because you aren't accessing a multi-byte variable in a thread safe manner. The issue is that when reading/writing a multi-byte shared variable you must take steps to guarantee atomic access. Since both of your tasks access the SpinCount and they can both be interrupted at any time (including in the middle of a read or write of the variable) you need to add extra code to guarantee atomic access. The simplest way to do this is to use the Atomic Block construct:GTBecker wrote:[...]the spin rate becomes very low, ~150Hz. Why?
Code: Select all
Atomic
SpinCount = SpinCount + 1
End Atomic
- Don Kinzer
I had previously tried a semaphore around three SpinCount expressions but neither it nor Atomic helps.
The issue seems related to storing a local value (ival or jval) to module level.
The issue seems related to storing a local value (ival or jval) to module level.
Code: Select all
' Connect a variable voltage source to pin indicated.
#if Option.TargetDevice = "ZX128a1"
Const pin as Byte = A.1
#else
Const pin as Byte = 14
#endif
' Set up the conversion parameters
#if Option.TargetFamily = "ATxmega"
Const vref as Single = 3.3 / 1.6
Const fs as UnsignedInteger = 4096
#else
Const vref as Single = 5.0
Const fs as UnsignedInteger = 1024
#endif
dim StartCount as long, StopCount as long, SpinCount as long, jval as integer = 0
dim ReadSpinTaskStack(1 to 200) as byte
Sub Main()
Debug.Print Option.TargetFamily; ": vref="; Fmt(vref, 3); ", FS="; fs
CallTask ReadSpinTask, ReadSpinTaskStack
do
Atomic
Startcount = SpinCount
End Atomic
Call Sleep(1.0)
Atomic
StopCount = SpinCount
End Atomic
Debug.Print "StartCount= "; CStr(StartCount); " Loops/Sec="; CStr(StopCount - StartCount); " jval= "; cstr(jval)
loop
End Sub
Sub ReadSpinTask()
Dim ival as Integer ' try this definition at module level
Do
ival = GetADC(pin)
'jval = ival ' or copy to a module-level var
Atomic
SpinCount = SpinCount + 1
End Atomic
Call Sleep(0)
Loop
End Sub
Tom
Don, I am stumped. Can you duplicate this behavior on a ZX-24x? If ival is copied to ivalp1, ReadSpinTask slows to ~150Hz.
Here, ival goes through two intermediary vars to be displayed in Main, ivalp1 at module level and ivalp2, a local in Main (to keep the debug.print out of Main's atomic section).
Here, ival goes through two intermediary vars to be displayed in Main, ivalp1 at module level and ivalp2, a local in Main (to keep the debug.print out of Main's atomic section).
Code: Select all
#if Option.TargetDevice = "ZX128a1"
Const pin as Byte = A.1
#else
Const pin as Byte = 14
#endif
' Set up the conversion parameters
#if Option.TargetFamily = "ATxmega"
Const vref as Single = 3.3 / 1.6
Const fs as UnsignedInteger = 4096
#else
Const vref as Single = 5.0
Const fs as UnsignedInteger = 1024
#endif
dim StartCount as long, StopCount as long
dim SpinCount as long, ivalp1 as integer
dim ReadSpinTaskStack(1 to 100) as byte
Sub Main()
Dim ivalp2 as integer
Debug.Print Option.TargetFamily; ": vref="; Fmt(vref, 3); ", FS="; fs
CallTask ReadSpinTask, ReadSpinTaskStack
Do
Atomic
Startcount = SpinCount
End Atomic
Call Sleep(1.0)
Atomic
ivalp2 = ivalp1 ' copy ival from module level
StopCount = SpinCount
End Atomic
Debug.Print "StartCount= "; CStr(StartCount); " Loops/Sec= "; CStr(StopCount - StartCount); " ival= "; cstr(ivalp2)
Loop
End Sub
Sub ReadSpinTask()
Dim ival as Integer
Do
ival = GetADC(pin)
Atomic
'ivalp1 = ival ' copy ival to module level
SpinCount = SpinCount + 1
End Atomic
Call Sleep(0)
Loop
End Sub
Tom
The behavior you noted is observable on both the ZX-24x and the ZX-128a1. The reason for it is that when the copy to the module level variable is commented out the compiler sees that the result of the call to GetADC() is not used and decides that calling GetADC is pointless and therefore removes the call.GTBecker wrote:Can you duplicate this behavior on a ZX-24x?
The modified ReadSpinTask() below achieves a sample frequency of about 65KHz. The first part of the code configures and enables the ADC and then the loop starts a conversion and waits for the result.
Code: Select all
Sub ReadSpinTask()
Const ADC_PS_32 as Byte = 3 ' divide-by-32 prescaler selector
Const ADC_PS_64 as Byte = 4 ' divide-by-64 prescaler selector
' set the prescaler to select the ADC clock
Register.ADCA_PRESCALER = ADC_PS_32
' select the desired channel using the bit index of the pin
Dim chan as Byte
chan = PortBit(pin) And &H03
Register.ADCA_CH0_MUXCTRL = Shl(chan, 3) And &H38
' select single-ended input mode
Register.ADCA_CH0_CTRL = &H01
Register.ADCA_CTRLB = &H00
' select Vcc/1.6 reference
Register.ADCA_REFCTRL = &H10
' clear the "done" (interrupt) flag
Register.ADCA_CH0_INTFLAGS = &H01
' enable the ADC and allow for the needed "settling time"
Register.ADCA_CTRLA = &H01
Call Sleep(1)
Do
Atomic
' start the conversion on channel 0
Register.ADCA_CTRLA = &H05
' wait for conversion completion
Do While Not CBool(Register.ADCA_CH0_INTFLAGS And &H01)
Loop
' read the result and clear the "done" flag
ivalp1 = CInt(Register.ADCA_CH0_RES)
Register.ADCA_CH0_INTFLAGS = &H01
SpinCount = SpinCount + 1
End Atomic
Call Sleep(0)
Loop
End Sub
Last edited by dkinzer on 17 September 2013, 8:44 AM, edited 2 times in total.
- Don Kinzer
I should have mentioned that I confirmed that this was the case by examining the C code produced. The first code sequence below is with the assignment to the module-level variable commented out and the second is with it in. The compiler option --keep-files is needed in the .pjt file in order to observe the C files.dkinzer wrote:[...] the compiler sees that the result of the call to GetADC() is not used and decides that calling GetADC is pointless and therefore removes the call.
Code: Select all
void
zf_ReadSpinTask(void)
{
while (1)
{
zbBeginAtomic
mzv_SpinCount++;
zbEndAtomic
zbTaskSleep(0);
}
}
Code: Select all
void
zf_ReadSpinTask(void)
{
while (1)
{
zbBeginAtomic
int16_t zv_ival;
zv_ival = zbGetADC(129, ZB_TRUE);
mzv_ivalp1 = zv_ival;
mzv_SpinCount++;
zbEndAtomic
zbTaskSleep(0);
}
}
- Don Kinzer