ATtiny84 Help with Low Noise ADC and Sleep Modes

I am trying to do a low noise ADC measurement by putting the unit into SLEEP_MODE_ADC sleep but I am struggling to understand what is going on. I am sure I got this flying before but my code must be flawed and I cannot see the error. I would appreciate it of someone could sense check it.

The code attached is a stripped down version to try just to keep the salient points / issues.

In the final code, I will be using SLEEP_MODE_PWR_DOWN to wait for a WDT to periodically wake up the unit to do the ADC measurement so I will need to switch between sleep modes.

In the code snippet, I stay fully powered up until I do the ADC measurement.

The readVoltage() function works as expected as a single shot ADC measurement but this is not using the low noise feature. I include this to prove my MUX ADC port selection etc is correct.

When I try to use the readVoltageLowNoise() function there are several things that I don’t understand.
The code does read a voltage and output this but the ISR (ADC_vect){} code never executes despite my setting bit ADIE in ADCSRC (in the sysInit() function). I tested this by trying to toggle the LED1.

It should not work at all as the selected sleep mode is SLEEP_MODE_PWR_DOWN (also set in the sysInit() function).

I do not understand what is waking the unit up after the ADC conversion if it isn’t the ADC interrupt. The plethora of Pin Change Interrupts is there for the final code where I want to wake up from POWER DOWN on some button presses and data arriving on several serial ports.

If I try to explicitly set the sleep mode to SLEEP_MODE_ADC, either just before or within the atomic code section in the goToSleep() function (I left the line commented out), the low noise ADC measurement starts but the code hangs - as it does not generate an interrupt ADC Conversion Complete Interrupt as described above. I also tried setting the bit values directly in MCUCR as in

  MCUCR &= ~((1<<SM1)|(1<<SM0));
  MCUCR |= (0<<SM1)|(1<<SM0);

but with the same result

I have hacked this code round and round and I can’t work out which way is up now.

What I would like to achieve is the ADC Conversion Complete Interrupt happening so I can capture the reading (currently this is commented out in ISR routine) and not have the code hang when I try to specify the sleep mode just before the ADC measurement.

I am missing something but I can’t see what.

Please can you help?

My source code snippet and my PlatformIO.ini for completeness

// cSpell:includeRegExp CStyleComment
// designed for ATtiny84
// data sheet <>

#ifdef F_CPU
#undef F_CPU
#define F_CPU 8000000UL    	// Main clock is internal 8MHz internal oscillator 

#include <Arduino.h>
#include <avr/sleep.h>				// useful macros for putting the unit into sleep mode

#define SW1_bp PA5
#define SW2_bp PA6
#define LED1_bp PB1
#define LED2_bp PB0
#define LED3_bp PA7
#define ADC_bp PA0
// PA1 Serial TXD
// PA2 Serial RXD
// PA3 Software Serial TXD - Tx
// PB2 Software Serial RXD - Rx

