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).
/** 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) {}
}
garygid:
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:
Thus, please, proceed to add the following 'if' into the SysTick_Handler at the top of the function as follows:
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
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.
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?
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)?
//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);
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.
/** 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
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.
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:
/* 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);
}
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?
Wanted to post for posterity the underlying issue here which I had Atmel engineering research and confirm (Case 00042100).
First, this has nothing to do with PWM faults. Arduino Due Pin D13 is not on a SAM3X8E PWM pin. The Due uses the 144-pin SAM3X8E package and Due Pin D13 is on SAM3X8E Pin PB27 which is a TIO pin. (Yes, the Arduino Due libraries let you use Due D13 as a PWM output, but it's not using hardware PWM to do that; it's emulating hardware PWM. I've not looked to see how; it's not relevant. What is relevant is that SAM3X8E PWM faults are not and cannot be related to Due D13.)
What is happening here, and you can refer to Atmel Case #00042100 for confirmation, is that when the internal AD15 input is enabled on the SAM3X8E chip, the mcu's internal temperature sensor's analog output is internally routed to AD15. This is routing is done by usurping the internal bus line otherwise used to route digital output to SAM3X8E Pin PB27. This is presently an undocumented "feature" of the SAM3X8E: enable AD15, lose PB27's digital output. The actual Atmel case indicates that you lose all GPIO capability of PB27. However, I know of a line of production boards using the SAM3X8E where PB27 is used for digital input and enabling AD15 does not impact that function (e.g., the Duet 0.6 and 0.8.5 boards). YMMV; consider PB27 / D13 off limits if you need to use the internal mcu temperature sensor.