Using ADC With Noise Reduction Sleep Mode Delays Timer 2 Overflow

Go To Last Post
15 posts / 0 new
Author
Message
#1
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Hello, I'm not sure what code to provide. The code to do all of the below is kind of large, and the problem as far as I can tell might involve all of the pieces.

 

My processor is an ATmega328PA. I have a loop which puts the processor to sleep and waits for an overflow of timer 2 (clocked from a 32.768kHz crystal), which happens once a second. The processor wakes, reads the ADC, transmits that value using the UART, and then goes back to sleep. If I start the ADC conversion by putting the processor into the noise reduction mode I do not receive ADC readings once a second, but closer to once every one and a half seconds.

 

The change in code is summarized here:

void
adc_convert(void)
{
        // Sends a value more slowly than once every second.
	/*SMCR = (0 << SM2) |
		   (0 << SM1) |
		   (1 << SM0) |
		   (1 << SE);
	sleep_cpu();
	SMCR = 0;*/

	// Sends a value once every second.
	ADCSRA |= (1 << ADSC);
	while(ADCSRA & (1 << ADSC));
}

 

Is there something I am missing from the datasheet? I am doing some things in less than the ideal way, such as utilizing the UART without interrupts and waiting synchronously to send the next character. However this and the ADC reading I would expect to take far less than a second.

 

Thanks in advance!

 

R0b0t1.

This topic has a solution.
Last Edited: Mon. Aug 7, 2017 - 09:06 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

R0b0t1 wrote:
The code to do all of the below is kind of large, and the problem as far as I can tell might involve all of the pieces.

