a/d problem

Coding and general discussion relating to the compiler

Moderators: David Barker, Jerry Messina

richardb
Posts: 307
Joined: Tue Oct 03, 2006 8:54 pm

a/d problem

Post by richardb » Wed Oct 25, 2006 9:06 pm

why dosnt the following ato d code give the correct result ?

Code: Select all

Device = 18f458
Clock = 40
Config osc = hspll
// LCD options...
#option LCD_DATA = PORTD.0
#option LCD_RS = PORTD.4
#option LCD_EN = PORTD.5

// uses LCD and AD libraries...
Include "LCD.bas"
Include "ADC.bas"
Include "convert.bas"
// initialise and clear LCD...
ADCON1 = %10000010'$07       // PORTE as digital (LCD)
TRISA.0 = 1        // configure AN0 as an input 
ADCON1.7 = 1       // set analogue input on PORTA.0 
DelayMS (500)
LCD.Cls

// main program loop...
TRISC.0 = 0
While true
   LCD.WriteAt(1,1,"CH0 = ", DecToStr(ADC.Read(0)), " ")
   DelayMS(100)
Wend
when the following PBP works on the same board.

Code: Select all

Define	LCD_DREG	PORTD
Define	LCD_DBIT	0
Define	LCD_RSREG	PORTD
Define	LCD_RSBIT	4
Define	LCD_EREG	PORTD
Define	LCD_EBIT	5

' Define ADCIN parameters
Define	ADC_BITS	10	' Set number of bits in result
Define	ADC_CLOCK	3	' Set clock source (3=rc)
Define	ADC_SAMPLEUS	50	' Set sampling time in uS
DEFINE OSC 40
adval	var	word		' Create adval to store result


	TRISA.0 = 1
	ADCON1 = %10000010	' Set PORTA analog and right justify result
	Pause 500		' Wait .5 second
 Lcdout $fe, 1		' Clear LCD  
LOOP:
 
    ADCIN 0, adval		' Read channel 0 to adval
	Lcdout $FE,1,"Value: ", DEC adval	,"  "' Display the decimal value  
	Pause 100		' Wait .1 second
	Goto loop		' Do it forever

End
??
Hmmm..

User avatar
Darrel Taylor
Posts: 29
Joined: Wed Oct 04, 2006 4:44 pm
Location: California

Post by Darrel Taylor » Thu Oct 26, 2006 6:44 am

Hi Richard,

In the PBP version, it's using the A/D's Internal RC clock Define ADC_CLOCK 3.
I believe in the Swordfish version, it's FOSC_2 by default. That's too fast for 40mhz.

Try this...

Code: Select all

ADC.SetConvTime(FRC)
Best regards,
DT

richardb
Posts: 307
Joined: Tue Oct 03, 2006 8:54 pm

Post by richardb » Thu Oct 26, 2006 5:31 pm

Hi Darrel, thanks for the help.
I've tried your suggestion and using FOSC_64 but it still seems to approximately the same thing.

voltage SF PBP

1.05 15-40 217-219
2.03 230-250 417-419
3.0 440-470 617-619
4.0 640-670 827-829
4.96 830-850 1023


any other ideas?


PS it would seem better to default to the rc clock or ideally use the _clock to direct the compiler to the correct divide .
Hmmm..

User avatar
David Barker
Swordfish Developer
Posts: 1214
Joined: Tue Oct 03, 2006 7:01 pm
Location: Saltburn by the Sea, UK
Contact:

Post by David Barker » Thu Oct 26, 2006 6:24 pm

Try setting the acquisition time...

Code: Select all

   SetAcqTime(20)
   while true
      ...
The ADC module currently defaults to zero. Perhaps it should not. Any comments?...

User avatar
Darrel Taylor
Posts: 29
Joined: Wed Oct 04, 2006 4:44 pm
Location: California

Post by Darrel Taylor » Thu Oct 26, 2006 6:48 pm

I was just going to say that. :)

I was originally thinking that you wouldn't need to worry about the acquisition time, since Richard was only using 1 channel, so the S/H capacitor would always be charged. But now I noticed that ADC.Read() turns the A/D module OFF after each sample. Power concerns ?? So the acquisition time comes back into play.

So my "comment" would be that it might be better to leave the A/D module turned on. The user can always turn it off for low power usage.

Of course, it would be really cool if there was a way to enter the impeadance and have it set the minimum aquisition time. Too much, I know. :oops:
Best regards,
DT

