ATtiny85 eeprom gets altered and possibly program

Hi, I’m having a problem with an ATtiny85 but unable to pin point the problem.

The ATtiny85 is running an open source pH circuit I made, it calculates the pH value and sends the data via I2C bus when requested. The I2C address and some other settings are saved to the ATtiny85 eeprom.

The first problem is at any given time when repowering the circuit it will stop responding to I2C commands, when you run an I2C address scanner the address is gone so obviously it’s not going to respond. It appears it’s getting erased from eeprom because I’ve put a “reset” jumper on the board that can be shorted out setting a pin HIGH, at that point the ATtiny85 program will run the “defaults()” function and write the I2C address back to eeprom. After that the scanner will see the address 99 and the circuit will work until this may happen again. It also appears once this happens it’s more likely to happen again, some it never happens.

Now with that said if the above happens it’s also possible once defaults() are restored the circuit will work however it outputs wrong values or complete jibberish, it’s almost like the actual program code was altered. I haven’t been able to get any back to reprogram so I can’t verify.

The first sketch below is complete, it uses 99% of program storage space and 40% of dynamic memory.

My one question is, because storage is filled right up is it possible the program somehow gets altered when the program crashes or whatever happens happens?

I’ve dropped features in code that aren’t needed so now the program is 65% with 32% memory, that’s the 2nd sketch below, but I’m not so confident this will fix the problem or why this is even happening. 100% the eeprom gets erased as running defaults() sorts out the address issue.

#include <Wire.h>
#include <EEPROM.h>
#include "EEPROMAnything.h"

#define PROBE_IN A3 //57   // anlaog a3  - green wire
#define PROBE_REF A2 //56   // anlaog a2  - white wire

byte version = 1;  // eeprom 0  - version
byte i2cAdd = 99;  // eeprom 1
float cal_mV1 = -0;       // point 1 pH reading during calibration - eeprom 2
float cal_mV2 = -177.48;  // point 2 pH reading during calibration - eeprom 6
float cal_mV3 = 177.48;  // point 3 pH reading during calibration - eeprom 10

float buffer1; // buffer 1 solution - eeprom 14
float buffer2; // buffer 2 solution - eeprom 18
float buffer3; // buffer 3 solution - eeprom 22

float slopeLow;  // low end calibration slope
float slopeHigh;  // high end calibration slope1
float offsetPoint1;  // offset percentage from point 1

byte calType;  // type of calibration being used, 1point, 2point or 3point - eeprom 26

byte calibrating = 0;  // disables calibration while calibrating
byte calibrationEnabled;  // tracks if calibration is enabled disabled
double pH;
float slope = 0;
float referenceRange = 0;
float readingRange = 0;

// variables below are calculated in sketch
double mVc;  // mV calibrated
float pHc;  // pH calibrated
float bc1; //offset correction 4-7
float bc2; //offset correction 7-10
float mc1; //slope correction 4-7
float mc2; //slope correction 7-10

float tempCalibrated;  // temperature of calibration solution during calibration
float tempC = 25.0;           // temperature of sample being monitored in Celsius

byte in_char = 0;
char inData[15];
byte w = 0;                      //counter used for ph_data array.
byte q = 0;                      //counter used for ph_data array.
byte k = 0;                      //counter used for ph_data array.

char computerdata[31];           //we make a 20 byte character array to hold incoming data from a pc/mac/other.
char phData[31];           //we make a 20 byte character array to hold incoming data from a pc/mac/other.

