-
-
Notifications
You must be signed in to change notification settings - Fork 7k
ADC initialization mistake in ADC.h #1418
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Comments
The values 40 and 12 accurately describe the number of microseconds required, but there is no equation that goes from those times to the values set in the STARTUP bitfield; it's more of a map that correlates bitfield values to numbers of ADC clock cycles: For the Due, the ADC clock is determined by a prescalar from MCK, and it's available in the code as a function. The code in adc.c for the function adc_init appears to lack this table above, and instead uses the values ADC_STARTUP_NORM directly. |
The hardware specification state that 20 ADCClock cycles are required for each conversion, so the minimum safe value is "3" => 24 ADCClock. Thanks! |
Hi, Cristian, I could not determine the correct safe value because the number of ADCClock = MCK / ( (PRESCAL+1) * 2 ) (spec sheet page 1346) Can you clarify why 20 ADCClock cycles is always correct? Joe On 9/25/2013 4:25 PM, Cristian Maglie wrote:
|
Well, it took some time for me to decode, from the following table: We deduce that ADCClock should be <= 20MHZ. The CPU on the Due runs at 84MHz so we are forced to use PRESCALER=2 that gives 84/(2+1)_2=14Mhz. Using PRESCALER=1 gives 84/(1+1)_2=21 that is 1Mhz off (the ADC will probably runs fine also with a small overclock like this, but that its the final user's choice, we can't deliver code that don't operate in the safe area). From the following table: we see that conversion time is always 20 ADCClock (well, its 20 T_cp_adc that is 1/f_adc). This is my understating of the Datasheet, I'll be glad if someone can confirm if is correct. Thanks! |
Hi, On 9/26/2013 2:19 AM, Cristian Maglie wrote:
I agree.
The CPU runs at HCLK rates. The ADC is derived from MCK rates. MCK is derived from the external I'm not sure what the scale for MCK is. However, assuming no scale, MCK
ADCClock = MCK / ( (PRESCAL+1) * 2 ) MCK = 12 MHz (let's assume) Using PRESCALER =1 (is that what it is?) then ADCClock is 3 MHz. Even
I don't understand that. STARTUP varies from 0 to 960 periods of the So I don't think this is correct. I think the answer in the code should be:
(where "lookup_in_table" finds the STARTUP value that corresponds to the I cannot see a way to avoid including the table and a lookup function. I Joe
Joe
|
Ok, let's face one piece at a time. The 12MHz external oscillator goes through PLLA that scales clock to 84Mhz. The Due Board runs at 84Mhz there is no doubt about that, so MCK is 84M. Setup code here: MCK==84M means that we must set the PRESCAL value to 2, because ADCClock = MCK / ( (PRESCAL+1) * 2 ) = 84M / ((2+1)*2 = 14M using 1 we get: ADCClock = MCK / ( (PRESCAL+1) * 2 ) = 84M / ((1+1)*2 = 21M > 20M (as per datasheet specification) do you agree until now? |
Hi, Christian, On 9/26/2013 5:49 AM, Cristian Maglie wrote:
PLLA is a divider (increases frequency), so you're saying DIV is set to
AOK. Got it ;-)
Yes. I see now. I agree that the conversion time is 20 ADC clocks, but that's not If ADCClock is 14MHz (I agree now), then:
ADC_STARTUP_NORM is 40 (microseconds)
i.e., 71.143 ns so we should lookup the appropriate value for (40 microsec / 71.143 ns) or
So STARTUP should be "9" - i.e., 576 cycles. 8 is too small (512 cycles), and 3 (24 cycles) is way too small. AFAICT ;-) Joe |
PS - this also explains why the original value of writing Writing 40 effectively writes 12 (because the register uses only the Note that the max is 40 microseconds only when going from off to normal If we went with typical values rather than max (worst case), we'd get a This all matters only when going from OFF to running, or standby to Joe On 9/26/2013 6:24 AM, Joe Touch wrote:
|
Joe, you're right, I misinterpreted that part of the datasheet and totally agree with you now. Looking at the adc_init function I see also that is buggy and it fails calculations:
in our case:
it will set ul_precal to = ul_mck / (2 * ul_adc_clock) - 1 = 84M / (2 * 20M) -1 = 1 Besides that, remains the last question: if this is the "startup" time, it should be applied only to the first ADC conversion, while the following should run at the maximum speed allowing a theoretical conversion rate of 700K in our case right? C |
Hi, Cristian, On 9/26/2013 7:38 AM, Cristian Maglie wrote:
We both learned a lot in this process, IMO ;-)
Right; that's what threw up a red flag to me; I expected the table.
AFAICT, yes - and there should be one of two different startups Joe
|
I've pushed a fix to the adc_init() function, now the register configuration is correct, but the gain in ADC performance is gone (it runs almost at the same speed as before). I see that the analogRead() function enable and disable the ADC each time is called, may this force the ADC to always wait the startup time causing the poor conversion speed? C |
Hi, On 10/9/2013 7:02 AM, Cristian Maglie wrote:
Right; that was what I expected. The number used before was basically
The setting we need depends on whether the system is in sleep mode or
Fast wakeup isn't worth it. It might be useful to do something to allow the user to say "don't sleep The specs claim the DAC costs 12.34 microamps/Mhz, so running the ADC Joe
|
I just did; the cost is 5-9mA vs. using sleep mode. I think it's worth the benefit to the ADC. |
Thanks. C |
En français... google translation in English File 'wiring_analog.c' line 152 now : // Enable the corresponding channel
if (ulChannel != latestSelectedChannel) {
adc_disable_channel(ADC,latestSelectedChannel);
adc_enable_channel( ADC, ulChannel );
latestSelectedChannel = ulChannel;
} File 'wiring_analog.c' line 152 before : // Enable the corresponding channel
if (ulChannel != latestSelectedChannel) {
adc_enable_channel( ADC, ulChannel );
latestSelectedChannel = ulChannel;
} |
Hi @seb46, |
In IDE 1.5.5-r2 with the modification that I had brought my program run. I compiled my sources with 1.5.6-r2 IDE. But for some reason I have not yet found the analog output provided me a bad value and fixed. I would look better tonight. |
I found ... It was simple. He just put faillait 'adc_disable_channel' before 'adc_enable_channel' File 'wiring_analog.c' line 150 now : // Enable the corresponding channel
if (ulChannel != latestSelectedChannel) {
if ( latestSelectedChannel != -1 )
adc_disable_channel( ADC, latestSelectedChannel );
adc_enable_channel( ADC, ulChannel );
latestSelectedChannel = ulChannel;
} File 'wiring_analog.c' line 150 before : // Enable the corresponding channel
if (ulChannel != latestSelectedChannel) {
adc_enable_channel( ADC, ulChannel );
if ( latestSelectedChannel != -1 )
adc_disable_channel( ADC, latestSelectedChannel );
latestSelectedChannel = ulChannel;
} |
@seb46 May I ask you to try to "double" the readings, for example in your sketch where you do:
you should do that twice (or even three times) and keep only the last reading:
and see if the thing go better? |
If the file if (ulChannel != latestSelectedChannel) {
adc_enable_channel( ADC, ulChannel );
if ( latestSelectedChannel != -1 )
adc_disable_channel( ADC, latestSelectedChannel );
latestSelectedChannel = ulChannel;
} The more analogRead(1) is great, the better the result. But after 10 analogRead(1), it is still not quite right. If, against the file 'wiring_analog.c' I reverse: if (ulChannel != latestSelectedChannel) {
if ( latestSelectedChannel != -1 )
adc_disable_channel( ADC, latestSelectedChannel );
adc_enable_channel( ADC, ulChannel );
latestSelectedChannel = ulChannel;
} From the first analogRead (1), the result is good. On the analog input of the photoresist and the analog input of the touch screen. |
Hi @seb46 may you give me some more details on your test circuit (for example the photoresistor and the additional resistors values) so I can try to reproduce the problem? Thank you! |
In simple terms, here's a tip from one end of the circuit and code first of all : Photoresistor : Touchscreen with TFT ILI9328 : void setup(void)
{
...
analogReadResolution(12);
...
//For Photoresistor
...
pinMode(54,OUTPUT);
...
//For Touchscreen
//Pin_xp=30
//Pin_yp=65
//Pin_xm=64
//Pin_ym=33
...
Bit_xp=digitalPinToBitMask(Pin_xp);
Port_xp=digitalPinToPort(Pin_xp);
Bit_yp=digitalPinToBitMask(Pin_yp_ana);
Port_yp=digitalPinToPort(Pin_yp_ana);
Bit_xm=digitalPinToBitMask(Pin_xm_ana);
Port_xm=digitalPinToPort(Pin_xm_ana);
Bit_ym=digitalPinToBitMask(Pin_ym);
Port_ym=digitalPinToPort(Pin_ym);
Port_xp->PIO_IDR=Bit_xp;
Port_xp->PIO_PUDR=Bit_xp;
Port_xp->PIO_MDER=Bit_xp;
Port_xp->PIO_IFDR=Bit_xp;
Port_xp->PIO_OER=Bit_xp;
PMC->PMC_PCER0=1<<g_APinDescription[Pin_xp].ulPeripheralId;
Port_xp->PIO_CODR=Bit_xp;
Port_xp->PIO_OER=Bit_xp;
Port_xp->PIO_PER=Bit_xp;
Port_yp->PIO_IDR=Bit_yp;
Port_yp->PIO_PUDR=Bit_yp;
Port_yp->PIO_MDER=Bit_yp;
Port_yp->PIO_IFDR=Bit_yp;
Port_yp->PIO_OER=Bit_yp;
PMC->PMC_PCER0=1<<g_APinDescription[Pin_yp_ana].ulPeripheralId;
Port_yp->PIO_CODR=Bit_yp;
Port_yp->PIO_OER=Bit_yp;
Port_yp->PIO_PER=Bit_yp;
Port_xm->PIO_IDR=Bit_xm;
Port_xm->PIO_PUDR=Bit_xm;
Port_xm->PIO_MDER=Bit_xm;
Port_xm->PIO_IFDR=Bit_xm;
Port_xm->PIO_OER=Bit_xm;
PMC->PMC_PCER0=1<<g_APinDescription[Pin_xm_ana].ulPeripheralId;
Port_xm->PIO_CODR=Bit_xm;
Port_xm->PIO_OER=Bit_xm;
Port_xm->PIO_PER=Bit_xm;
Port_ym->PIO_IDR=Bit_ym;
Port_ym->PIO_PUDR=Bit_ym;
Port_ym->PIO_MDER=Bit_ym;
Port_ym->PIO_IFDR=Bit_ym;
Port_ym->PIO_OER=Bit_ym;
PMC->PMC_PCER0=1<<g_APinDescription[Pin_ym].ulPeripheralId;
Port_ym->PIO_CODR=Bit_ym;
Port_ym->PIO_OER=Bit_ym;
Port_ym->PIO_PER=Bit_ym;
...
}
void loop(void)
{
//For Photoresistor
...
XXX=analogRead(54):
...
//For Touchscreen
...
Port_xp->PIO_CODR=Bit_xp; //digitalWrite(Pin_xp,LOW);
Port_ym->PIO_SODR=Bit_ym; //digitalWrite(Pin_ym,HIGH);
Port_xp->PIO_OER=Bit_xp; //pinMode(Pin_xp,OUTPUT);
Port_ym->PIO_OER=Bit_ym; //pinMode(Pin_ym,OUTPUT);
Port_xm->PIO_CODR=Bit_xm; //digitalWrite(Pin_xm,LOW);
Port_yp->PIO_CODR=Bit_yp; //digitalWrite(Pin_yp,LOW);
Port_xm->PIO_ODR=Bit_xm; //pinMode(Pin_xm,INPUT);
Port_yp->PIO_ODR=Bit_yp; //pinMode(Pin_yp,INPUT);
z1=analogRead(Pin_xm);
z2=analogRead(Pin_yp);
...
Port_xp->PIO_SODR=Bit_xp; //digitalWrite(Pin_xp,HIGH);
Port_xm->PIO_CODR=Bit_xm; //digitalWrite(Pin_xm,LOW);
Port_xp->PIO_OER=Bit_xp; //pinMode(Pin_xp,OUTPUT);
Port_xm->PIO_OER=Bit_xm; //pinMode(Pin_xm,OUTPUT);
Port_yp->PIO_CODR=Bit_yp; //digitalWrite(Pin_yp,LOW);
Port_ym->PIO_CODR=Bit_ym; //digitalWrite(Pin_ym,LOW);
Port_yp->PIO_ODR=Bit_yp; //pinMode(Pin_yp,INPUT);
Port_ym->PIO_ODR=Bit_ym; //pinMode(Pin_ym,INPUT);
for (cpt=0;cpt\<9;cpt++) {
ListeX[cpt]=analogRead(Pin_yp);
...
} //for
...
Port_yp->PIO_SODR=Bit_yp; //digitalWrite(Pin_yp,HIGH);
Port_ym->PIO_CODR=Bit_ym; //digitalWrite(Pin_ym,LOW);
Port_yp->PIO_OER=Bit_yp; //pinMode(Pin_yp,OUTPUT);
Port_ym->PIO_OER=Bit_ym; //pinMode(Pin_ym,OUTPUT);
Port_xp->PIO_CODR=Bit_xp; //digitalWrite(Pin_xp,LOW);
Port_xm->PIO_CODR=Bit_xm; //digitalWrite(Pin_xm,LOW);
Port_xp->PIO_ODR=Bit_xp; //pinMode(Pin_xp,INPUT);
Port_xm->PIO_ODR=Bit_xm; //pinMode(Pin_xm,INPUT);
for(cpt=0;cpt\<9;cpt++) {
ListeY[cpt]=analogRead(Pin_xm);
...
} //for
...
Port_xp->PIO_CODR=Bit_xp; //digitalWrite(Pin_xp,LOW);
Port_ym->PIO_SODR=Bit_ym; //digitalWrite(Pin_ym,HIGH);
Port_xp->PIO_OER=Bit_xp; //pinMode(Pin_xp,OUTPUT);
Port_ym->PIO_OER=Bit_ym; //pinMode(Pin_ym,OUTPUT);
Port_xm->PIO_CODR=Bit_xm; //digitalWrite(Pin_xm,LOW);
Port_yp->PIO_CODR=Bit_yp; //digitalWrite(Pin_yp,LOW);
Port_xm->PIO_ODR=Bit_xm; //pinMode(Pin_xm,INPUT);
Port_yp->PIO_ODR=Bit_yp; //pinMode(Pin_yp,INPUT);
z1=analogRead(Pin_xm);
z2=analogRead(Pin_yp);
;...
} |
On 2/26/2014 10:51 AM, seb46 wrote:
It might be preferable to use analog pins that aren't overloaded with
The above is an error; it should be INPUT.
Why are the above commented out, when you use symbolic names in the rest
Not sure what you're doing below here: these are analog pins.
Why are you referring to this by number, vs. using "A0"? And why not refer to it by name (photo_in), where you could say: #define photo_in A0
|
The code I put, is only a small piece of that 155KB program. This is the reason Laquel I did a shortcut in the code that I put. Bit_XX=digitalPinToBitMask(Pin_XX); But in any case, if the file 'wiring_analog.c' I put: if (ulChannel != latestSelectedChannel) {
if ( latestSelectedChannel != -1 )
adc_disable_channel( ADC, latestSelectedChannel );
adc_enable_channel( ADC, ulChannel );
latestSelectedChannel = ulChannel;
} From the first analogRead(X), the result is good. |
Hi @seb46 Your analog sources have a very high impedance (on the MOhm magnitude) and the internal sample-hold capacitor is probably affecting your signal during the first read (it would be cool to have a scope reading to confirm this). Now, swapping the adc_enable/disable statements, as you did, will slow down the ADC sample rate (because it adds the ADC startup-time every time) and probably this helps the capacitor to fully charge-up before the conversion. Another guy had a similar problem and solved it by placing an Op-Amp between the analog source and the ADC input pin. |
I use a screen TFT 2.8'' of Adafruit (https://www.adafruit.com/products/335) with ILI9328. |
As has been noted by "http://www.djerickson.com/arduino/" (all credits to that person).
It seems there may be a mistake in the ADC initialization which results in a relatively slow analogRead, for a full description of his finding go to the link, but the summary of the problem (quoted):
Here are the #defines for the STARTUP field from
\arduino\sam\system\libsam\include\adc.h
This may be a bug. STARTUP is a four bit field so 12 (0xC) works, but 40 (0x28) won't work. And 12 sets the value to 768 which is why the ADC is so slow. So I suspect that the programmer confused the value with the hardware settings. The table of values vs. register settings is in the SAM3S processor manual.
The text was updated successfully, but these errors were encountered: