Initializing Multiple Sensors in an array

Hello. I'm currently using multiple Adafruit MCP9600 thermocouple sensors and an Arduino R3 to monitor a system. The goal is to be able to dynamically initialize them and call upon them in further lines of code. The basic function of said code is 'if temp > setPoint relayPin = high;'

When I initialize them individually such as

Adafruit_MCP9600 mcp;
Adafruit_MCP9600 mcp2;

and so on, I have no issues. However, if I initialize them as an array such as

Adafruit_MCP9600 mcpArray[4];
int I2C_ADDRESS[4] = {0x60, 0x67, 0x64, 0x65};

The arduino resets to the beginning of the code.

I have two prompts at the beginning of my code. The first asking how many thermocouples I have. This sets the tcChannels int which the array relies on. The second asks to reuse the previous setpoint. Currently, it makes it through each prompt, and then when it transitions to the IDLE state, it resets. I've put in a few debugging serial prints, and this is the output I get from the serial monitor:

Serial Begin!
MCP9600 HW test
Found MCP9600!
ADC resolution set to 18 bits
Thermocouple type set to K type
Filter coefficient value set to: 3
Setpoint Prompt
transitioning to Serial Begin!

Here is my complete code:

#include <Wire.h>
#include <Adafruit_I2CDevice.h>
#include <Adafruit_I2CRegister.h>
#include "Adafruit_MCP9600.h"
#include <LiquidCrystal.h>
#include <EEPROM.h>  // Include the EEPROM library

#define upButtonPin A0
#define downButtonPin A1
#define menuButtonPin A2
#define selectButtonPin A3
#define alarmPin 7
//#define I2C_ADDRESS (0x67)
//#define I2C_ADDRESS2 (0x60)
//Adafruit_MCP9600 mcp;
//Adafruit_MCP9600 mcp2;

Adafruit_MCP9600 mcpArray[4];
int I2C_ADDRESS[4] = {0x60, 0x67, 0x64, 0x65};

const int relayPin = 8;           // Digital pin connected to the relay
int setPoint = 30.0;              // Setpoint temperature in degrees Celsius
float responseTime = 0;             // Response time in seconds
int relayTrigger = HIGH;          // Relay trigger behavior
int storedSetPointAddr = 0;
int storedSetPoint = EEPROM.read(storedSetPointAddr);
float hysteresis = 0.00;  // Initial hysteresis value
int tcChannels = 1;
float temp[4];

LiquidCrystal lcd(12, 11, 5, 4, 3, 2); // RS, EN, D4, D5, D6, D7
enum State
{
  SETUP,
  IDLE,
  MENU,
  SET_SETPOINT,
  SET_RESPONSE_TIME,
  SET_RELAY_TRIGGER,
  SET_HYSTERESIS,
  SET_TCCHANNELS
};

State currentState = SETUP;
unsigned long lastButtonPressTime = 0;
const unsigned long menuTimeout = 10000; // 10 seconds timeout for menu

