Go Down

Topic: Reading the temperature sensor inside Arduino Due (Read 1 time) previous topic - next topic

Palliser

Aug 30, 2013, 05:53 am Last Edit: Aug 30, 2013, 05:55 am by Palliser Reason: 1
Here my 'ported' raw code of the internal temperature sensor example from Atmel AS6 ASF. It run OK in my Due.
The temperature values match accurately the ones from the original code (Arduino Due/X). Despite the sensor it is not accurate (+-15%) it could be use as a warning if the SAM3X8E gets low-temps (~ -40 C)  or over-temps (~ +85 C). Anyway, a good start could be to calibrate your Due's sensor at ambient temperature, getting 0.8V and knowing that the temperature slope dVT/dT = 2.65 mV/C. I did the calibration in my Due, and around 25.5C in my room I got 800 of ul_value.(that's why my value in code line 44).

Code: [Select]
/** Example - ADC temperature sensor for Arduino Due. */

/** Size of the receive buffer and transmit buffer. */
#define BUFFER_SIZE     (100)
/** Reference voltage for ADC,in mv. */
#define VOLT_REF        (3300)
/** The maximal digital value */
#define MAX_DIGITAL     (4095)

void setup() {
Serial.begin(115200);
}

/** adc buffer */
static int16_t gs_s_adc_values[BUFFER_SIZE] = { 0 };
/** brief ADC interrupt that handles RXBUFF interrupt */
void ADC_Handler(void)
{
uint32_t ul_counter;
       int32_t l_vol;
       float f_temp;
       uint32_t ul_value = 0;
uint32_t ul_temp_value = 0;
if ((ADC->ADC_ISR & ADC_ISR_RXBUFF) == ADC_ISR_RXBUFF) {
       /* The destination buffer. */
       ADC->ADC_RPR = (uint32_t) gs_s_adc_values;
       /* The size of the buffer. */
       ADC->ADC_RCR = BUFFER_SIZE;
       /* enable transfer. */
ADC->ADC_PTCR = ADC_PTCR_RXTEN;
       /* Multisample. */
for (ul_counter = 0; ul_counter < BUFFER_SIZE; ul_counter++) {
         ul_value += gs_s_adc_values[ul_counter];
               }
               /* Averaging */
ul_temp_value = ul_value / 10;
ul_value = ul_value / 100;
ul_temp_value -= (ul_value * 10);
/* Round for last decimal */
if (ul_temp_value > 4) {
ul_value++;
}
l_vol = ul_value * VOLT_REF / MAX_DIGITAL;
               f_temp = (float)(l_vol - 800) * 0.37736 + 25.5;
               Serial.print("Temp: ");
               Serial.println(f_temp);
               }
}

void loop() {
       /* Enable ADC channel 15 and turn on temperature sensor */
       ADC->ADC_CHER = 1 << 15;
       ADC->ADC_ACR |= ADC_ACR_TSON;
       /* Enable ADC interrupt. */
       NVIC_EnableIRQ(ADC_IRQn);
       /* Start conversion. */
ADC->ADC_CR = ADC_CR_START;
       /* Enable PDC channel interrupt. */
       ADC->ADC_IER = ADC_ISR_RXBUFF;
while (1) {}
}


Regards!

jtw11

Good work, i'll have a play with this later!

garygid

I run this as is with 1.5.3 and it only prints one value.
Any suggestions, please?
Cheers, Gary
Due for controlling Electric car charging.
Nissan LEAF - Mini Quick Charge (mQC)

Palliser

#3
Sep 01, 2013, 05:16 am Last Edit: Sep 01, 2013, 05:26 am by Palliser Reason: 1

I run this as is with 1.5.3 and it only prints one value.
Any suggestions, please?


Hello garygid,

In order to synchronize the analog-to-digital conversions, the code needs to be interrupted and the ADC restarted every time the conversion ends. To do that, I forgot to tell that it is required to add few lines of code (an 'if') into the SysTick_Handler function inside the cortex_handlers.c source file.

The cortex_handlers.c file is located in the following path:

Quote
...\arduino-1.5.3-windows\arduino-1.5.3\hardware\arduino\sam\cores\arduino\


Thus, please, proceed to add the following 'if' into the SysTick_Handler at the top of the function as follows:

Code: [Select]
void SysTick_Handler(void)
{
if ((adc_get_status(ADC) & ADC_ISR_EOC15) == ADC_ISR_EOC15) {
adc_start(ADC);
}
if (sysTickHook())
return;

tickReset();

//Increment tick count each ms
TimeTick_Increment();
}


Doing the above change, your Due should start to show the values from the temperature sensor. Sorry for overlook that. Please, let me know how it goes. Regards!

EDIT: Remember to re-upload the code after the change. -P

garygid

Yes, Thanks,

Adding the following line to
void SysTick_Handler(void)
in cortex_handlers.c
found in ...\arduino-1.5.3\hardware\arduino\sam\cores\arduino\
makes it work.

Code: [Select]

    if ((adc_get_status(ADC) & ADC_ISR_EOC15) == ADC_ISR_EOC15) adc_start(ADC);


