AnalogRead; Reference Voltage

The third problem is happening because we set the reference voltage for the ADC as Vcc/1.6. The problem is that the other options for this register are worse. I’m still trying to find a software solution for this issue, but I believe the only way to fix it is by hardware.

Useful information

Useful information In unsigned mode, the negative input is connected to half of the voltage reference (VREF) voltage minus a fixed offset. The nominal value for the offset is:

Since the ADC is differential, the input range is VREF to zero for the positive single-ended input. The offset enables the ADC to measure zero crossing in unsigned mode, and allows for calibration of any positive offset when the internal ground in the device is higher than the external ground.

This offset is not compensated for automatically, and the software needs to subtract the measured offset from the conversion results.

OFFSET: 10.3mV – measured using a voltage generator and a multimeter. This voltage makes the AnalogRead returns -4 when is grounded. It was measured after the modifications.

Vmax = 2.05V


Update Millis and Delay

Doing some test, it was discovered that the “millis()” function was wrong. It was returning a value in milliseconds of the running time but 33% faster. So if we ask, for example, for a delay of 1 second, what we were actually getting was 0.75 second.

Analyzing the LithneDuino files, it was discovered that the millis() function works in the following way:

ISR(RTC_OVF_vect)
{
rtc_millis = rtc_millis+4;
}
unsigned long millis()
{
unsigned long m;
uint8_t oldSREG = SREG;
// disable interrupts while we read rtc_millis or we might get an
// inconsistent value (e.g. in the middle of a write to rtc_millis)
cli();
m = rtc_millis;
SREG = oldSREG;
return m;
}

This code can be found in the file (line 60): “LithneDuino\hardware\xmegaduino\cores\xmega\wiring.c”

So, the millis function uses the Real-Time Counter to provide a interruption that will add 4 to rtc_millis. Initial value of rtc_millis is zero.

At the same file, it was find how the internal registers are preset. The following values for the registers are shown below:

OSC.CTRL.SCLKSEL = 0b(1xx) //enable the internal clock of 32.768kHz
CLK.RTCCTRL = 0b((010)1) //choose 1024Hz from the 32.768kHZ crystal oscillator
RTC.CTRL = 0b0001 //prescaler of 1
RTC.PER = 0;
RTC.CNT = 0;
RTC.COMP = 0;

The first 3 registers were explained in the commentary. The last 3 ones control the interruption frequency. RTC.CNT counts every clock pulse and every time it reaches RTC.PER or RTC.COMP, the register becomes zero again. If it reaches RTC.PER, the interruption RTC_OVF_vect is activated.      

But the problem is that PER and CNT are the same, so it is really hard to define what is the interruption frequency.

The first tool it will be used to solve this problem is change the RTC clock frequency. 1024Hz is too little, so I will choose to work with the full internal frequency of 32.768 kHz:

CLK.RTCCTRL = CLK_RTCSRC_RCOSC32_gc | CLK_RTCEN_bm;//32,768kHz
CLK_RTCSRC_RCOSC32_gc is defined as 0x06.

With this frequency, the value of PER is calculated using the formula:

How we want a time of 4ms, PER will be 129. We subtract 2 of PER because, from the datasheet, it took two clock pulses to update the flag value.

RTC.PER = 129;
RTC.CNT = 0;
RTC.COMP = 129;

Doing some more tests, it can be noticed that the delay functions and millis() are much more precise, but it is still not perfect. The precision was calculated as 99,95%.

Code parts coppied to help in the problem search solution!

 