void setup()
{
  Serial.begin(115200);
  Serial.println("Serial Begin!");
  pinMode(relayPin, OUTPUT);
  pinMode(upButtonPin, INPUT_PULLUP);
  pinMode(downButtonPin, INPUT_PULLUP);
  pinMode(menuButtonPin, INPUT_PULLUP);
  pinMode(selectButtonPin, INPUT_PULLUP);
  pinMode(alarmPin, OUTPUT);

  digitalWrite(relayPin, !relayTrigger);
  digitalWrite(alarmPin, LOW);
  lcd.begin(16, 2);
  delay(10);

  lcd.clear();
  lcd.setCursor(0, 0);
  lcd.print("Number of TCs:  ");
  lcd.setCursor(0, 1);
  lcd.print(tcChannels);

  int chosenChannels = tcChannels;
  while (true) {
    if (digitalRead(upButtonPin) == LOW) {
      chosenChannels ++;  // Increase channels by 1
      lcd.setCursor(0, 1);
      lcd.print(chosenChannels);  // Display
      delay(500);
    } else if (digitalRead(downButtonPin) == LOW) {
      chosenChannels--;  // Decrease channels by 1
      lcd.setCursor(0, 1);
      lcd.print(chosenChannels);  // Display with two decimal places
      delay(500);
    } else if (digitalRead(selectButtonPin) == LOW) {
      tcChannels = chosenChannels;
      lcd.clear();
      delay(500);
      break;
    }
  }

  //  Serial.println("MCP9600 HW test");
  //
  //
  ////  /* Initialise the driver with I2C_ADDRESS and the default I2C bus. */
  ////  if (! mcp.begin(I2C_ADDRESS)) {
  ////    Serial.println("Sensor not found. Check wiring!");
  ////    while (1);
  ////  }
  ////  if (! mcp2.begin(I2C_ADDRESS2)) {
  ////    Serial.println("Sensor not found. Check wiring!");
  ////    while (1);
  ////  }
  Serial.println("MCP9600 HW test");
  lcd.clear();
  lcd.print("Initializing TCs");
  delay(500);
  for (int i = 0; i <= tcChannels-1; i++) {
    // Initialize each MCP9600 object with the appropriate I2C address
    if (!mcpArray[i].begin(I2C_ADDRESS[i])) {
      Serial.println("Sensor not found. Check wiring!");
      lcd.clear();
      lcd.setCursor(0, 0);
      lcd.print("No TC CH:");
      lcd.print(i);
      //while (1);
    }
  }
  Serial.println("Found MCP9600!");
  for (int i = 0; i <= tcChannels-1; i++) {
    mcpArray[i].setADCresolution(MCP9600_ADCRESOLUTION_18);
    Serial.print("ADC resolution set to ");
    switch (mcpArray[i].getADCresolution()) {
      case MCP9600_ADCRESOLUTION_18:   Serial.print("18"); break;
      case MCP9600_ADCRESOLUTION_16:   Serial.print("16"); break;
      case MCP9600_ADCRESOLUTION_14:   Serial.print("14"); break;
      case MCP9600_ADCRESOLUTION_12:   Serial.print("12"); break;
    }
    Serial.println(" bits");
  }
  for (int i = 0; i <= tcChannels-1; i++) {
    mcpArray[i].setThermocoupleType(MCP9600_TYPE_K);
    Serial.print("Thermocouple type set to ");
    switch (mcpArray[i].getThermocoupleType()) {
      case MCP9600_TYPE_K:  Serial.print("K"); break;
      case MCP9600_TYPE_J:  Serial.print("J"); break;
      case MCP9600_TYPE_T:  Serial.print("T"); break;
      case MCP9600_TYPE_N:  Serial.print("N"); break;
      case MCP9600_TYPE_S:  Serial.print("S"); break;
      case MCP9600_TYPE_E:  Serial.print("E"); break;
      case MCP9600_TYPE_B:  Serial.print("B"); break;
      case MCP9600_TYPE_R:  Serial.print("R"); break;
    }
    Serial.println(" type");
  }

  for (int i = 0; i <= tcChannels-1; i++) {
    mcpArray[i].setFilterCoefficient(3);
    Serial.print("Filter coefficient value set to: ");
    Serial.println(mcpArray[i].getFilterCoefficient());
  }
  for (int i = 0; i <= tcChannels-1; i++) {
    mcpArray[i].enable(true);
  }



  //  mcp.setAlertTemperature(1, 30);
  //  Serial.print("Alert #1 temperature set to ");
  //  Serial.println(mcp.getAlertTemperature(1));
  //  mcp.configureAlert(1, true, true);  // alert 1 enabled, rising temp


  //  mcp2.enable(true);

  // Serial.println(F("------------------------------"));


  // Show the prompt to use the last setpoint or clear it with auto-scroll
  Serial.println("Setpoint Prompt");
  lcd.clear();
  lcd.setCursor(0, 0);
  //lcd.autoscroll();
  lcd.print("Use last setpoint?");
  lcd.print(storedSetPoint + "C");
  lcd.setCursor(0, 1);
  //lcd.noAutoscroll();
  lcd.print("Up=Yes Down=Clear");

  // Wait for button press
  while (true) {
    if (digitalRead(upButtonPin) == LOW) {
      // Use the last setpoint
      setPoint = storedSetPoint;
      lcd.clear();
      lcd.print("Setpoint: ");
      lcd.print(setPoint);
      lcd.print((char)223); // Print the degree symbol
      lcd.print("C");
      delay(1000);  // Show the setpoint for a moment
      currentState = IDLE;  // Transition to IDLE state
      break;
    } else if (digitalRead(downButtonPin) == LOW) {
      // Clear the stored setpoint
      EEPROM.write(storedSetPointAddr, 0);
      lcd.clear();
      lcd.print("Setpoint cleared");
      delay(1000);  // Show the message for a moment
      lcd.clear();
      lcd.print("Setpoint: "); //Show the default setpoint
      lcd.print(setPoint);
      lcd.print((char)223);
      delay(1000);
      delay(1000);
      Serial.println("transitioning to IDLE");
      break;
    }
  }
  currentState = IDLE;  // Transition to IDLE state

}