------
But, is this a patch that might adversly affect other uses of SysTick_Handler?
Cheers, Gary
Due for controlling Electric car charging.
Nissan LEAF - Mini Quick Charge (mQC)

garygid

#5
Sep 01, 2013, 07:58 pm Last Edit: Sep 01, 2013, 08:28 pm by garygid Reason: 1
Strange, this morning it is printing only -276.39 degrees,
Like, getting all zero data?

I will try the original Sketch again, with no changes.

And, my modified Sketch (removed the print from the
interrupt routine, and printing each 3 seconds...)
also works. 

It now reports about 40 degrees.
Is that rather hot, or normal?

Not even noticably warm to the touch,
room temp less than 25 degrees C, I think.

Strange, I had to power the Due completely Off and On,
and now it is working.  Is there some startup timing issue?
Cheers, Gary
Due for controlling Electric car charging.
Nissan LEAF - Mini Quick Charge (mQC)

garygid

I just tried to blink the LED after printing the Temperature
(in the loop() routine), and the LED does not flash.

Blink works with v1.5.3, but this still prints the Temperature,
but does not flash the LED.  Is there some interaction between
this ADC process and the LED pin (13)?

Code: [Select]

  //int led = 13; // virtual pin number [before Setup()]

  //pinMode(led, OUTPUT);     //[both in Setup()]
  //delay(1);

  // [these in the loop(), after the Serial.print(...)]
    digitalWrite(led, HIGH);   // turn the LED on (HIGH is the voltage level)
    delay(1000); //one sec LED ON
    digitalWrite(led, LOW);   // turn the LED Off
           
    delay(2000);
Cheers, Gary
Due for controlling Electric car charging.
Nissan LEAF - Mini Quick Charge (mQC)

garygid

OK, how does the LED fail to work?

A fault in the ADC is apparently fed to the PWM fault input,
which apparently puts the PWMs into some sort of "safe mode".

Since the LED is on the PWM13 output (chip pin 68, B.27, or D13),
the "frozen" PWM system keeps the LED ON.

So, how can we avoid the ADC fault, or how to clear it, or
how to clear the PWM fault so that the LED can operate?
Cheers, Gary
Due for controlling Electric car charging.
Nissan LEAF - Mini Quick Charge (mQC)

Palliser


OK, how does the LED fail to work?

A fault in the ADC is apparently fed to the PWM fault input,
which apparently puts the PWMs into some sort of "safe mode".

Since the LED is on the PWM13 output (chip pin 68, B.27, or D13),
the "frozen" PWM system keeps the LED ON.

So, how can we avoid the ADC fault, or how to clear it, or
how to clear the PWM fault so that the LED can operate?


Hello Gary,

I need to analyze a little bit more what you just stated, but in my understanding, to keep good readings of the sensor temp (synchronized), if a different task is required (like toggling a led), we have to disable the temp channel 15 once a reading is done (conversion completed), then we run the led logic, and then, we enable again the channel 15. Although I am not comfortable with this mechanism an I also believe that there a sort of PWM registers conflict, it works.

Here the modified code example that does what you want.

Code: [Select]
/** Example - ADC temperature sensor for Arduino Due. */

/** Size of the receive buffer and transmit buffer. */
#define BUFFER_SIZE     (100)
/** Reference voltage for ADC,in mv. */
#define VOLT_REF        (3300)
/** The maximal digital value */
#define MAX_DIGITAL     (4095)

int led = 13;

void setup() {
Serial.begin(115200);
pinMode(led, OUTPUT);
}

/** adc buffer */
static int16_t gs_s_adc_values[BUFFER_SIZE] = { 0 };
/** brief ADC interrupt that handles RXBUFF interrupt */
void ADC_Handler(void)
{
uint32_t ul_counter;
        int32_t l_vol;
        float f_temp;
        uint32_t ul_value = 0;
uint32_t ul_temp_value = 0;
if ((ADC->ADC_ISR & ADC_ISR_RXBUFF) == ADC_ISR_RXBUFF) {
        /* The destination buffer. */
        ADC->ADC_RPR = (uint32_t) gs_s_adc_values;
        /* The size of the buffer. */
        ADC->ADC_RCR = BUFFER_SIZE;
        /* enable transfer. */
ADC->ADC_PTCR = ADC_PTCR_RXTEN;
        /* Multisample. */
for (ul_counter = 0; ul_counter < BUFFER_SIZE; ul_counter++) {
          ul_value += gs_s_adc_values[ul_counter];
                }
                /* Averaging */
ul_temp_value = ul_value / 10;
ul_value = ul_value / 100;
ul_temp_value -= (ul_value * 10);
/* Round for last decimal */
if (ul_temp_value > 4) {
ul_value++;
}
l_vol = ul_value * VOLT_REF / MAX_DIGITAL;
                f_temp = (float)(l_vol - 800) * 0.37736 + 25.5;
                Serial.print("Temp: ");
                Serial.println(f_temp);
                // Disable the corresponding channel
                ADC->ADC_CHDR = 1 << 15;
                //Led 13 blink logic
                digitalWrite(led, HIGH); // turn the LED on
                Delay(1000); //one sec LED ON
                digitalWrite(led, LOW); // turn the LED Off
                Delay(2000); //two secs LED OFF
                // Enable ADC channel 15
                ADC->ADC_CHER = 1 << 15;
                }
}