Thus, our usual recommendation here is to create the smallest complete test program that demonstrates the symptoms.  [It isn't unusual for that exercise to uncover the source of the problem.]

 

But hey -- how can we give suggestions?  I don't see where the processor model is stated.  We don't know toolchain, version, or optimization level.  We don't know AVR clock speed.  We don't see timer init.

 

R0b0t1 wrote:
Is there something I am missing from the datasheet?

How can we tell when we don't know what datasheet to look at?  What are the allowed wakeup sources for your chosen sleep mode?

 

 

You can put lipstick on a pig, but it is still a pig.

I've never met a pig I didn't like, as long as you have some salt and pepper.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

theusch wrote:

R0b0t1 wrote:

The code to do all of the below is kind of large, and the problem as far as I can tell might involve all of the pieces.

Thus, our usual recommendation here is to create the smallest complete test program that demonstrates the symptoms.  [It isn't unusual for that exercise to uncover the source of the problem.]

The only problem I have with this suggestion is having done this plenty of times I tend to just make the same mistake twice. Also, a minimal testcase is nearly three hundred lines, mostly because I have to set everything up - I've had people act like I'm dumping my whole project on them and asking them to figure it out.

 

theusch wrote:

But hey -- how can we give suggestions?  I don't see where the processor model is stated.  We don't know toolchain, version, or optimization level.  We don't know AVR clock speed.  We don't see timer init.

 

 

R0b0t1 wrote:

Is there something I am missing from the datasheet?

How can we tell when we don't know what datasheet to look at?  What are the allowed wakeup sources for your chosen sleep mode?

My apologies, it's an ATmega328PA. I am using the latest WinAVR release. My fuse selection is E: 0xFF,  H: 0xD8, L: 0xC2. The rest should be covered by my Makefile and the project code (flattened into one code block as best as possible):

MCU=atmega328p
F_CPU=8000000
PORT=COM3
USE_2X=1
BAUD_TOL=3
BAUD=57600

CC=avr-gcc
CF=-mmcu=${MCU} -DF_CPU=${F_CPU} -DBAUD_TOL=${BAUD_TOL} -DUSE_2X=${USE_2X} -DBAUD=${BAUD} -Os -std=gnu99 -Wall -pedantic
LF=-mmcu=${MCU}
OC=avr-objcopy

SRC=$(wildcard *.c)
OBJ=${SRC:.c=.o}
BIN=lcbc

.phony: all clean

${BIN}.hex: ${BIN}.elf
	${OC} -O ihex $< $@

${BIN}.elf: ${OBJ}
	${CC} ${LF} -o $@ $^

%.o: %.c
	${CC} ${CF} -c -o $@ $<

program:
	avrdude -p m328p -P ${PORT} -b 57600 -c arduino -D -U flash:w:${BIN}.hex:i

clean:
	rm ${OBJ} ${BIN}.elf ${BIN}.hex
#include <avr/io.h>
#include <avr/power.h>
#include <avr/sleep.h>
#include <avr/interrupt.h>
#include <util/delay.h>
#include <util/setbaud.h>

#include <string.h>
#include <stdint.h>
#include <stdlib.h>

#include "io.h"
#include "adc.h"
#include "usart0.h"
#include "power.h"

void usart0_up(void);
void usart0_down(void);
void usart0_putc(int c);
void usart0_puts(int8_t *s, size_t n);

void adc_up(void);
void adc_convert(void);
uint16_t adc_getv(void);

void timer2_up(void);

volatile uint64_t rtc_time = 0;
void rtc_up(void);

void power_up(void);
void power_down(void);

int
main(void)
{
	// Initialize hardware.
	cli();

	// Turn off all peripherals.
	PRR = (1 << PRTIM2)	  |
		  (1 << PRTIM0)	  |
		  (1 << PRTIM1)	  |
		  (1 << PRUSART0) |
		  (1 << PRADC);

	DDRB  |=  (1 << PB5);
	PORTB &= ~(1 << PB5);

	adc_up();
	rtc_up();
	usart0_up();

	// Start running application logic.
	sei();

	// Wait for the oscillator to stabilize by ensuring it has counted one
	// second.
	while (!rtc_time);

	while (1) {
		uint16_t v = 0;
		char buffer[16];
		memset(buffer, 0, sizeof(buffer));

		adc_convert();
		v = adc_getv();
		itoa(v, buffer, 10);
		usart0_puts(buffer, 16);
		usart0_putc('\n');

		adc_down();
		usart0_down();
		power_down();
		// Execution stops and then resumes here.
		power_up();
		usart0_up();
		adc_up();
	}
}

void
usart0_putc(int c)
{
	while (!(UCSR0A & (1 << UDRE0)));
	UDR0 = c;

	while (!(UCSR0A & (1 << TXC0)));
	UCSR0A |= (1 << TXC0);
}

void
usart0_puts(int8_t *s, size_t n)
{
	for(n--; *s && n; s++, n--)
		usart0_putc(*s);
}

void
usart0_up(void)
{
	DDRD &= ~(1 << PD0);
	DDRD |=  (1 << PD1);

	// Enable usart 0.
	PRR &= ~(1 << PRUSART0);

	// Double speed, normal communication.
	UCSR0A = (1 << U2X0) |
			 (0 << MPCM0);

	// Disable usart 0 interrupts, enable transmitter and receiver.
	UCSR0B = (0 << RXCIE0) |
			 (0 << TXCIE0) |
			 (0 << UDRIE0) |
			 (1 << RXEN0)  |
			 (1 << TXEN0)  |
			 (0 << UCSZ02);

	// Enable asynchronous USART operation, no parity, one stop bit, 8 data
	// bits.
	UCSR0C = (0 << UMSEL01) |
			 (0 << UMSEL00) |
			 //(0 << UPMON01) |
			 //(0 << UPMON00) |
			 (0 << USBS0)	|
			 (1 << UCSZ01)	|
			 (1 << UCSZ00)	|
			 (0 << UCPOL0);

	// Set baudrate as calculated by setbaud.h.
	UBRR0 = UBRR_VALUE;
}

void
usart0_down(void)
{
	PRR |= (1 << PRUSART0);
}

void
adc_up(void)
{
	PRR &= ~(1 << PRADC);

	// Ref. voltage is AVcc with decoupling on Aref.
	// Multiplexer reading ADC0, or pin 23.
	ADMUX = (0 << REFS1) |
			(1 << REFS0) |
			(0 << ADLAR) |
			(0 << MUX3)	 |
			(0 << MUX2)	 |
			(0 << MUX1)  |
			(0 << MUX0);

	// Prescaler of 64 for 125kHz clock from 8MHz.
	ADCSRA = (1 << ADEN)  |
			 (0 << ADSC)  |
			 (0 << ADATE) |
			 (0 << ADIF)  |
			 (0 << ADIE)  |
			 (1 << ADPS2) |
			 (1 << ADPS1) |
			 (0 << ADPS0);

	// Comparator multiplexing off, freerunning conversion mode.
	ADCSRB = (0 << ACME)  |
			 (0 << ADTS2) |
			 (0 << ADTS1) |
			 (0 << ADTS0);

	// Disable digital IO logic for all ADC pins.
	DIDR0 = (1 << ADC5D) |
			(1 << ADC4D) |
			(1 << ADC3D) |
			(1 << ADC2D) |
			(1 << ADC1D) |
			(1 << ADC0D);
}

void
adc_down(void)
{
	// ADC must be disabled before setting PRR.
	ADCSRA &= ~(1 << ADEN);
	PRR    |=  (1 << PRADC);
}

void
adc_convert(void)
{
	/*SMCR = (0 << SM2) |
		   (0 << SM1) |
		   (1 << SM0) |
		   (1 << SE);
	sleep_cpu();
	SMCR = 0;*/

	ADCSRA |= (1 << ADSC);
	while(ADCSRA & (1 << ADSC));
}

uint16_t
adc_getv(void)
{
	uint16_t r = 0;
	r = ADC;

	ADCSRA |= (1 << ADIF);
	return r;
}

EMPTY_INTERRUPT(ADC_vect);

void
rtc_up(void)
{
	rtc_time = 0;
	timer2_up();
}

void
timer2_up(void)
{
	// Enable timer 2.
	PRR &= ~(1 << PRTIM2);

	// Use crystal as asynchronous clock source.
	ASSR = (0 << EXCLK) |
		   (1 << AS2);

	// Set start value.
	TCNT2 = 0;

	// No PWM generation.
	TCCR2A = (0 << COM2A1) |
			 (0 << COM2A0) |
			 (0 << COM2B1) |
			 (0 << COM2B0) |
			 (0 << WGM21)  |
			 (0 << WGM20);

	// Clock divided by 128, resulting in a 256Hz signal.
	TCCR2B = (0 << FOC2A) |
			 (0 << FOC2B) |
			 (0 << WGM22) |
			 (1 << CS22)  |
			 (0 << CS21)  |
			 (1 << CS20);

	// Wait for asynchronous hardware to update.
	while (ASSR & (1 << TCN2UB));
	while (ASSR & (1 << TCR2AUB));
	while (ASSR & (1 << TCR2BUB));

	// Clear the interrupt flags.
	TIFR2 = (1 << OCF2B) |
			(1 << OCF2A) |
			(1 << TOV2);

	// Enable overflow interrupt.
	TIMSK2 = (0 << OCIE2B) |
			 (0 << OCIE2A) |
			 (1 << TOIE2);
}

ISR(TIMER2_OVF_vect)
{
	rtc_time += 1;
}

void
power_up(void)
{
	SMCR &= ~(1 << SE);
}

void
power_down(void)
{
	// Select power-save mode. Timer 2 remains active.
	// Enable sleep; if SE is unset sleep_cpu does nothing.
	SMCR = (0 << SM2) |
		   (1 << SM1) |
		   (1 << SM0) |
		   (1 << SE);
	sleep_cpu();
}

 

Additionally if anyone has a comment on a good way to check for the stabilization of the oscillator driving timer 2 I would appreciate it. I am currently waiting for an overflow. Would a faster method be to use a compare match on timer 2 to wait for a single increment? Are there any other methods?

 

R0b0t1.

Last Edited: Mon. Aug 7, 2017 - 08:54 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

So, with the code as-posted, what happens?  What do you expect to happen?

 

 

You can put lipstick on a pig, but it is still a pig.

I've never met a pig I didn't like, as long as you have some salt and pepper.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

theusch wrote:

So, with the code as-posted, what happens?  What do you expect to happen?

 

R0b0t1 wrote:

I have a loop which puts the processor to sleep and waits for an overflow of timer 2 (clocked from a 32.768kHz crystal), which happens once a second. The processor wakes, reads the ADC, transmits that value using the UART, and then goes back to sleep. If I start the ADC conversion by putting the processor into the noise reduction mode I do not receive ADC readings once a second, but closer to once every one and a half seconds.

 

The posted code triggers a single ADC conversion by writing to ADCSRA. There is a block above it that triggers a conversion using the noise reduction sleep mode. The noise reduction sleep mode seems to add a lot of time to the ADC conversion. I haven't measured it exactly yet but it's a very noticeable amount of time, tending to be over half a second.

 

I'm aware that the first conversion the ADC performs takes more time, but that should still be so quick as to not be noticeable. Nothing stood out to me as the explanation for why this is happening when I read the datasheet.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

R0b0t1 wrote:
There is a block above it that triggers a conversion using the noise reduction sleep mode.

Isn't that code commented out?

 

So, the code as posted does the operation once per second.  Is that correct?  Is that what you want?

 

If you enable the noise reduction mode, then it starts a conversion and goes to sleep, right?  Then awakens when the conversion is complete, triggers your empty ISR, then you return to your routine and do another conversion.

 

 

You can put lipstick on a pig, but it is still a pig.

I've never met a pig I didn't like, as long as you have some salt and pepper.

This reply has been marked as the solution. 
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Note that to get anywhere decent results, you will need to do more than one conversion, as you are shutting down the ADC between runs.

 

How does the interrupt get handled if ADIE isn't set?  Maybe not needed.  Dunno.

You can put lipstick on a pig, but it is still a pig.

I've never met a pig I didn't like, as long as you have some salt and pepper.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I think you may have misunderstood noise reduction mode. The point is that sleeping the CPU actually triggers an ADC conversion. You should set ADIE and provide an ISR() which will be triggered when conversion complete (and wake CPU). 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

theusch wrote:

R0b0t1 wrote:

There is a block above it that triggers a conversion using the noise reduction sleep mode.

 

Isn't that code commented out?

 

So, the code as posted does the operation once per second.  Is that correct?  Is that what you want?

 

If you enable the noise reduction mode, then it starts a conversion and goes to sleep, right?  Then awakens when the conversion is complete, triggers your empty ISR, then you return to your routine and do another conversion.

No, it does it once every 1.5s or so if I use the sleep mode. There is some variability in the delay. The flow control of the program seems to be correct however, and as far as I know it repeatedly sleeps and wakes to read the ADC.

 

clawson wrote:

I think you may have misunderstood noise reduction mode. The point is that sleeping the CPU actually triggers an ADC conversion. You should set ADIE and provide an ISR() which will be triggered when conversion complete (and wake CPU). 

Sorry, I explained poorly in my post - the code that is not commented out does a single-shot ADC conversion by writing the ADC control register. There is also a commented out block of code that uses the sleep mode to trigger a conversion. If I uncomment that block and comment the single-shot convesion, the conversion takes about half a second longer than it should (as far as I can tell by reading the datasheet).

 

theusch wrote:

Note that to get anywhere decent results, you will need to do more than one conversion, as you are shutting down the ADC between runs.

 

How does the interrupt get handled if ADIE isn't set?  Maybe not needed.  Dunno.

Thank you, this was the insight I was looking for - I enabled ADIE and I receive events every second now. What is strange is that this misconfiguration didn't seem to be resetting the microcontroller. The bootloader took its time to initialize, and then the ADC readout would be given at a slightly longer interval than usual, but not as long as the initial start time.

 

Should I be doing multiple quick reads of the ADC, and then monitoring trends in that over the seconds as I intend? Or will monitoring the ADC value over many seconds be sufficient? I am monitoring a battery and it seems sufficient, what do you think about other signal types?

 

 

In any case, thank you all for helping me with my problem. As I tried to explain in one of my other threads I am not very smart.

 

R0b0t1.

Last Edited: Mon. Aug 7, 2017 - 09:16 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I am monitoring a battery

I think more info is needed to answer how frequently to measure the battery and/or other signals.

 

If it is your expectation that the battery is going to last several weeks, or several months, or perhaps even longer, then I'm not sure why you would measure it every one second.

Perhaps you measure and report the battery voltage once every 100 readings of the other, primary, signal being measured and reported.

Or perhaps once every 1000 times, etc.

 

The point is the battery voltage isn't expected to change suddenly, and depending upon the battery chemistry, and its discharge curve, perhaps you sample it at a slow rate for weeks (or months), and then increase the sample rate when you notice the voltage start to drop, (i.e. read it once every 1000 samples of the primary signal, then once every 100 samples when the voltage starts to drop, or once every 10 samples, etc.).

 

How many samples you take "per reading" is a slightly different question.

 

Again, a lot depends upon the input signal.

Taking multiple readings of a rapidly changing signal is going to give different results!

Taking four readings, back to back, on a battery's voltage, ought to give a very stable reading, (unless a RF transmitter kicks on/off, or an LED, etc.).

Often one would read the battery voltage through a resistor voltage divider, and when one puts a small cap from the resistor divider to ground one is essentially making a Low Pass Filter, (typically with a low frequency, long time constant), which further helps to smooth out small noise artifact in the signal. 

Battery voltage is the essence of a slowing changing signal, unlike stereo audio signal sampling, or serial data sampling and decoding , etc.

 

There are innumerable ways to set up one's sampling algorithm, based upon the signal source, sampling rate desired, and other factors.

For a battery you might simply read the ADC value 4 times, eliminate the high and the low reading, and average the remaining two samples, for example.

 

Then wait an hour, or 1000 samples, or whatever, and repeat the process.

 

JC

 

 

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Hello JC,

 

Thank you for the advice. Something I forgot about due to missing parts for my prototype was that I would be using a resistive divider and turning it on with a MOSFET. Due to its current consumption reading a value every second might be excessive, in addition to the fact that this is supposed to discharge over an hour or so.

 

I might read the battery every minute, or every five minutes, and average 4-10 readings in a burst. There should be plenty of decoupling but it might also be a wise idea to throw out any outlying samples I take.

 

Thank you for the help.

Last Edited: Mon. Aug 7, 2017 - 11:13 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

R0b0t1 wrote:
Sorry, I explained poorly in my post - the code that is not commented out does a single-shot ADC conversion by writing the ADC control register.
It was not your post as much as the thread title that confused me. You said "Using ADC With Noise Reduction Sleep Mode" but this is not about "noise reduction" mode at all it seems.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Well, the problem was happening when I was using the noise reduction mode. Failing to enable ADIE caused the chip to exhibit some undocumented behavior. My apologies, I wasn't sure what else to call it. Thank you for the help!

Last Edited: Tue. Aug 8, 2017 - 02:01 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

R0b0t1 wrote:
Failing to enable ADIE caused the chip to exhibit some undocumented behavior.
Why would you use noise reduction and NOT ADIE ?? The whole point is that you put the AVR to sleep for the 66us that it takes to make a conversion (nominal) at the end of which ADIF is set, it triggers ADC_vect and that's what wakes the CPU back out of noise reduction sleep mode - so it should only be asleep as long as the conversion takes so that all the noisy edge switching that usually goes on in the CPU stops happening so as to not mess up your ADC reading.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

The datasheet never mentioned that it might need to be enabled. Had the ADC failed to perform a conversion I would have probably been able to guess my mistake, but as it is, it seems that disabling the ADC conversion interrupt does not keep the ADC from performing a conversion and returning flow to your program after entering the noise reduction sleep mode. There is just something that takes more time than usual. I am actually quite interested in what that makes the processor do, but it is likely that only Atmel knows.

 

As I have been saying, sir, I am not very smart. I appreciate the help I've received in my threads here as it has likely saved me weeks.