2
\$\begingroup\$

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?

\$\endgroup\$
8
  • \$\begingroup\$ I'm not going to read all the datasheets and look over the code. I'm going to assume that you've done that. Besides, you were successful with the "mini." I'm sure you are careful and it's not such a jump from an ATmega to an ATtiny. I'm just going to trust you on that. This leaves clock rate vagaries that may interact with your prior logic to produce your results or, possibly, the fact that this unit is placed differently. Is it possible that the sun might directly strike the sensor once or twice a day? Or something similar to account for the new behavior? When do these occur?? \$\endgroup\$
    – jonk
    Oct 31 '20 at 19:17
  • 2
    \$\begingroup\$ aside from that, many open questions in this design. To place this castellated pad module on your board, you need to SMD-solder. But you use a large, expensive through-hole microcontroller. Odd! The same device, cheaper, is available in SMD packages. Also, your traces are so wide and have so much clearance that they don't fit between even 2.54 mm spaced pins... um. You need to fix that. Also, add a ground plane. This whole layout is an invitation to noise. Pretty safe to say that if your ground, and with that, your current return path, switches layers multiple times, you'll have a noisy day. \$\endgroup\$ Oct 31 '20 at 19:38
  • \$\begingroup\$ So just the temperature measurement "spikes" or there is also an issue with Vcc measurement? Consider adding a small capacitor to ground on the output of the LMT86 (100 to 1000pF, no more). RF rectification is a possibility. \$\endgroup\$ Nov 1 '20 at 6:17
  • \$\begingroup\$ @jonk I tried to place it on a different place, very close to the working one and out of direct sun light. It worked well from 8pm to 9am and then it started to malfunction again. \$\endgroup\$
    – stenio
    Nov 1 '20 at 15:51
  • 1
    \$\begingroup\$ You should make sure you're putting your own radio into a low power mode with nothing running on in between reports, for power consumption reasons as well as preventing any possibility of RFI to yourself, since you won't be examining the sensor output at any time when the radio is on. You should also make sure your packet has a meaningful checksum. Then, plot your data not in terms of temperature, but in terms of the actual binary value. Look particularly to see if that hard upper limit to the spikes corresponds to a power of two, as that may hint at a programming mistake. \$\endgroup\$ Nov 1 '20 at 15:57
3
\$\begingroup\$

I've found the problem: there is a glitch on the code that converts the floating point number representing the temperature. The leading zero is removed. For example the number xx.09 is erroneously converted to xx.9.

To solve the problem it is sufficient to change the sprintf call in this way:

sprintf(buffer, "%i.%02u %i %i\r", temp_, dec, vcc, msg_count++);

Anyway thank you for the comments, I've learned a lot.

Edit: it is also necessary to round the temp value in this way before the call to trunc:

temp *= 100;
temp = round(temp);
temp /= 100;
\$\endgroup\$
2
\$\begingroup\$

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.

\$\endgroup\$
2
  • \$\begingroup\$ Thank you for your answer. Do you think that joining the ground island with some jumper cables could be of some help? \$\endgroup\$
    – stenio
    Nov 1 '20 at 16:43
  • 1
    \$\begingroup\$ @stenio I'd also try soldering decoupling capacitors for all the various devices on the underside of the board, straight across the power pins of the respective device. As it is now, with your chopped up groundplane, most of the decoupling caps have little use, having a way too large loop surface. \$\endgroup\$ Nov 1 '20 at 16:58

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.