void handleMenu() {
  const char *menuOptions[] = {"Setpoint        ", "Response Time   ", "Relay Trigger   ", "Hysteresis      ", "Multi Channel   "};
  int currentOption = 0;
  unsigned long menuStartTime = millis();

  lcd.clear();

  while (currentState == MENU) {
    lcd.setCursor(0, 0);
    lcd.print("Menu: ");
    lcd.setCursor(0, 1);
    lcd.print(menuOptions[currentOption]);

    if (digitalRead(upButtonPin) == LOW) {
      currentOption = (currentOption + 1) % 5; // Cycle through options
      delay(200); // Debounce delay
    } else if (digitalRead(downButtonPin) == LOW) {
      currentOption = (currentOption + 3) % 5; // Cycle through options in reverse
      delay(200); // Debounce delay
    } else if (digitalRead(selectButtonPin) == LOW) {
      switch (currentOption) {
        case 0:
          currentState = SET_SETPOINT;
          break;
        case 1:
          currentState = SET_RESPONSE_TIME;
          break;
        case 2:
          currentState = SET_RELAY_TRIGGER;
          break;
        case 3:
          currentState = SET_HYSTERESIS;
          break;
        case 4:
          currentState = SET_TCCHANNELS;
          break;
      }

      delay(200); // Debounce delay
    } else if (digitalRead(menuButtonPin) == LOW && currentState == MENU &&
               millis() - menuStartTime >= 1000) {
      currentState = IDLE; // Exit to IDLE state if 10 seconds have passed
      lcd.clear();
      delay(500);          // Debounce delay
    }

    lastButtonPressTime = millis();
    delay(100);
  }
}

void handleSetSetpoint()
{
  lcd.clear();
  lcd.setCursor(0, 0);
  lcd.print("Setpoint: ");
  lcd.setCursor(0, 1);
  lcd.print(setPoint);
  lcd.print((char)223); // Print the degree symbol
  lcd.print("C");
  int chosenSetPoint = setPoint;
  while (currentState == SET_SETPOINT)
  {
    if (digitalRead(upButtonPin) == LOW)
    {
      chosenSetPoint++;
      lcd.setCursor(0, 1);
      lcd.print(chosenSetPoint);
      lcd.print((char)223); // Print the degree symbol
      lcd.print("C  ");
    }
    else if (digitalRead(downButtonPin) == LOW)
    {
      chosenSetPoint--;
      lcd.setCursor(0, 1);
      lcd.print(chosenSetPoint);
      lcd.print((char)223); // Print the degree symbol
      lcd.print("C  ");
    }
    else if (digitalRead(menuButtonPin) == LOW)
    {
      currentState = MENU;
    }
    else if (digitalRead(selectButtonPin) == LOW)
    {
      setPoint = chosenSetPoint;
      EEPROM.write(storedSetPointAddr, setPoint);
      currentState = IDLE; // Exit to IDLE state if 10 seconds have passed
      lcd.clear();
      delay(500);
    }

    lastButtonPressTime = millis();
    delay(100); // Debounce delay
  }
}