richardb
Posts: 307
Joined: Tue Oct 03, 2006 8:54 pm

Post by richardb » Thu Oct 26, 2006 8:20 pm

ok that worked
interestingly 2 us gives original problem and 3 us works fine!?

thanks for the help.
Hmmm..

JackOfVA
BETA Tester
Posts: 16
Joined: Tue Oct 03, 2006 9:04 pm
Location: Clifton, VA USA
Contact:

Post by JackOfVA » Thu Oct 26, 2006 9:38 pm

The acquisition time is always relevant even in the case where there is only one ADC channel used, as the holding capacitor is disconnected from the ADC input channel whilst the conversion is being made. At the end of the conversion, the holding capacitor is reconnected. Granted that in most cases there will be a rather long interval (in terms of the normal acquisition time) after the ADC read is completed, so charging can proceed even at a leisurely pace.

To make this post most useful to someone who may read it a year from now and is not as conversant with ADC issues, let's first summarize how the PIC's ADC works.

At some time t=0, the analog input pin is connected to a small value capacitor Chold (120 pF in the 18F458). That analog voltage to be measured charges the capacitor through the series resistance associated with the PIC's multiplexing switch and the voltage source's internal resistance. In the 18F458, the internal resistance is spec'd at "less than 1Kohm."

When the ADC conversion starts, Chold is disconnected from the input pin and its value is read by the conversion stage.

From elementary circuit theory, we know that the voltage across Chold cannot change instantaneously, and in fact we have a classical RC network, where the series R consists of the PIC's internal resistance in series with the resistance of the external voltage source connected to the analog input pin.

Assuming we know the effective external resistance, and if we neglect some of the PIC's internal leakage, it is relatively simple to determine how many time constants the analog input must be connected to the source so that Chold charges to approximate the voltage being measured. Since the 18F458 has a 10-bit ADC, it is reasonable to want the RC changing time to be sufficiently long enough that the acquisition time error is less than 1/1024, or 0.1%, or 1 LSB. (Most would take the desired error budget as 1/2 LSB or even 1/4 LSB, but we'll keep it at 1 LSB for simplicity.)

After one time constant Chold = 63.2...% of the final value, after two time constants it has reached 86.4% etc. We can determine that after seven time constants, the voltage is within 0.1% of the ultimate value.

If the 18F458's internal resistance is at the high side of the spec, 1K, and if the external resistance is, for example, 2K, the total R in our RC network is 3K, and one RC time constant is 3*10^3*120*10^-12 = 0.36 microseconds. Seven time constants is thus about 2.5 microseconds.

So, we need to ensure that our voltage source with an internal resistance of 2K is connected to Chold at least 2.5 microseconds before we start the actual AD conversion process. If we are not pressed for time, longer is better and we can stretch the acquisition time to 10 or 20 usec or more.

Now we are in a position to consider the second part of the process--how long it takes the PIC to convert the voltage on Chold to an accurate digital value. It takes 12 "ticks" for this process. The duration of one "tick" is is governed by the conversion clock setting and the correct parameter can be determined by Table 20-1 and 20-2 in the 18Fxx8 data sheet. The data sheet states that for the 18Fxx8, the minimum acquisition time step (tick length) is 1.6us. For a 40 MHz clock we can determine that this time interval requires the clock to be divided by 64. (40000000/64 = 625000 ticks/sec. 1/625000 = 1.6 us.)

The total conversion process, once the ADC has been initiated, is 12 * 1.6 us, or 19.2 us.

The overall time is:

2.5 us acquisition time for Chold to reach 0.1% of final value
19.2us conversion time

Total time = 21.7 microseconds.

There are some overheads associated with Swordfish that mean this time will not exactly be reached.

All this can be found in the 18Fxx8 Data Sheet, but in my opinion, Microchip's ADC chapter is difficult for a beginner to understand.

To address the question of whether Swordfish's ADC module should stop the ADC module after each read and restart it for the next read...reasonable persons could reach different conclusions in this regard. There is some slight overhead added by starting and stopping the PIC's ADC module, but that is rather modest. There is some additional acquisition time added as has been pointed out, but the acquisition time is generally not that much, compared with the conversion time, at least assuming the ADC is being driven by a lowish impedance source. And if it is not, then the error analysis must be extended to consider errors resulting from the protective diode leakage and other factors. As a general proposition, Microchip recommends ADC driving impedance < 2.5 K and I agree with that.