volatile unsigned long millis_count = 0;
volatile unsigned long rtc_millis = 0;
unsigned long millis()
{
unsigned long m;
uint8_t oldSREG = SREG;
// disable interrupts while we read rtc_millis or we might get an
// inconsistent value (e.g. in the middle of a write to rtc_millis)
cli();
m = rtc_millis;
SREG = oldSREG;
return m;
}
ISR(RTC_OVF_vect)
{
rtc_millis = rtc_millis+4;
}
/* Turn on internal 32kHz. */
OSC.CTRL |= OSC_RC32KEN_bm;
do {
/* Wait for the 32kHz oscillator to stabilize. */
} while ( ( OSC.STATUS & OSC_RC32KRDY_bm ) == 0);
/* Set internal 32kHz oscillator as clock source for RTC. */
CLK.RTCCTRL = CLK_RTCSRC_RCOSC_gc | CLK_RTCEN_bm;//1kHz
#define OSC_PLLEN_bm 0x10 /* PLL Enable bit mask. */
#define OSC_PLLEN_bp 4 /* PLL Enable bit position. */
#define OSC_XOSCEN_bm 0x08 /* External Oscillator Enable bit mask. */
#define OSC_XOSCEN_bp 3 /* External Oscillator Enable bit position. */
#define OSC_RC32KEN_bm 0x04 /* Internal 32.768 kHz RC Oscillator Enable bit mask. */
#define OSC_RC32KEN_bp 2 /* Internal 32.768 kHz RC Oscillator Enable bit position. */
#define OSC_RC32MEN_bm 0x02 /* Internal 32 MHz RC Oscillator Enable bit mask. */
#define OSC_RC32MEN_bp 1 /* Internal 32 MHz RC Oscillator Enable bit position. */
#define OSC_RC2MEN_bm 0x01 /* Internal 2 MHz RC Oscillator Enable bit mask. */
#define OSC_RC2MEN_bp 0 /* Internal 2 MHz RC Oscillator Enable bit position. */
CLK_RTCSRC_RCOSC_gc = (0x02<<1), /* 1.024 kHz from internal 32.768 kHz RC oscillator */
#define CLK_RTCEN_bm 0x01 /* Clock Source Enable bit mask. */
#define RTC_PRESCALER_gm 0x07 (0b0111)
RTC_PRESCALER_DIV1_gc = (0x01<<0)
RTC.PER = 0;//1ms
RTC.CNT = 0;
RTC.COMP = 0;
RTC.CTRL = ( RTC.CTRL & ~RTC_PRESCALER_gm ) | RTC_PRESCALER_DIV1_gc;
/* Enable overflow interrupt. */
RTC.INTCTRL = ( RTC.INTCTRL & ~( RTC_COMPINTLVL_gm | RTC_OVFINTLVL_gm ) ) | RTC_OVFINTLVL_LO_gc | RTC_COMPINTLVL_OFF_gc;
OSC.CTRL.SCLKSEL = 0b(1xx) -> enable the internal clock of 32.768kHz
CLK.RTCCTRL = 0b((010)1) -> choose 1024Hz from the 32.768kHZ crystal oscillator
RTC.CTRL = 0b0001 -> prescaler of 1

 

AnalogRead; Zero Crossing

After a narrow reading of the code and datasheet, I discovered that the ADC is working in unsigned single-ended mode. In this mode, the ADC is measured between the input and a voltage value that is (Vref - Offset). Vref, in our case, is 0V and Offset was measured using a multimeter as 10.3mV.

To solve this problem, an unconventional method was used. I changed the operation mode to signed single-ended (that will show negative values also) and then I defined that, if the value is negative, the function returns 0. So, now analogueRead() is capable to sense voltage variations in a range between 0 and 2.05V. The changes were made in the file: LithneDuino\hardware\xmegaduino\cores\xmega\wiring_analogue.c

int analogRead12(uint8_t pin)
{
ADC_t* adc;
#if defined(ADCB) //Atxmega128a1 has 2 8 channel ADCs
if ( pin < 8 ) {
adc = &ADCA;
} else if ( pin < 16 ) {
adc = &ADCB;
pin -= 8;
#else //Atxmega32a4 has 1 12 channel ADC
if ( pin < 12 ) {
adc = &ADCA;
#endif
} else {
return -1;
}
pin = adcToChannel(pin);
adc->CTRLB |= 0b10000; //sets pin CONVMODE to enable signed mode
adc->REFCTRL = analog_reference << ADC_REFSEL_gp;
adc->CH0.MUXCTRL = pin << ADC_CH_MUXPOS_gp; // Select pin for positive input
adc->CH0.CTRL |= ADC_CH_START_bm; // Start conversion
while ( 0 == (adc->CH0.INTFLAGS & ADC_CH_CHIF_bm) ); // wait for adc to finish
adc->CH0.INTFLAGS = 1;
uint16_t result = adc->CH0RES;
if(result & 0x8000) return 0; //if result < 0
else return result;
}
int analogRead(uint8_t pin)
{
return analogRead12(pin) >> 1; // divide by 2 to return a value between 0 and 1023
}