void handleSetResponseTime()
{
  lcd.clear();
  lcd.setCursor(0, 0);
  lcd.print("Response Time:");
  lcd.setCursor(0, 1);
  lcd.print(responseTime);
  lcd.print(" sec");

  while (currentState == SET_RESPONSE_TIME)
  {
    if (digitalRead(upButtonPin) == LOW)
    {
      responseTime += 0.01;
      lcd.setCursor(0, 1);
      lcd.print(responseTime, 2);
      lcd.print("s     ");
      //delay(100); // Debounce delay
    }
    else if (digitalRead(downButtonPin) == LOW)
    {
      if (responseTime > 0)
      {
        responseTime -= 0.01;
        lcd.setCursor(0, 1);
        lcd.print(responseTime);
        lcd.print(" sec  ");
      }
      //delay(100); // Debounce delay
    }
    else if (digitalRead(menuButtonPin) == LOW)
    {
      currentState = MENU;
    }

    else if (digitalRead(selectButtonPin) == LOW)
    {
      currentState = IDLE; // Exit to IDLE state if 10 seconds have passed
      lcd.clear();
      delay(500);
    }

    lastButtonPressTime = millis();
    delay(100); // Debounce delay
  }
}

void handleSetRelayTrigger()
{

  lcd.clear();
  lcd.setCursor(0, 0);
  lcd.print("Relay Trigger: ");

  while (currentState == SET_RELAY_TRIGGER)
  {
    lcd.setCursor(0, 1);
    lcd.print((relayTrigger == HIGH) ? "High " : "Low ");

    if (digitalRead(upButtonPin) == LOW || digitalRead(downButtonPin) == LOW) {
      relayTrigger = (digitalRead(upButtonPin) == LOW) ? HIGH : LOW;
      lcd.setCursor(0, 1);
      lcd.print((relayTrigger == HIGH) ? "High " : "Low ");
      delay(200); // Debounce delay
    }
    else if (digitalRead(menuButtonPin) == LOW)
    {
      currentState = MENU;
      delay(200); // Debounce delay
    }
    else if (digitalRead(selectButtonPin) == LOW)
    {
      currentState = IDLE; // Exit to IDLE state if 10 seconds have passed
      lcd.clear();
      delay(500);
    }
    lastButtonPressTime = millis();
    delay(100); // Debounce delay
  }
}

void handleSetHysteresis() {
  lcd.clear();
  lcd.setCursor(0, 0);
  lcd.print("Hysteresis: ");
  lcd.setCursor(0, 1);
  lcd.print(hysteresis, 2);  // Display hysteresis with two decimal places
  lcd.print((char)223); // Print the degree symbol
  lcd.print("C");

  float chosenHysteresis = hysteresis;
  while (currentState == SET_HYSTERESIS) {
    if (digitalRead(upButtonPin) == LOW) {
      chosenHysteresis += 0.01;  // Increase hysteresis by 0.01
      lcd.setCursor(0, 1);
      lcd.print(chosenHysteresis, 2);  // Display with two decimal places
      lcd.print((char)223); // Print the degree symbol
      lcd.print("C  ");
    } else if (digitalRead(downButtonPin) == LOW) {
      chosenHysteresis -= 0.01;  // Decrease hysteresis by 0.01
      lcd.setCursor(0, 1);
      lcd.print(chosenHysteresis, 2);  // Display with two decimal places
      lcd.print((char)223); // Print the degree symbol
      lcd.print("C  ");
    } else if (digitalRead(menuButtonPin) == LOW) {
      currentState = MENU;
    } else if (digitalRead(selectButtonPin) == LOW) {
      hysteresis = chosenHysteresis;
      currentState = IDLE;  // Exit to IDLE state if 10 seconds have passed
      lcd.clear();
      delay(500);
    }

    lastButtonPressTime = millis();
    delay(100);  // Debounce delay
  }
}