As far as setting the conversion clock, yes, it absolutely is necessary to select the conversion division ratio based on the 1.6 us interval and the PIC's clock rate. It's not a big deal for me to remember to set it, but I can see that Swordfish would be more beginner friendly if the conversion clock were automatically defaulted to the correct divisor. However, I would not recommend that Dave move that to the top of the priority queue, as I am sure there are many more important things to be added to Swordfish.

I apologize if this discussion is (a) too long or (b) too simplistic, but I hope that it will be of benefit when stumbled across by a beginner next year or the year after.

Jack

Here is a quick mod on the original code that works for me. I don't have an LCD connected to the test board, so I have the output routed to the serial port. I arbitrarily doubled the 2.5 us acquisition time to 5us.

Code: Select all

Device = 18f458
Clock = 40
Config osc = hspll
// LCD options...
#option LCD_DATA = PORTD.0
#option LCD_RS = PORTD.4
#option LCD_EN = PORTD.5

// uses LCD and AD libraries...
Include "LCD.bas"
Include "ADC.bas"
Include "usart.bas"
Include "convert.bas"
// initialise and clear LCD...
ADCON1 = %10000010'$07       // PORTE as digital (LCD)
TRISA.0 = 1        // configure AN0 as an input
ADCON1.7 = 1       // set analogue input on PORTA.0
DelayMS (500)
LCD.Cls

// main program loop...
TRISC.0 = 0

USART.SetBaudrate(br115200)
USART.Write("Init OK",$0A,$0D)
ADC.SetConvTime(FOSC_64)
ADC.SetAcqTime(5)
DelayMS(1000)
   
While true
   'LCD.WriteAt(1,1,"CH0 = ", DecToStr(ADC.Read(0)), " ")
   USART.Write("CH0 = ", DecToStr(ADC.Read(0)),$0A,$0D)
   DelayMS(100)
Wend 
Jack, Clifton VA

TimB
Posts: 262
Joined: Wed Oct 04, 2006 7:25 am
Location: London UK

Post by TimB » Fri Oct 27, 2006 7:05 am

Jack

Just wanted to say thanks for the great explanation.

On the issue of turning on and off the A/D converter. On another compiler I have become used to it turning the A/D converter on and me turning it off when needed.

In most cases power is not an issue so I don't worry about turning it off.

It would be good to have a plug in when you can enter all the parameters and the prog would give you the recommended settings.

richardb
Posts: 307
Joined: Tue Oct 03, 2006 8:54 pm

Post by richardb » Fri Oct 27, 2006 5:30 pm

OK It looks like Darryl was actually correct. I modified the ADC routine by commenting out the "Enabled = false" after the conversion. so by the second time around the adc was already enabled.

This allowed the adc to give correct result even with the ADC.SetAcqTime set to zero. i realise a larger delay is required when using the multiplexer but in this case...
Hmmm..

User avatar
David Barker
Swordfish Developer
Posts: 1214
Joined: Tue Oct 03, 2006 7:01 pm
Location: Saltburn by the Sea, UK
Contact:

Post by David Barker » Fri Oct 27, 2006 5:50 pm

If you plan to make changes to a compiler library, it's probably best to save in the 'UserLibrary' folder and rename the file. For example, xADC.bas or whatever. Then just include in your program using the new filename (you don't need to rename the actual module).

This way, your code changes won't be overwritten if you update the compiler. Just change the implementation to suit your needs. For example,

Code: Select all

function ReadADC(pChannel as byte) as word
   // set channel...
   CHS0 = pChannel.0
   CHS1 = pChannel.1
   CHS2 = pChannel.2
   #if _adc > 8
   CHS3 = pChannel.3
   #endif
   
   Convert = true
   while Convert      
   wend           
   Result = ADResult
end function
Your main program will just need to call the public 'enable' flag. For example,

Code: Select all

ADC.Enabled = true
Value = ADC.Read(0)

richardb
Posts: 307
Joined: Tue Oct 03, 2006 8:54 pm

Post by richardb » Fri Oct 27, 2006 7:23 pm

thanks for the info but i only made the change to test the theory
Hmmm..

JackOfVA
BETA Tester
Posts: 16
Joined: Tue Oct 03, 2006 9:04 pm
Location: Clifton, VA USA
Contact:

Post by JackOfVA » Fri Oct 27, 2006 8:10 pm

OK It looks like Darryl was actually correct. I modified the ADC routine by commenting out the "Enabled = false" after the conversion. so by the second time around the adc was already enabled.