/*  Pin definitions and use
//                  			          ATtiny84
//                              VCC 1| u |14 GND
//            PCINT8/XTAL1/CLKI PB0 2|   |13 PA0 ADC0/AREF/PCINT0
//                 PCINT9/XTAL2 PB1 3|   |12 PA1 ADC1/AIN0/PCINT1
//           PCINT11/~RESET~/dW PB3 4|   |11 PA2 ADC2/AIN1/PCINT2
//      PCINT10/INT0/OC0A/CKOUT PB2 5|   |10 PA3 ADC3/T0/PCINT3
//         PCINT7/ICP/OC0B/ADC7 PA7 6|   |9  PA4 ADC4/USCK/SCL/T1/PCINT4

This core uses a simple scheme for assigning the Arduino pin numbers: 
Pins are numbered starting from the the I/O pin closest to Vcc as pin 0 
and proceeding counterclockwise, skipping the (mostly) non-usable RESET pin. `
See Dr Azzy link <>

//  ARDUINO digital pin numbers

//                     VCC  1| u |14  GND
//             (D  0)  PB0  2|   |13  AREF (D 10)
//             (D  1)  PB1  3|   |12  PA1  (D  9) 
//                     PB3  4|   |11  PA2  (D  8) 
//  PWM  INT0  (D  2)  PB2  5|   |10  PA3  (D  7) 
//  PWM        (D  3)  PA7  6|   |9   PA4  (D  6) 
//  PWM        (D  4)  PA6  7|   |8   PA5  (D  5)        PWM
//  PA1 TXD Arduino pin 9
//  PA2 RXD Arduino pin 8

//  ISP header

//            MISO  1 2 VCC
//              SCK 3 4 MOSI
//             ~RST 5 6 GND   

#define BAUD 9600           // define the serial baud rate

#define Prtln(x) Serial.println(x)  // these just left to save typing
#define Prt(x) Serial.print(x)

#define settleTime 1  					// time to allow ADC to stabilize

volatile uint8_t INT_FLAG;      // ISR flags
#define ADC_INT_bp 1	

volatile uint16_t reading = 0;					// the ADC reading

// put function declarations here:
void goToSleep();
void sysInit();
void readVoltage();
void readVoltageLowNoise();

void setup() {
  // put your setup code here, to run once:

void loop() {
  // put your main code here, to run repeatedly:
  static unsigned long last_time = millis();

  if (millis() - last_time > 2000){       
    //Prt(F("Reading" ));
    Prt(F("Low noise reading "));

    last_time = millis();
    //PORTB ^= (1<<LED1_bp);

// interrupt service routines

ISR (ADC_vect){										          // ADC conversion complete
  PORTB ^= (1<<LED1_bp);                    // THIS NEVER HAPPENS!!!
	//reading = (ADCL | (ADCH<<8));						// it's important to read ADCL before ADCH

// put function definitions here:
void goToSleep(void){
  //PORTB ^= (1<<LED1_bp);
	char oldSREG = SREG;												// grab a copy of the current value of the system register
	cli();																			// turn off interrupts
  //MCUCR &= ~((1<<SM1)|(1<<SM0));
  //MCUCR |= (0<<SM1)|(1<<SM0);
	SREG = oldSREG;															// enable interrupts if they were enabled before
	sleep_cpu();																// this is the point where the MCU shuts down
	// zzZZzz
	sleep_disable();														// code restarts here on wake up
  //PORTB ^= (1<<LED1_bp);

void readVoltage(){
  // select the ADC channel for required pin
  ADCSRA |= (1<<ADEN);									// enable ADC
	delay(settleTime);									  // wait for power to settle down
  ADCSRA |= (1<<ADSC);                  // start a conversion
  while (ADCSRA & (1<<ADSC)){}
  reading = (ADCL | (ADCH<<8));					// it's important to read ADCL before ADCH
	ADCSRA &= ~(1<<ADEN);									// disable ADC	

void readVoltageLowNoise(){
  ADCSRA |= (1<<ADEN);									// enable ADC
	delay(settleTime);									  // wait for power to settle down
  reading = (ADCL | (ADCH<<8));					// it's important to read ADCL before ADCH
  ADCSRA &= ~(1<<ADEN);									// disable ADC	

// initialization functions here:

void sysInit(){

  PCMSK0 |= (1<<SW1_bp)|(1<<SW2_bp)|(1<<PA2); // set PCI on SW1, SW2 and Serial RXD
  PCMSK1 |= (1<<PB2);                         // set PCI for Software Serial RXD
	GIMSK |= (1<<PCIF1)|(1<<PCIF0);             // enable PCI
	DDRB |= (1<<LED1_bp)|(1<<LED2_bp);          // define output ports
	PORTB &= ~((1<<LED1_bp)|(1<<LED2_bp));      // set outputs LOW
  DDRA |= (1<<LED3_bp);                       // define output ports
  PORTA &= ~((1<<LED3_bp));                   // set outputs LOW
	PORTA |= (1<<SW1_bp)|(1<<SW2_bp);           // define internal pull up on digital inputs 

  PRR &= ~(1<<PRADC);                         // make sure PRADC is clear
  // Disable Comparator
	ACSR &= ~(1<<ACIE);                         // disable Analog Comparator interrupt
  ACSR |= (1<<ACD);                           // disable Comparator
	/*  ADCSRA Scaler
	//	ADPS2	ADPS1	ADPS0	Division Factor
	//	0		0		0		2
	//	0		0		1		2   (!)
	//	0		1		0		4
	//	0		1		1		8
	//	1		0		0		16
	//	1		0		1		32
	//	1		1		0		64	<--- needs to end up between 50kHz and 200kHz so 125kHz is OK
	//	1		1		1		128 <--- 65.5kHz also OK

  ADCSRA &= ~((1<<ADEN)|(1<<ADATE)|(1<<ADIE)|(1<<ADPS2)|(1<<ADPS1)|(1<<ADPS0)); // mask out bits
  ADCSRA |=   (0<<ADEN)|(0<<ADATE)|(1<<ADIE)|(1<<ADPS2)|(1<<ADPS1)|(0<<ADPS0);   // set no auto trigger, interrupts enabled, ADC prescaler of 64
  // ADMUX selection - make sure ADEN is clear before changing MUX[5:0]
	ADMUX |= (0<<REFS1)|(0<<REFS0)|(0<<MUX5)|(0<<MUX4)|(0<<MUX3)|(0<<MUX2)|(0<<MUX1)|(0<<MUX0);	// use VCC as ref, set ADC0 (PA0)

	ADCSRB &= ~((1<<BIN)|(1<<ADLAR));   // right justified  - 10 bit
  DIDR0 |= (1<<ADC0D);                // disable digital input on ADC0
	sei ();	


; PlatformIO Project Configuration File
;   Build options: build flags, source filter
;   Upload options: custom upload port, speed and extra flags
;   Library options: dependencies, extra library storages
;   Advanced options: extra scripting
; Please visit documentation for the other options and examples

platform = atmelavr
board = attiny84
framework = arduino

monitor_port = COM16

; device is USBASP V2.0 with updated firmware
upload_protocol = custom
upload_port = usb
upload_flags =
    ; use "tool-avrdude-megaavr" for the atmelmegaavr platform
upload_command = avrdude $UPLOAD_FLAGS -U flash:w:$SOURCE:i

Doh! hang on - I see that I am not calling sysInit() in setup()

Solved - I feel so stupid. I’ve spent so much time looking at this.

The original code must only have sort of worked as by happenstance, I’d used default ADC channel, voltage reference etc and the Arduino wrapper must have filled in some of the other gaps.

But now my ADC Conversion Complete Interrupt is ticking away just fine and I may modify the selected sleep mode outside the atomic code section.

Happy days!