void loop() {
        /* Enable ADC channel 15 and turn on temperature sensor */
        ADC->ADC_CHER = 1 << 15;
        ADC->ADC_ACR |= ADC_ACR_TSON;
        /* Enable ADC interrupt. */
        NVIC_EnableIRQ(ADC_IRQn);
        /* Start conversion. */
ADC->ADC_CR = ADC_CR_START;
        /* Enable PDC channel interrupt. */
        ADC->ADC_IER = ADC_ISR_RXBUFF;
while (1) {}
}
// One second delay
void Delay(unsigned int delay) {
  while (delay--) for(int x=0; x<0x3FFF; x++) __asm__("nop\n\t");
}


Regards!
Palliser

P.S. Here another thing. I had to use a while delay because for reasons unknown yet to me, the function delay() appears to be also in conflict with the ADC process. -P

garygid

#9
Sep 02, 2013, 03:28 am Last Edit: Sep 02, 2013, 04:28 am by garygid Reason: 1
To use a print or a delay inside an interrupt handler is
usually unfriendly the any other process.

One should not have to turn the Analog Sampling, or PWM
off to flash an LED. 

Apparently the ADC is producing a fault, which is fed to the
PWM section, where it can be blocked.  However, not being blocked,
it apparently forces the PWM outputs to a "safe" state.

Since the LED output uses the PWM13 output pin, it is
apparently blocked at the fault-protected pin.

So, to use ADC (and making whatever Fault we are making)
one needs to block the fault-Input to the PWM section,
which can be done... according to the 1467 page Datasheet.
Cheers, Gary
Due for controlling Electric car charging.
Nissan LEAF - Mini Quick Charge (mQC)

Palliser

Avoiding having to face potential conflicts between ADC and PWM processes, here a better approach to read the temp sensor without having to use interruptions. I just created a simple function 'temp_mV' that performs the following tasks:
- Enable channel 15
- Turn on temp sensor
- Start conversion
- Wait for the end of conversion
- Read millivolts value
- Disable channel 15
- Return mV

It neither requires to modify the SysTick_Handler function in the cortex_handlers.c given that I converted the 'if' into a 'while' to wait for the end of the conversion. I also got rid of the secondary delay function. Now you can add prints and delays and other tasks in the main loop without problems (I think). Just remember to keep the small delay after the end of the conversion. Please, try it and let me know.

Here the new code:

Code: [Select]
/* Example 2- ADC temperature sensor for Arduino Due. */

/* Size of the receive buffer and transmit buffer. */
#define BUFFER_SIZE     (100)
/* Reference voltage for ADC,in mv. */
#define VOLT_REF        (3300)
/* The maximal digital value */
#define MAX_DIGITAL     (4095)

int led = 13;
int32_t l_vol;
float f_temp;
uint32_t mV = 0;
uint32_t ul_value = 0;

void setup() {
  Serial.begin(115200);
  pinMode(led, OUTPUT);
}

  /* ADC function that returns conversion in mV. */
  uint32_t temp_mV() {
  /* Enable ADC channel 15 and turn on temperature sensor */
  ADC->ADC_CHER = 1 << 15;
  ADC->ADC_ACR |= ADC_ACR_TSON;
  /* Start conversion. */
  ADC->ADC_CR = ADC_CR_START;
  /* Wait for end of the conversion. */
  while (ADC->ADC_ISR & ADC_ISR_EOC15 == ADC_ISR_EOC15);
  delay(100); // Keep this delay     
  /* Read the value. */
  mV = ADC->ADC_LCDR;
  /* Disable channel 15. */
  ADC->ADC_CHDR = 1 << 15;
  return mV;
}

void loop() {
  float ul_value = temp_mV();
  l_vol = ul_value * VOLT_REF / MAX_DIGITAL;
  f_temp = (float)(l_vol - 800) * 0.37736 + 25.5;
  Serial.print("Temp: ");
  Serial.println(f_temp);
  digitalWrite(led, HIGH);
  delay(1000);
  digitalWrite(led, LOW);
  delay(1000);
}


Regards!

garygid

Works reasonably well, except the value returned depends upon
the delay time used: 100, 10, 5, and 2 ms return different values.

Code: [Select]

/* Wait for end of the conversion. */
  while (ADC->ADC_ISR & ADC_ISR_EOC15 == ADC_ISR_EOC15);
  delay(100); // Keep this delay     


There must be a better way to get the value, since the conversion
itself should only take some micro-seconds.

If ADC_ISR_EOC15 signals the End of Conversion, why the delay at all?

Yes, I kow that no delay just returns zero, so this delay must be
the charge time for some capacitor, that is later A-to-D converted.

Thus, ADC_ISR_EOC15 must not be the END of Conversion?
Cheers, Gary
Due for controlling Electric car charging.
Nissan LEAF - Mini Quick Charge (mQC)

Go Up