void handleSetChannels() {
  lcd.clear();
  lcd.setCursor(0, 0);
  lcd.print("Number of TCs:  ");
  lcd.setCursor(0, 1);
  lcd.print(tcChannels);

  int chosenChannels = tcChannels;
  while (currentState == SET_TCCHANNELS) {
    if (digitalRead(upButtonPin) == LOW) {
      chosenChannels ++;  // Increase channels by 1
      lcd.setCursor(0, 1);
      lcd.print(chosenChannels);  // Display
    } else if (digitalRead(downButtonPin) == LOW) {
      chosenChannels--;  // Decrease hysteresis by 1
      lcd.setCursor(0, 1);
      lcd.print(chosenChannels);  // Display with two decimal places
    } else if (digitalRead(menuButtonPin) == LOW) {
      currentState = MENU;
    } else if (digitalRead(selectButtonPin) == LOW) {
      tcChannels = chosenChannels;
      currentState = IDLE;  // Exit to IDLE state if 10 seconds have passed
      lcd.clear();
      delay(500);
    }

    lastButtonPressTime = millis();
    delay(100);  // Debounce delay

  }
}

void overtempControl() {
  for (int i = 0; i < 4; i++) {
    temp[i] = mcpArray[i].readThermocouple();
  }
  unsigned long currentTime = millis();
  static unsigned long lastOvertempTime = 0;
  if (tcChannels == 1) {
    lcd.setCursor(0, 0);
    lcd.print("Temp: ");
    lcd.print(temp[0]);
    lcd.print((char)223);
    lcd.print("C   ");
  }
  else if (tcChannels == 2) {
    lcd.setCursor(0, 0);
    lcd.print("1: ");
    lcd.print(temp[0], 1);
    lcd.print("C  ");

    lcd.setCursor(8, 0);  // Adjust the cursor position for the second channel
    lcd.print("2: ");
    lcd.print(temp[1], 1);
    lcd.print("C  ");
  }

  lcd.setCursor(0, 1);
  lcd.print("Setpoint: ");
  lcd.print(setPoint);
  lcd.print((char)223);
  lcd.print("C  ");

  if (temp[0] >= (setPoint + hysteresis) || temp[1] >= (setPoint + hysteresis)) {
    if (currentTime - lastOvertempTime >= responseTime * 1000) {
      lcd.setCursor(0, 1);
      lcd.print("Overtemp!");
      digitalWrite(relayPin, relayTrigger);
      digitalWrite(alarmPin, HIGH);
    }
    else
    {
      digitalWrite(relayPin, !relayTrigger);
      digitalWrite(alarmPin, LOW);
      lastOvertempTime = currentTime; // Reset overtemp time when temperature is below setpoint
    }
  }
  if (digitalRead(menuButtonPin) == LOW)
  {
    currentState = MENU;
    lastButtonPressTime = currentTime;
  }
}

void loop()
{
  //  Serial.print("Hot Junction: "); Serial.println(mcp.readThermocouple());
  //  Serial.print("Cold Junction: "); Serial.println(mcp.readAmbient());
  //  Serial.print("ADC: "); Serial.print(mcp.readADC() * 2); Serial.println(" uV");
  //  delay(1000);
  //  float temp = mcp.readThermocouple();
  //  float temp2 = mcp2.readThermocouple();


  //get_temperature(&temp);
  //digitalWrite(relayPin,relayTrigger);
  switch (currentState)
  {
    case IDLE:
      overtempControl();
      break;
    case MENU:
      handleMenu();
      break;
    case SET_SETPOINT:
      handleSetSetpoint();
      break;
    case SET_RESPONSE_TIME:
      handleSetResponseTime();
      break;
    case SET_RELAY_TRIGGER:
      handleSetRelayTrigger();
      break;
    case SET_HYSTERESIS:
      handleSetHysteresis();
      break;
    case SET_TCCHANNELS:
      handleSetChannels();
      break;
  }
  delay(100);
}

