ADC with internal 2.56v reference on -24x

Discussion specific to the 24-pin ZX microcontrollers, e.g. ZX-24r, ZX-24s and ZX-24t.
GTBecker
Posts: 616
Joined: 17 January 2006, 19:59 PM
Location: Cape Coral

ADC with internal 2.56v reference on -24x

Post by GTBecker »

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
dkinzer
Site Admin
Posts: 3120
Joined: 03 September 2005, 13:53 PM
Location: Portland, OR

Re: ADC with internal 2.56v reference on -24x

Post by dkinzer »

GTBecker wrote:Is there any reason why AN-217 code would not work on a -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.

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
dkinzer
Site Admin
Posts: 3120
Joined: 03 September 2005, 13:53 PM
Location: Portland, OR

Re: ADC with internal 2.56v reference on -24x

Post by dkinzer »

GTBecker wrote:Is there any reason why AN-217 code would not work on a -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.
- Don Kinzer
GTBecker
Posts: 616
Joined: 17 January 2006, 19:59 PM
Location: Cape Coral

ADC with internal 2.56v reference on -24x

Post by GTBecker »

> ... 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
Tom
dkinzer
Site Admin
Posts: 3120
Joined: 03 September 2005, 13:53 PM
Location: Portland, OR

Re: ADC with internal 2.56v reference on -24x

Post by dkinzer »

GTBecker wrote:I'll take a crack at it using AN-217 as a template - with the A family docs.
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.

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&#40;chan, 3&#41; 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" &#40;interrupt&#41; flag
    Register.ADCA_CH0_INTFLAGS =  &H01

    ' enable the ADC and allow for the needed "settling time"
    Register.ADCA_CTRLA = &H01
    Call Sleep&#40;0.016&#41;

    ' start the conversion on channel 0
    Register.ADCA_CTRLA = &H05
    
    ' Wait for conversion completion.
    Do While Not CBool&#40;Register.ADCA_CH0_INTFLAGS And &H01&#41;
        Sleep&#40;1&#41;
    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&#40;&#41;

    ' convert the ADC result to voltage
    readADC = CSng&#40;adcVal&#41; * ADC_REF / ADC_FS
End Function
Last edited by dkinzer on 10 September 2013, 15:27 PM, edited 1 time in total.
- Don Kinzer
dkinzer
Site Admin
Posts: 3120
Joined: 03 September 2005, 13:53 PM
Location: Portland, OR

Re: ADC with internal 2.56v reference on -24x

Post by dkinzer »

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

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
GTBecker
Posts: 616
Joined: 17 January 2006, 19:59 PM
Location: Cape Coral

ADC with internal 2.56v reference on -24x

Post by GTBecker »

>... ' 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?
Tom
dkinzer
Site Admin
Posts: 3120
Joined: 03 September 2005, 13:53 PM
Location: Portland, OR

Re: ADC with internal 2.56v reference on -24x

Post by dkinzer »

GTBecker wrote:[...] though that the Bandgap is already operating so is even that period necessary?
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.

You are right, of course, that this time is on the order of microseconds not milliseconds as I had indicated.
- Don Kinzer
GTBecker
Posts: 616
Joined: 17 January 2006, 19:59 PM
Location: Cape Coral

Re: ADC with internal 2.56v reference on -24x

Post by GTBecker »

GTBecker wrote:... intending to sample at ~8kHz - and will enjoy faster if that's possible...
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.
Tom
GTBecker
Posts: 616
Joined: 17 January 2006, 19:59 PM
Location: Cape Coral

Post by GTBecker »

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&#40;1 to 200&#41; as byte

Sub Main&#40;&#41;
	Debug.Print Option.TargetFamily; "&#58; vref="; Fmt&#40;vref, 3&#41;; ", FS="; fs
	CallTask ReadSpinTask, ReadSpinTaskStack
	do
		Startcount = SpinCount
		Call Sleep&#40;1.0&#41;
		StopCount = SpinCount
		Debug.Print "StartCount= "; CStr&#40;StartCount&#41;; " Loops/Sec="; CStr&#40;StopCount - StartCount&#41;; " jval= "; cstr&#40;jval&#41;
	loop
End Sub

Sub ReadSpinTask&#40;&#41;
    Dim ival as Integer	' try this definition at module level
    Do
		ival = GetADC&#40;pin&#41;
		'jval = ival		' or copy to a module-level var
		SpinCount = SpinCount + 1
		Call Sleep&#40;0&#41;
    Loop
End Sub
Tom
dkinzer
Site Admin
Posts: 3120
Joined: 03 September 2005, 13:53 PM
Location: Portland, OR

Post by dkinzer »

GTBecker wrote:[...]the spin rate becomes very low, ~150Hz. Why?
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:

Code: Select all

Atomic
    SpinCount = SpinCount + 1