byte flashLED = 0;
unsigned long previousMillis = 0;
void setup()
{
//  Serial.begin(115200);
//  Serial.println("startup");
  byte z = 255;
  z = EEPROM.read(50);
  if (z != 1) 
  {
  //  Defaults  
    defaults();
  }
  pinMode(1, OUTPUT);
  flashLED = 1;
  i2cAdd = EEPROM.read(1);
  readDefaults();
  calculateSlopes();
  measuremV();
  Wire.begin(i2cAdd);
  Wire.onReceive(receiveEvent); // register event
  Wire.onRequest(requestEvent); // register event
}
void loop()
{
  if (flashLED == 1)
  {
    for(byte i=0; i < 4; i++)
    {
//      digitalWrite(13, LOW);  // LED on circuit board
      digitalWrite(1, LOW);  // LED on circuit board
      delay(200);
      digitalWrite(1, HIGH);  // LED on circuit board
//      digitalWrite(13, HIGH);  // LED on circuit board
      delay(200);
    }
    flashLED = 0;
  }
  if ((millis() - previousMillis) >= 3000) 
  {
    measuremV();
    previousMillis = millis();
  }
}
byte buttonPressCount = 0;
void measuremV() 
{
  double differentialmV;
  float  probeIn, probeRef, inputV;
  analogReference(DEFAULT);

  // read input voltage START   //////////////////////////////////
  ADMUX = _BV(MUX3) | _BV(MUX2);
  delay(2);
  ADCSRA |= _BV(ADSC);
  while (bit_is_set(ADCSRA, ADSC));

  uint8_t low  = ADCL;
  uint8_t high = ADCH;
  long result = (high << 8) | low;

  result = 1125300L / result;
  inputV = (double(result) - 0) * (6 - 0) / (6000 - 0) + 0;
  // read input voltage END     //////////////////////////////////

  int buttonReset = readADC(PROBE_REF);
  if (buttonReset > 1000) 
  {
    buttonPressCount++;
    if (buttonPressCount == 2) {defaults();}
  }
  else {buttonPressCount = 0;}
  
  probeIn  = ((readADC(PROBE_IN) * inputV) / 1024.0);
  probeRef = ((readADC(PROBE_REF) * inputV) / 1024.0);
  differentialmV = (probeIn - probeRef);

  analogReference(INTERNAL);  // set to INTERNAL for ATtiny

  // take a reading
  probeIn  = ((readADC(PROBE_IN) * 1.1) / 1024.0);
  probeRef = ((readADC(PROBE_REF) * 1.1) / 1024.0);

  // turn V into mV  
  differentialmV = (probeIn - probeRef) * 1000;
  float temp_mvPerPH = 59.16;  // calibrated mV per pH with temperature compensation
  float mVPerPH = 59.16;;  // calibrated mV per pH without temperature compensation
  if ((calibrating == 0) && (calibrationEnabled == 1))
  {
    switch (calType)
    {
      case 1:  // 1 point calibration being used
        pHc = mc1*differentialmV + bc1;
        differentialmV = (7.0-pHc)*mVPerPH;
      break;

      case 2:  // 2 point calibration being used
        pHc = mc1*differentialmV + bc1;
        mVPerPH = (cal_mV2 - (cal_mV1)) / 3;
        differentialmV = (7.0-pHc)*mVPerPH;
      break;
    }        
  }

  temp_mvPerPH = mVPerPH * (tempC+273.15) / ((tempCalibrated)+273.15);
  // convert to pH
  pH = fabs(7.0 - (differentialmV / temp_mvPerPH));
  
  dtostrf(pH, 0, 3, phData);  // convert float to char array so it can be sent over I2C
}
byte calReading = 0;  // used so we know where stored reading from calibration is in computerdata array
void receiveEvent()
{
  while (Wire.available()) {    // are there bytes to receive.
    in_char = Wire.read();   // receive a byte.
    inData[w] = in_char;            // load this byte into our array.
    if (q > 0)
    {
      computerdata[q-1] = inData[w];
      q += 1;
    }
    if (inData[w] == ',') {q = 1;}
    if (inData[w] == '-') {k = w + 1;}
    w += 1;                         // incur the counter for the array element.
    if (in_char == 0) {             // if we see that we have been sent a null command.
      w = 0;                        // reset the counter w to 0.
      q = 0;                        // reset the counter q to 0.
      digitalWrite(1, LOW);  // LED on circuit board
      break;                        // exit the while loop.
    }
  }
}
void requestEvent()
{
  if ((inData[0] == 'R'))           // send pH
  {
    memset(inData,0,sizeof(inData));
    Wire.write(1);               // send byte 1
    Wire.write(phData,30);
    digitalWrite(1, HIGH);  
  }
  else if ((inData[0] == 'A'))           // change I2C address
  {
    byte a = 0;
    a = atoi(computerdata);
    
    if ((a > 0) && (a < 128))  // verify address is valid
    {
      flashLED = 1;
      i2cAdd = a;  // change address
      EEPROM_writeAnything(1, i2cAdd);  // I2C address - 40, 41, 42, 43, 44, 45, 46, 47
    }
    memset(inData,0,sizeof(inData));  
    digitalWrite(1, HIGH);  // LED on circuit board
  }
  
  else if ((inData[0] == 'V'))           // send version
  {
    dtostrf(version, 0, 3, computerdata);
    sendD();
  }
  else if (inData[0] == 'D')  // set defaults
  {
    defaults();
    memset(inData,0,sizeof(inData));
    Wire.write(1);
    digitalWrite(1, HIGH);
  }
  else if ((inData[0] == 'P') && (inData[1] == '1'))  // P1,7.0-8.5  =  P1,buffer-pH  Example
  {
    if (inData[2] == ',')  // check if we are calibrating
    {
      buffer1 = (7 - atof(computerdata)) * 59.16;  // convert to mV
      EEPROM_writeAnything(14, buffer1);  // buffer1

      memset(computerdata,0,sizeof(computerdata));  
      computerdata[0] = inData[k];  // 1
      computerdata[1] = inData[k+1];  // 0
      computerdata[2] = inData[k+2];  // .
      computerdata[3] = inData[k+3];  // 2
      computerdata[4] = inData[k+4];  // 7
      computerdata[5] = inData[k+5];  // 3
      computerdata[6] = '0';  // 3
      cal_mV1 = (7 - atof(computerdata)) * 59.16;  // convert to mV
      EEPROM_writeAnything(2, cal_mV1);  // cal_mV1
      calculateSlopes();
      k = 0;                        // reset the counter k to 0.
      calType = 1;
      EEPROM.write(26,calType);
    }
    
    dtostrf(buffer1, 0, 3, computerdata);
    sendD();
  }
  else if ((inData[0] == 'P') && (inData[1] == '2'))  // change buffer2
  {
    if (inData[2] == ',')  // if comma is seen change buffer2
    {
      buffer2 = (7 - atof(computerdata)) * 59.16;
      EEPROM_writeAnything(18, buffer2);  // buffer2

      memset(computerdata,0,sizeof(computerdata));  
      computerdata[0] = inData[k];  // 1
      computerdata[1] = inData[k+1];  // 0
      computerdata[2] = inData[k+2];  // .
      computerdata[3] = inData[k+3];  // 2
      computerdata[4] = inData[k+4];  // 7
      computerdata[5] = inData[k+5];  // 3
      computerdata[6] = '0';  // 3
      cal_mV2 = (7 - atof(computerdata)) * 59.16;  // convert to mV
      EEPROM_writeAnything(2, cal_mV2);  // cal_mV1
      calculateSlopes();
      k = 0;                        // reset the counter k to 0.
      calType = 2;
      EEPROM.write(26,calType);
    }
    
    dtostrf(buffer2, 0, 3, computerdata);
    sendD();
  }
  else if (inData[0] == 'T')  // send calType
  {
    if (inData[1] == ',') 
    {
      if(byte(inData[2]) == 49) calType = 1;
      else calType = 2;
      EEPROM.write(26,calType);
    }
    calculateSlopes();
    dtostrf(calType, 0, 3, computerdata);
    sendD();
  }
  else if (inData[0] == 'C')  // send calibrationEnabled
  {
    if (inData[1] == ',') 
    {
      if(byte(inData[2]) == 48) calibrationEnabled = 0;
      else calibrationEnabled = 1;
      EEPROM.write(27,calibrationEnabled);
    }
    dtostrf(calibrationEnabled, 0, 3, computerdata);
    sendD();
  }
  else if (inData[0] == 'S')  // set sample temperature
  {
    tempC = atof(computerdata);
    Wire.write(1);
  }
  else if (inData[0] == 'Z')  // set tempCalibrated
  {
    tempCalibrated = atof(computerdata);
    dtostrf(tempC, 0, 3, computerdata);
    sendD();
    EEPROM_writeAnything(28, tempCalibrated);  // tempCalibrated
  }
  else
  {
    memset(inData,0,sizeof(inData));    
    digitalWrite(1, HIGH);  // LED on circuit board
  }
}
void defaults()
{
    flashLED = 1;
  //  Defaults  
    EEPROM.write(1, 99);  // I2C address - 40, 41, 42, 43, 44, 45, 46, 47
    delay(200);
    EEPROM_writeAnything(2, 0.0);  // cal_mV1
    EEPROM_writeAnything(6, -177.48);  // cal_mV2
    EEPROM_writeAnything(14, 0.0);  // buffer1
    EEPROM_writeAnything(18, -177.48);  // buffer2
    EEPROM.write(26, 1);  // calibration type
    EEPROM.write(27, 0);  // calibration enabled/disabled
    EEPROM_writeAnything(28, 25.0);  // calibration temperature
    EEPROM.write(50, 1);  // version

    // read defaults
    readDefaults();
    calculateSlopes();
}
void readDefaults()
{
    // read defaults
    i2cAdd = EEPROM.read(1);
    EEPROM_readAnything(2, cal_mV1);
    EEPROM_readAnything(6, cal_mV2);
    EEPROM_readAnything(14, buffer1);
    EEPROM_readAnything(18, buffer2);
    calType = EEPROM.read(26);
    calibrationEnabled = EEPROM.read(27);
    EEPROM_readAnything(28, tempCalibrated);
}
void sendD()
{
  memset(inData,0,sizeof(inData));
  Wire.write(1);
  Wire.write(computerdata,30);
  digitalWrite(1, HIGH);
  flashLED = 1;
}
double readADC(int channel)
{
  uint32_t total = 0UL;
  uint16_t sampleCount = 4096;  // 2048
  for (uint16_t i = 0; i < sampleCount; i++) {total += analogRead(channel);}
  total = total >> 6;
  double proportional = (total * 1.0) / (0b00000001 << 6);
  return proportional;
}
void calculateSlopes()  //Slope intercept calculations  
{
  float m1; //slope for 4-7
  float b1; //offset for 4-7
  float m2; //slope for 7-10
  float b2; //offset for 7-10

  float bufferPh1 = 7.0 - (buffer1 / 59.16); // convert mV to pH
  float bufferPh2 = 7.0 - (buffer2 / 59.16); // convert mV to pH

  m1 = (bufferPh2 - bufferPh1) / (buffer2 - buffer1);
  b1 = bufferPh2 - buffer2*m1;
  
  mc1 = (bufferPh2 - bufferPh1) / (cal_mV2 - cal_mV1);
  bc1 = bufferPh2 - cal_mV2*mc1; 
}

