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 <https://ww1.microchip.com/downloads/en/DeviceDoc/Atmel-7701_Automotive-Microcontrollers-ATtiny24-44-84_Datasheet.pdf>
#ifdef F_CPU
#undef F_CPU
#define F_CPU 8000000UL // Main clock is internal 8MHz internal oscillator
#endif
#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
// PCINT6/OC1A/SDA/MOSI/DI/ADC6 PA6 7| |8 PA5 ADC5/DO/MISO/OC1B/PCINT5
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 <https://europe1.discourse-cdn.com/arduino/original/4X/3/a/5/3a50e2c98f4f87265f182d2e8e1adf5fcaa2376d.jpeg>
// 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:
Serial.begin(BAUD);
Prtln(F("\n\r\n\rStarting"));
}
void loop() {
// put your main code here, to run repeatedly:
static unsigned long last_time = millis();
if (millis() - last_time > 2000){
//Prt(F("Reading" ));
//readVoltage();
//Prtln(reading);
Prt(F("Low noise reading "));
readVoltageLowNoise();
Prtln(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
//set_sleep_mode(SLEEP_MODE_ADC);
//MCUCR &= ~((1<<SM1)|(1<<SM0));
//MCUCR |= (0<<SM1)|(1<<SM0);
sleep_enable();
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
goToSleep();
reading = (ADCL | (ADCH<<8)); // it's important to read ADCL before ADCH
ADCSRA &= ~(1<<ADEN); // disable ADC
}
// initialization functions here:
void sysInit(){
set_sleep_mode(SLEEP_MODE_PWR_DOWN);
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.ini
; 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
; https://docs.platformio.org/page/projectconf.html
[env:attiny84]
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 =
-C
; use "tool-avrdude-megaavr" for the atmelmegaavr platform
${platformio.packages_dir}/tool-avrdude/avrdude.conf
-p
$BOARD_MCU
-P
$UPLOAD_PORT
-c
usbasp
upload_command = avrdude $UPLOAD_FLAGS -U flash:w:$SOURCE:i