Please forgive me if I've left out any details. I'm aware that it could be an issue with SRAM, and I'm open to solutions to reduce the amount of SRAM being used. Here's my current upload output:

Sketch uses 14226 bytes (44%) of program storage space. Maximum is 32256 bytes.
Global variables use 1084 bytes (52%) of dynamic memory, leaving 964 bytes for local variables. Maximum is 2048 bytes

Sry, I don't see how that was meant to work. Those two lines are unrelated. You have an array of four Adafruit_MCP9600 objects and an array of four ints.

Post the code where four worked the way you don't want to do it.

a7

Unrelated to your problem, but you should consider revising this line of code

int storedSetPoint = EEPROM.read(storedSetPointAddr);

EEPROM.read() reads a single byte so either use EEPROM.get() to load the int value or declare storedSetPoint as byte and use EEPROM.read()

1 Like

Four is the maximum I will have. Currently I'm only using 2. The number in the array is gotten from tcChannels. For example, if tcChannels = 2(what I have wired up currently), then it will only initialize two sensors, using the first two values in the array for the mcp objects, and the i2c addresses.
i.e. mcpArray[0] using the address I2C_ADDRESS[0] and
mcpArray[1] using I2C_ADDRESS[1].

I might be calling the arrays incorrectly.

Colour me blind… I see and just now that looks plausible. I look for my brain and try again, sry.

a7

I'd use an array of struct:

#include <Wire.h>
#include <Adafruit_I2CDevice.h>
#include <Adafruit_I2CRegister.h>
#include "Adafruit_MCP9600.h"

struct MCP {
  uint8_t i2cAddress;
  Adafruit_MCP9600 mcp;

  MCP(uint8_t addr) : i2cAddress(addr) {}
  bool begin() {
    return mcp.begin(i2cAddress);
  }
};

MCP mcpArray[] = {0x60, 0x67, 0x64, 0x65};

void setup() {
  for (auto &m : mcpArray) {
    if (!m.begin()) {
      Serial.println("Sensor not found. Check wiring!");
    }
  }
}

void loop() {
}

@jaypresleyTAT check to make sure you aren't trying to do anything with more than the four sensors and I2C addresses you really have.

Such a check might be put in the code in a few places, or monitor

Also, not your problem, but it is more common and less ink to write

  for (int i = 0; i < tcChannels; i++) {

which is instantly recognized as looping over tcChannels number of <whatevers>.

a7

1 Like

Hi @jaypresleyTAT ,

after (more carefully :wink: ) reading your code I found this:

void overtempControl() {
  for (int i = 0; i < 4; i++) {
    temp[i] = mcpArray[i].readThermocouple();

which is the function that is called in IDLE.

I guess it should read

void overtempControl() {
  for (int i = 0; i < tcChannels; i++) {
    temp[i] = mcpArray[i].readThermocouple();

instead ...

Guess I was distracted by the initialization of the mcpArray (as some others also) from this problem. This inspired me to look at IDLE ...

Four is the maximum I will have. Currently I'm only using 2.

2 Likes

This seemed to fix it. The loop must have run into an error trying to call an index of an array that didn't exist. Thank you so much!

The loop must have run into an error trying to call an index of an array that didn't exist.

Not exactly, the index of that array existed (you declared mcpArray[4]).

I think it was the call of the function mcpArray[i].readThermocouple(); where the object mcpArray[i] was not initialized.

Glad that it works now!

P.S.: You should avoid "Magic Numbers" in the code and make sure that one cannot set tcChannels to a value greater than the maximum number of installed and successfully initialized mcps!

1 Like

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