Updated sketch

#include <Wire.h>
#include <EEPROM.h>

#define PROBE_IN A3 //57   // anlaog a3  - green wire
#define PROBE_REF A2 //56   // anlaog a2  - white wire

byte i2cAdd = 99;  // eeprom 1

double pH;

byte in_char = 0;
char inData[15];
byte w = 0;                      //counter used for ph_data array.
byte q = 0;                      //counter used for ph_data array.

char computerdata[31];           //we make a 20 byte character array to hold incoming data from a pc/mac/other.
char phData[31];           //we make a 20 byte character array to hold incoming data from a pc/mac/other.

byte flashLED = 0;
unsigned long previousMillis = 0;
void setup()
{
  byte z = 255;
  z = EEPROM.read(50);
  if (z != 1) 
  {
    defaults();  //  Defaults  
  }
//    defaults();
//    EEPROM.write(1, 99);  // I2C address - 40, 41, 42, 43, 44, 45, 46, 47
  pinMode(1, OUTPUT);
  flashLED = 1;
  i2cAdd = EEPROM.read(1);
  readDefaults();
  measuremV();
  Wire.begin(i2cAdd);
  Wire.onReceive(receiveEvent); // register event
  Wire.onRequest(requestEvent); // register event
}
void loop()
{
  if (flashLED == 1)
  {
    for(byte i=0; i < 4; i++)
    {
      digitalWrite(1, LOW);  // LED on circuit board
      delay(200);
      digitalWrite(1, HIGH);  // LED on circuit board
      delay(200);
    }
    flashLED = 0;
  }
  if ((millis() - previousMillis) >= 3000) 
  {
    measuremV();
    previousMillis = millis();
  }
}
byte buttonPressCount = 0;
void measuremV() 
{
  double differentialmV;
  float  probeIn, probeRef, inputV;
  analogReference(DEFAULT);

  // read input voltage START   //////////////////////////////////
  ADMUX = _BV(MUX3) | _BV(MUX2);
  delay(2);
  ADCSRA |= _BV(ADSC);
  while (bit_is_set(ADCSRA, ADSC));

  uint8_t low  = ADCL;
  uint8_t high = ADCH;
  long result = (high << 8) | low;

  result = 1125300L / result;
  inputV = (double(result) - 0) * (6 - 0) / (6000 - 0) + 0;
  // read input voltage END     //////////////////////////////////

  int buttonReset = readADC(PROBE_REF);
  if (buttonReset > 1000) 
  {
    buttonPressCount++;
    if (buttonPressCount == 1) {defaults();}
  }
  else {buttonPressCount = 0;}
    
  probeIn  = ((readADC(PROBE_IN) * inputV) / 1024.0);
  probeRef = ((readADC(PROBE_REF) * inputV) / 1024.0);
  differentialmV = (probeIn - probeRef);

  analogReference(INTERNAL);  // set to INTERNAL for ATtiny

  // take a reading
  probeIn  = ((readADC(PROBE_IN) * 1.1) / 1024.0);
  probeRef = ((readADC(PROBE_REF) * 1.1) / 1024.0);

  // calculate mV  
  differentialmV = (probeIn - probeRef) * 1000;
  float temp_mvPerPH = 59.16;  // calibrated mV per pH with temperature compensation
  float mVPerPH = 59.16;;  // calibrated mV per pH without temperature compensation

  // convert to pH
  pH = fabs(7.0 - (differentialmV / temp_mvPerPH));
  
  dtostrf(pH, 0, 3, phData);  // convert float to char array so it can be sent over I2C
}
void receiveEvent()
{
  while (Wire.available()) {    // are there bytes to receive.
    in_char = Wire.read();   // receive a byte.
    inData[w] = in_char;            // load this byte into our array.
    if (q > 0)
    {
      computerdata[q-1] = inData[w];
      q += 1;
    }
    if (inData[w] == ',') {q = 1;}
    w += 1;                         // incur the counter for the array element.
    if (in_char == 0) {             // if we see that we have been sent a null command.
      w = 0;                        // reset the counter w to 0.
      q = 0;                        // reset the counter q to 0.
      digitalWrite(1, LOW);  // LED on circuit board
      break;                        // exit the while loop.
    }
  }
}
void requestEvent()
{
  if ((inData[0] == 'R'))           // send pH
  {
    memset(inData,0,sizeof(inData));
    Wire.write(1);               // send byte 1
    Wire.write(phData,30);
    digitalWrite(1, HIGH);  
  }
  else if ((inData[0] == 'A'))           // change I2C address
  {
    byte a = 0;
    a = atoi(computerdata);
    
    if ((a > 0) && (a < 128))  // verify address is valid
    {
      flashLED = 1;
      i2cAdd = a;  // change address
      EEPROM.write(1, i2cAdd);  // I2C address - 40, 41, 42, 43, 44, 45, 46, 47
    }
    memset(inData,0,sizeof(inData));  
    digitalWrite(1, HIGH);  // LED on circuit board
  }
  else if (inData[0] == 'D')  // set defaults
  {
    defaults();
    memset(inData,0,sizeof(inData));
    Wire.write(1);
//    Wire.write("ok",30);
    digitalWrite(1, HIGH);
  }
  else
  {
    memset(inData,0,sizeof(inData));    
    digitalWrite(1, HIGH);  // LED on circuit board
  }
}
void defaults()
{
    flashLED = 1;
  //  Defaults  
    EEPROM.write(1, 99);  // I2C address - 40, 41, 42, 43, 44, 45, 46, 47
    delay(200);

    // read defaults
    readDefaults();
}
void readDefaults()
{
    // read defaults
    i2cAdd = EEPROM.read(1);
}
double readADC(int channel)
{
  uint32_t total = 0UL;
  uint16_t sampleCount = 4096;  // 2048
  for (uint16_t i = 0; i < sampleCount; i++) {total += analogRead(channel);}
  total = total >> 6;
  double proportional = (total * 1.0) / (0b00000001 << 6);
  return proportional;
}

I’m happy I can answer my own question. :slight_smile:

Turns out the flash memory can be corrupted from low voltage and the simple solution is to enable the brown out detection via the fuses. Like this if the voltage drops below 2.7v (for my setup) the ATtiny85 will shut down and restart when voltage goes back to safe level.

You can see it on page 144 of the datasheet.

Here’s the fuse settings I use, you can see under “HIGH fuse presets” header the setting for brownout detection.

http://eleccelerator.com/fusecalc/fusecalc.php?chip=attiny85&LOW=E2&HIGH=D5&EXTENDED=FF&LOCKBIT=FF

2 Likes

This topic was automatically closed 120 days after the last reply. New replies are no longer allowed.