This allowed the adc to give correct result even with the ADC.SetAcqTime set to zero. i realise a larger delay is required when using the multiplexer but in this case...[
In fact, this change gives a rather lengthy acquisition time, as the acquisition time is equal to the time it takes your code to execute from the time the ADC ceases the conversion and reconnects the holding capacitor to the input pin. Thus, the acquisition time > 100ms (the delay) plus whatever time the LCD routine consumes.

When using the multiplexer a delay of a few microseconds will be sufficient, assuming the impedance of the voltage source you are measuring is under a couple thousand ohms.
Jack, Clifton VA

User avatar
Darrel Taylor
Posts: 29
Joined: Wed Oct 04, 2006 4:44 pm
Location: California

Post by Darrel Taylor » Fri Oct 27, 2006 10:26 pm

Ok, so using some of Jack's formulas, and some of my own (and of course, the datasheet), I came up with a solution to my previous "Wish".

It's a simple module that will automatically set up the ADC for the Minimum clock and aquisition time according to your selected clock frequency and impedance. It's done mainly with constants, so it uses very little code space.

Using it is simple, just set the resistance option, and include the module...

Code: Select all

#option AD_RESIST = 2000
Include "MinADtime.bas"
Where AD_RESIST is the external impedance of your analog circuit in OHMs.
If omitted, it will default to 10k

You don't need to call any Sub's. It initializes it for you. Although you can call the SetMinADtime() Sub at any time if needed.

Here's the module...

Code: Select all

Module MinADtime

Include "ADC.bas" 

{ You can include this option in your main program
  to indicate the external impedance in OHMs
    #option AD_RESIST = 2000
}
//---- Set Default AD_RESIST if needed ------------------------------
#if IsOption(AD_RESIST)
  Const ADimpedance  = AD_RESIST
#else
  Const ADimpedance  = 10000
#endif
Const  MinAD_AQ As Byte = (((ADimpedance +1000)*(120*7))/1000000)+1

//---- Find Minimum A/D oscillator requirements ---------------------
#variable  ADoscRaw = 64/(40.0/_clock)
#if ADoscRaw > 64
     Const MinAD_OSC = FRC
#elseIf ADoscRaw > 32
     Const MinAD_OSC = FOSC_64
#elseif ADoscRaw > 16
     Const MinAD_OSC = FOSC_32
#elseif ADoscRaw > 8
     Const MinAD_OSC = FOSC_16
#elseif ADoscRaw > 4
     Const MinAD_OSC = FOSC_8
#elseif ADoscRaw > 2
     Const MinAD_OSC = FOSC_4
#else
     Const MinAD_OSC = FOSC_2
#endif

//-------------------------------------------------------------------
Public Sub SetMinADtime()
     SetConvTime(MinAD_OSC)
     SetAcqTime(MinAD_AQ)
End Sub

Public Sub SetMinADtime(Mult As Byte)
     SetConvTime(MinAD_OSC)
     SetAcqTime(MinAD_AQ * Mult)
End Sub

//----[Initialize]---------------------------------------------------
SetMinADtime
For instance, at 40mhz it will set FOSC_64

And with AD_RESIST = 2000 it will set the aquisition time to 3uS.

Or, if you prefer to have a little safety factor thrown in, you can add a "multiplier" to the SetMinADtime() Sub to increase the aquisition time.

Code: Select all

SetMinADtime(2)
Would give an aquisition time of 6uS with the same numbers above.

I don't know if it's worth putting in the Wiki or not?
--------------------------------
Post Change(s):
Changed the way it rounds MinAD_AQ
From: /100,000+5)/10
To: /1,000,000)+1
-----------------
Changed impeadance to impedance
For some reason impedance looked more like an E.D. problem. :oops:
Best regards,
DT

User avatar
David Barker
Swordfish Developer
Posts: 1214
Joined: Tue Oct 03, 2006 7:01 pm
Location: Saltburn by the Sea, UK
Contact:

Post by David Barker » Sun Oct 29, 2006 4:53 pm

> I don't know if it's worth putting in the Wiki or not?

Yes, why not - it will be easier for people to find...

xor
Posts: 286
Joined: Sun Nov 05, 2006 1:15 pm
Location: NYC
Contact:

Post by xor » Sun Nov 19, 2006 3:31 pm

I would like to recommend that Jack's ADC explanation find its way to the WIKI. Better yet, include examples of the ADC library as part of the discourse.

Post Reply