Electronic – Accuracy problem with simple thermometer project

attinyavremi-filtering

I have built a simple circuit that should send temperature mesurements using a radio module.
The main component are:

  • Attiny84a microcontroller
  • RFM69HW transceiver
  • LMT86 sensor

The project is powered by two 1.5V AA batteries. Here is the schematic:
Schema

and the board layout

board

The themperature is sampled and transmitted every 56s (first sample discarded, five averaged). In the mean time the device is put to sleep waiting for an interrupt from the WDT.

The reading is unstable at least once per day. It has spikes between 0.4C and 0.7C. In the two image below you can see how the green thermometer follows the blue one and then suddenly it becomes unstable (but somehow bounded above).

good

no good
I have built a similar project (same sensor, RFM69W transceiver) using an Arduino mini (voltage regulator removed) and I have no problem at all.

I first suspected that the problem were EMI. So I added the caps C4, C6, C7 and C8. I am currently trying a 10uF instead of the 33nF C4 cap, but the problem remains.

I tried to see if there were spikes across pins 1 and 2 with my scope using infinite persistance but it seems OK to me (by the way I am a self-taught hobbyst). By the way it is difficult for me to spot a difference of a few mV with my scope.

I thought that it could have been a problem with my code. This is my last code (sorry comments removed because in Italian):

#include <avr/io.h>
#include <avr/interrupt.h>
#include <util/delay.h>
#include <stdio.h>
#include <string.h>

#include "RFM69.h"

#define VREF 1081.8
#define VDR 0.49927290
#define N_MESUREMENTS 6
#define CYCLES_BEFORE_VCC 1350
#define N_AWAKES 7
#define MESUREMENT_DELAY 2

uint16_t iteration = 0;
uint16_t msg_count = 1;
volatile uint16_t vcc;
volatile uint8_t awakes = 1;
volatile uint8_t have_to_sleep = 1;

void init_ADC() {
    ADCSRA |= (1 << ADEN);
    ADMUX |= (1 << REFS1);
    ADMUX |= (1 << MUX0);
    // prescaler 16 (62.5kHz)
    ADCSRA |= (1 << ADPS2);
}

float voltage_to_temp(float voltage){
    // r2: 9.97 kOhm, r1: 9.99kOhm r2/(r1+r2) = 0.49949899
    float voltageUnscaled = voltage / VDR;
    return (10.888 - sqrt(10.888*10.888+4.0*0.00347*(1777.3-voltageUnscaled)))/(2.0*-0.00347)+30.0;
}

float do_mesurement() {
    ADCSRA |= 1 << ADSC;

    while(ADCSRA & (1 << ADSC));
        
    uint16_t result = ADCL;
    result |= (ADCH << 8);
    
    return result;
}

float do_avg_mesurement(){
    uint8_t porta = PORTA;
    uint8_t ddra = DDRA;
    uint8_t portb = PORTB;
    uint8_t ddrb = DDRB;
    uint8_t didr0 = DIDR0;
    
    DDRB = 0;
    PORTB = 0xFF;
    DDRA = (1 << DDA7);
    PORTA = ~(1 << PORTA1);
    DIDR0 = 0xFF;
    
    _delay_ms(200); // Big cap...
    do_mesurement();
        
    float sum = 0;
    for (int i=0; i<N_MESUREMENTS; i++){
        _delay_ms(MESUREMENT_DELAY);
        sum += do_mesurement();
    }
    
    PORTA = porta;
    DDRA = ddra;
    PORTB = portb;
    DDRB = ddrb;
    DIDR0 = didr0;
    
    float avg = sum/N_MESUREMENTS;
            
    float v_sensor = (avg*VREF)/1024.0; // 10 bit
    return voltage_to_temp(v_sensor);
}

uint16_t measure_vcc(){
    uint8_t admux = ADMUX;
    
    ADMUX = 0b00100001;

    ADCSRA |= 1 << ADSC;
        
    while(ADCSRA & (1 << ADSC));
    
    ADCSRA |= 1 << ADSC;
        
    while(ADCSRA & (1 << ADSC));
        
    uint8_t low = ADCL;
    uint16_t v_ref_on_vcc = (ADCH << 8) | low; // (v1.1/vcc)*1024 
    
    ADMUX = admux;
        
    return (VREF*1024.0)/v_ref_on_vcc;
}

void update_vcc(){
    if(iteration == 0){
        vcc = measure_vcc();
    }
    iteration = (iteration + 1) % CYCLES_BEFORE_VCC;
}

void send_mesurement(float temp, uint16_t vcc) {
    receiveDone();
    
    float t_int = trunc(temp);
    float t_dec = temp-t_int;
    // 2 cifre decimali
    uint8_t dec = (uint8_t)round(t_dec*100);
    int8_t temp_ = (int8_t)t_int;
    char buffer[19]; 
    sprintf(buffer, "%i.%i %i %i\r", temp_, dec, vcc, msg_count++); 
    send(1, buffer, strlen(buffer), 0);
    
    sleep();
}

void WDT_off(void){
    asm("WDR");
    /* Clear WDRF in MCUSR */
    MCUSR = 0x00;
    /* Write logical one to WDCE and WDE */
    WDTCSR |= (1<<WDCE) | (1<<WDE);
    /* Turn off WDT */
    WDTCSR = 0x00;
}

void goto_sleep() {
    ADCSRA &= ~(1 << ADEN);
    PORTA &= ~(1 << PORTA7);
    
    WDTCSR |= (1 << WDIE) |
        (1 << WDP3) | (1 << WDP0); // 1024000 cicli a 128kHz = 7.812us a ciclo quindi 8000000us = 8s
        
    MCUCR = (1 << SE) | (1 << SM1);
    
    do  {
        asm("sleep");
    } while (have_to_sleep);
    have_to_sleep = 1;
    
    ADCSRA |= (1 << ADEN);
}

ISR(WATCHDOG_vect){
    cli();
    if(awakes == N_AWAKES){
        awakes = 1;
        WDT_off();
        have_to_sleep = 0;
    }else{
        awakes++;
    }
    sei();
}

int main(void) {
    PORTA |= (1 << PORTA0) | (1 << PORTA2);
    PORTB |= (1 << PORTB1);
    DDRA |= (1 << DDA7);

    init_ADC();

    rfm69_init(433, 4, 73);
    setHighPower(1);
    setPowerLevel(31);
    encrypt(NULL);
    
    while (1) {
        float avg_temp = do_avg_mesurement();
        update_vcc();
        send_mesurement(avg_temp, vcc);
        goto_sleep();
    }
}

Don't know what else to try 🙁

Edit1: added images that show the problem.

Edit2: following Chris' suggestion, I've added an image that shows the min, max and average of each gruop of six sample. It seems odd to see always a difference of at least tree units between max and min. I'll try to investigate with the scope if it is caused by a decay or by a constant noise.

raw data

Edit3: I think I discovered something with the scope: it seems to me that, as supposed, there is a lot of noise, see the image below:

enter image description here

At this point I think that, a part from redesigning the board taking into account a good ground plane, the only solution should be doing some oversampling. What do you think?

Best Answer

Get a piece of copper foil, or single_layer PCB material, and use those as GROUND PLANE.

Drill holes in the foil, or the copper_clad single_Layer, and solder all the GROUND points to this new Ground Plane, with 1/8" or shorter wires.

Related Topic