End Atomic
For more information, see Executing Blocks of Code Atomically in the ZBasic Language Reference and the application note AN-210 Sharing Data Between Tasks.
- Don Kinzer
GTBecker
Posts: 616
Joined: 17 January 2006, 19:59 PM
Location: Cape Coral

Post by GTBecker »

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.

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&#40;1 to 200&#41; as byte

Sub Main&#40;&#41;
    Debug.Print Option.TargetFamily; "&#58; vref="; Fmt&#40;vref, 3&#41;; ", FS="; fs
    CallTask ReadSpinTask, ReadSpinTaskStack
    do
        Atomic
            Startcount = SpinCount
        End Atomic
        Call Sleep&#40;1.0&#41;
        Atomic
            StopCount = SpinCount
        End Atomic
        Debug.Print "StartCount= "; CStr&#40;StartCount&#41;; " Loops/Sec="; CStr&#40;StopCount - StartCount&#41;; " jval= "; cstr&#40;jval&#41;
    loop
End Sub

Sub ReadSpinTask&#40;&#41;
    Dim ival as Integer    ' try this definition at module level
    Do
        ival = GetADC&#40;pin&#41;
        'jval = ival        ' or copy to a module-level var
        Atomic
            SpinCount = SpinCount + 1
        End Atomic
        Call Sleep&#40;0&#41;
    Loop
End Sub
Tom
GTBecker
Posts: 616
Joined: 17 January 2006, 19:59 PM
Location: Cape Coral

Post by GTBecker »

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

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&#40;1 to 100&#41; as byte

Sub Main&#40;&#41;
	Dim ivalp2 as integer
	Debug.Print Option.TargetFamily; "&#58; vref="; Fmt&#40;vref, 3&#41;; ", FS="; fs
	CallTask ReadSpinTask, ReadSpinTaskStack
	Do
		Atomic
			Startcount = SpinCount
		End Atomic
		Call Sleep&#40;1.0&#41;
		Atomic
			ivalp2 = ivalp1		' copy ival from module level
			StopCount = SpinCount
		End Atomic
		Debug.Print "StartCount= "; CStr&#40;StartCount&#41;; " Loops/Sec= "; CStr&#40;StopCount - StartCount&#41;; " ival= "; cstr&#40;ivalp2&#41;
	Loop
End Sub

Sub ReadSpinTask&#40;&#41;
    Dim ival as Integer
    Do
		ival = GetADC&#40;pin&#41;
		Atomic
			'ivalp1 = ival		' copy ival to module level 
			SpinCount = SpinCount + 1
		End Atomic
		Call Sleep&#40;0&#41;
    Loop
End Sub
Tom
dkinzer
Site Admin
Posts: 3120
Joined: 03 September 2005, 13:53 PM
Location: Portland, OR

Post by dkinzer »

GTBecker wrote:Can you duplicate this behavior on a ZX-24x?
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.

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&#40;&#41;
    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&#40;pin&#41; And &H03
    Register.ADCA_CH0_MUXCTRL = Shl&#40;chan, 3&#41; 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" &#40;interrupt&#41; flag
    Register.ADCA_CH0_INTFLAGS =  &H01

    ' enable the ADC and allow for the needed "settling time"
    Register.ADCA_CTRLA = &H01
    Call Sleep&#40;1&#41;

    Do
      Atomic
         ' start the conversion on channel 0
         Register.ADCA_CTRLA = &H05
   
         ' wait for conversion completion
         Do While Not CBool&#40;Register.ADCA_CH0_INTFLAGS And &H01&#41;
         Loop

         ' read the result and clear the "done" flag
         ivalp1 = CInt&#40;Register.ADCA_CH0_RES&#41;
         Register.ADCA_CH0_INTFLAGS = &H01

         SpinCount = SpinCount + 1
      End Atomic
      Call Sleep&#40;0&#41;
    Loop
End Sub
Last edited by dkinzer on 17 September 2013, 8:44 AM, edited 2 times in total.
- Don Kinzer
dkinzer
Site Admin
Posts: 3120
Joined: 03 September 2005, 13:53 PM
Location: Portland, OR

Post by dkinzer »

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

Code: Select all

void
zf_ReadSpinTask&#40;void&#41;
&#123;
   while &#40;1&#41;
   &#123;
      zbBeginAtomic
         mzv_SpinCount++;
      zbEndAtomic
      zbTaskSleep&#40;0&#41;;
   &#125;
&#125;

Code: Select all

void
zf_ReadSpinTask&#40;void&#41;
&#123;
   while &#40;1&#41;
   &#123;
      zbBeginAtomic
         int16_t zv_ival;

         zv_ival = zbGetADC&#40;129, ZB_TRUE&#41;;
         mzv_ivalp1 = zv_ival;
         mzv_SpinCount++;
      zbEndAtomic
      zbTaskSleep&#40;0&#41;;
   &#125;
&#125;
- Don Kinzer
Post Reply