State Machine transitions get stuck after an indeterminate of time

I am running two 2-state state machines, controlling a fan and a humidifier.
Desired behaviour is:
The fan runs for onTimeMinutesPerUnit per timeframe (e..g 1 minute per 15 minutes), and the humidifer has an "on" transition when the relative humidity drops below rhLowerBound and turns off when it goes above rhUpperBound.
This works for a while, until what I assume is a timing error in my code occurs, and the fan gets stuck in an on state, running permanently, while the humidifier is off, regardless of how low the humidity is. Restarting the arduino makes everything work again.

  • I don't believe the sensor library is crashing because that has nothing to do with the fan. I don't believe I have pin conflicts since I am using D5-7.
  • I am worried that maybe the relays are getting "stuck" during operation (using pre purchased relay modules).
  • I am worried that I have a stupid timing issue that causes transition issues, but I am not so sure, since the fan getting stuck shouldn't affect the humidifer.
  • I don't think it makes sense to "blame" the delay() function, but I have no idea what else the problem could be (except sticky relays).

Cheers for any help.

Here's a paste of the code:


#include "DHT.h"
#include <stdint.h>
#include <stdlib.h>

#define RELAY_FAN_PIN (7UL)
#define RELAY_HUMID_PIN (5UL)
#define SENSOR_HUMID_PIN (6UL)
#define MILLISECONDS_PER_MINUTE (60000UL)

//#define DEBUG
const uint32_t STATE_OFF = LOW;
const uint32_t STATE_ON = HIGH;

typedef enum
{
  PER_10_MINUTES = 10,
  PER_15_MINUTES = 15,
  PER_30_MINUTES = 30,
  PER_40_MINUTES = 40,
  PER_45_MINUTES = 45,
  PER_HOUR = 60
} timeframe_t;

typedef struct
{
  uint32_t currentState;
  uint32_t controlPin;
  uint32_t onPeriodMs;
  uint32_t offPeriodMs;
  uint32_t onTimeMs;
  uint32_t offTimeMs;
} fanData_t;

typedef struct
{
  uint32_t currentState;
  float rhUpperBound;
  float rhLowerBound;
  uint32_t controlPin;
  DHT *sensor;
} humidifierData_t;

DHT dht(SENSOR_HUMID_PIN, DHT11);
fanData_t fanInstance;
humidifierData_t hmdInstance;

void initFan(fanData_t *fan, uint32_t controlPin, uint32_t onTimeMinutesPerUnit, timeframe_t timeframe);
void initHumidifier(humidifierData_t *hmd, uint32_t controlPin, DHT *sensor, float rhLowerBound, float rhUpperBound);
void manageFan(fanData_t *fan);
void manageHumidity(humidifierData_t *hmd);

void setup() {
  // put your setup code here, to run once:
  initHumidifier(&hmdInstance, RELAY_HUMID_PIN, &dht, 87.F, 90.F);
  initFan(&fanInstance, RELAY_FAN_PIN, 1, PER_15_MINUTES); 
  
#ifdef DEBUG
  Serial.begin(9600);
#endif
}

void loop() {
  // put your main code here, to run repeatedly:
  manageFan(&fanInstance);
  manageHumidity(&hmdInstance);
  delay(5000);
}

void initHumidifier(humidifierData_t *hmd, uint32_t controlPin, DHT *sensor, float rhLowerBound, float rhUpperBound)
{
  hmd->currentState = STATE_ON;
  hmd->rhUpperBound = rhUpperBound;
  hmd->rhLowerBound = rhLowerBound;
  hmd->controlPin = controlPin;
  hmd->sensor = sensor;
  sensor->begin();
  pinMode(hmd->controlPin, OUTPUT);
}


void initFan(fanData_t *fan, uint32_t controlPin, uint32_t onTimeMinutesPerUnit, timeframe_t timeframe)
{
  fan->currentState = STATE_ON;
  fan->onPeriodMs = onTimeMinutesPerUnit * MILLISECONDS_PER_MINUTE;
  if (onTimeMinutesPerUnit < timeframe)
  {
    fan->offPeriodMs = (timeframe - onTimeMinutesPerUnit)* MILLISECONDS_PER_MINUTE;
  }
  else
  {
    fan->offPeriodMs = 0UL;
  }
  fan->controlPin = controlPin;
  fan->offTimeMs = millis();
  pinMode(fan->controlPin, OUTPUT);
  fan->onTimeMs = millis();
}

void manageFan(fanData_t *fan)
{
  uint32_t currentTime = millis();
  if (fan->currentState == STATE_ON)
  {
    #ifdef DEBUG
    Serial.print("Fan is on! Time (ms) till off: ");
    Serial.println(fan->onPeriodMs - (currentTime - fan->onTimeMs));
    #endif
    if ((currentTime - fan->onTimeMs) >= fan->onPeriodMs)
    {
      fan->currentState = STATE_OFF;
      fan->offTimeMs = millis();
    }
  }
  else if (fan->currentState == STATE_OFF)
  { 
    #ifdef DEBUG
    Serial.print("Fan is off! Time (ms) till on: ");
    Serial.println(fan->offPeriodMs - (currentTime - fan->offTimeMs));
    #endif
    if ((currentTime - fan->offTimeMs) >= fan->offPeriodMs)
    {
      fan->currentState = STATE_ON;
      fan->onTimeMs = millis();
    }
  }
  digitalWrite(fan->controlPin, fan->currentState);
}

void manageHumidity(humidifierData_t *hmd)
{
  float currentRh = hmd->sensor->readHumidity();

  if (hmd->currentState == STATE_ON)
  {
    #ifdef DEBUG
    Serial.print("Humidifier is on! ");
    #endif
    if (currentRh >= hmd->rhUpperBound)
    {
      hmd->currentState = STATE_OFF;
    }
  }
  else 
  {
    //Serial.print("Humidifier is off! ");
    if (currentRh <= hmd->rhLowerBound)
    { 
      hmd->currentState = STATE_ON;
    }
  }
  digitalWrite(hmd->controlPin, hmd->currentState);
  #ifdef DEBUG
  Serial.print("Current Humidity: ");
  Serial.print(currentRh);
  Serial.print(" Humidity Lower: ");
  Serial.print(hmd->rhLowerBound);
  Serial.print(" Humidity Upper: ");
  Serial.println(hmd->rhUpperBound);
  Serial.print("Current Temp: ");
  float currentTemp = hmd->sensor->readTemperature();
  Serial.println(currentTemp);
  #endif
}
1 Like

Welcome to the forum

Please do yourself and all of us a favour and post your code here, using code tags when you do, to avoid having to download it from another site

Hi hi, I've added the code, thanks for the heads up

Nice looking code but yes, post it here.

And since it is so nice looking and you seem to have some kind of mystery, everyone is going to want to see a schematic diagram.

Be sure to show what is powering everything and how that is arranged and wired.

A photo of everything in front of you might tell us something about where to point you for solving this.

a7

Don't think too much, instead check your code!

I'd remove the delay(5000) and disable debug output. You can use millis() to output a state log every 5 or 10 seconds.

I also would remove the "if" from the "else if" fan part or add another unconditional "else" to catch all state related trouble. "bool" state vars are safer, they cannot be anything but true (on) and false (off) otherwise.

If the problem persists then more checks are required.

How are the relays powered? The on-board regulator shuts down on an overheat caused by overload. A complete circuit diagram were nice, including the fan and humidifier and all supplies.

I'm currently traveling and can't draw an entire schematic or check it visually... but I am starting to worry about it being an issue with the NC/NO connections on the relays.

I use positive ON/OFF logic, which would lead me to believe that the fan and humidifier are connected to NO contacts. Which would mean that with a disconnected arduino, they shouldn't run. But they do. So maybe they are on NC contacts. In this case, the logic would be inverted. But it isn't, at least for a while.

I may have to return to this problem when I am back home in a few weeks.

Relays receive logical GND from the Arduino, +ve control from the digital pins. Internal pull resistors from the arduino are used.
The actual switching contacts are connected to the mains Phase Cable (Germany). They all have a unified Earth mains line.

if tried simulating your code and accelerating the timing my multiplying millis() by 100. had to make a few other changes (e.g. set onTimeMs to currentTime rather that call millis() again.

here's some output. maybe you could do something similar to at least verify the logic of your code to confirm it's a relay problem

 on           0          0      60000 -- Fan is on! Time (ms) till off: 60000
 on      101400          0      60000 -- Fan is on! Time (ms) till off: 4294925896
 OFF     203400     101400     840000 -- Fan is off! Time (ms) till on: 738000
 OFF     305000     101400     840000 -- Fan is off! Time (ms) till on: 636400
 OFF     406700     101400     840000 -- Fan is off! Time (ms) till on: 534700
 OFF     508300     101400     840000 -- Fan is off! Time (ms) till on: 433100
 OFF     609800     101400     840000 -- Fan is off! Time (ms) till on: 331600
 OFF     711400     101400     840000 -- Fan is off! Time (ms) till on: 230000
 OFF     813100     101400     840000 -- Fan is off! Time (ms) till on: 128300
 OFF     914700     101400     840000 -- Fan is off! Time (ms) till on: 26700
 OFF    1016200     101400     840000 -- Fan is off! Time (ms) till on: 4294892496
 on     1118300    1016200      60000 -- Fan is on! Time (ms) till off: 4294925196
 OFF    1220300    1118300     840000 -- Fan is off! Time (ms) till on: 738000
 OFF    1321800    1118300     840000 -- Fan is off! Time (ms) till on: 636500
 OFF    1423500    1118300     840000 -- Fan is off! Time (ms) till on: 534800
 OFF    1525100    1118300     840000 -- Fan is off! Time (ms) till on: 433200
 OFF    1626700    1118300     840000 -- Fan is off! Time (ms) till on: 331600
 OFF    1728400    1118300     840000 -- Fan is off! Time (ms) till on: 229900
 OFF    1829900    1118300     840000 -- Fan is off! Time (ms) till on: 128400
 OFF    1931500    1118300     840000 -- Fan is off! Time (ms) till on: 26800
 OFF    2033000    1118300     840000 -- Fan is off! Time (ms) till on: 4294892596
 on     2135100    2033000      60000 -- Fan is on! Time (ms) till off: 4294925196
 OFF    2237100    2135100     840000 -- Fan is off! Time (ms) till on: 738000
 OFF    2338800    2135100     840000 -- Fan is off! Time (ms) till on: 636300
 OFF    2440300    2135100     840000 -- Fan is off! Time (ms) till on: 534800
 OFF    2541900    2135100     840000 -- Fan is off! Time (ms) till on: 433200
 OFF    2643500    2135100     840000 -- Fan is off! Time (ms) till on: 331600
 OFF    2745200    2135100     840000 -- Fan is off! Time (ms) till on: 229900
 OFF    2846800    2135100     840000 -- Fan is off! Time (ms) till on: 128300
 OFF    2948400    2135100     840000 -- Fan is off! Time (ms) till on: 26700
 OFF    3049900    2135100     840000 -- Fan is off! Time (ms) till on: 4294892496
 on     3151900    3049900      60000 -- Fan is on! Time (ms) till off: 4294925296
 OFF    3253900    3151900     840000 -- Fan is off! Time (ms) till on: 738000
 OFF    3355600    3151900     840000 -- Fan is off! Time (ms) till on: 636300
 OFF    3457200    3151900     840000 -- Fan is off! Time (ms) till on: 534700
 OFF    3558800    3151900     840000 -- Fan is off! Time (ms) till on: 433100
 OFF    3660400    3151900     840000 -- Fan is off! Time (ms) till on: 331500
 OFF    3762000    3151900     840000 -- Fan is off! Time (ms) till on: 229900
 OFF    3863600    3151900     840000 -- Fan is off! Time (ms) till on: 128300
 OFF    3965300    3151900     840000 -- Fan is off! Time (ms) till on: 26600
 OFF    4066800    3151900     840000 -- Fan is off! Time (ms) till on: 4294892396
 on     4168800    4066800      60000 -- Fan is on! Time (ms) till off: 4294925296
 OFF    4270800    4168800     840000 -- Fan is off! Time (ms) till on: 738000
 OFF    4372400    4168800     840000 -- Fan is off! Time (ms) till on: 636400
 OFF    4474000    4168800     840000 -- Fan is off! Time (ms) till on: 534800
 OFF    4575600    4168800     840000 -- Fan is off! Time (ms) till on: 433200
 OFF    4677300    4168800     840000 -- Fan is off! Time (ms) till on: 331500
 OFF    4778900    4168800     840000 -- Fan is off! Time (ms) till on: 229900
 OFF    4880400    4168800     840000 -- Fan is off! Time (ms) till on: 128400
 OFF    4982100    4168800     840000 -- Fan is off! Time (ms) till on: 26700
 OFF    5083600    4168800     840000 -- Fan is off! Time (ms) till on: 4294892496
 on     5185600    5083600      60000 -- Fan is on! Time (ms) till off: 4294925296
 OFF    5287700    5185600     840000 -- Fan is off! Time (ms) till on: 737900
 OFF    5389300    5185600     840000 -- Fan is off! Time (ms) till on: 636300
 OFF    5490800    5185600     840000 -- Fan is off! Time (ms) till on: 534800
 OFF    5592500    5185600     840000 -- Fan is off! Time (ms) till on: 433100
 OFF    5694100    5185600     840000 -- Fan is off! Time (ms) till on: 331500
 OFF    5795700    5185600     840000 -- Fan is off! Time (ms) till on: 229900
 OFF    5897400    5185600     840000 -- Fan is off! Time (ms) till on: 128200
 OFF    5999000    5185600     840000 -- Fan is off! Time (ms) till on: 26600
 OFF    6100400    5185600     840000 -- Fan is off! Time (ms) till on: 4294892496
 on     6202500    6100400      60000 -- Fan is on! Time (ms) till off: 4294925196
 OFF    6304500    6202500     840000 -- Fan is off! Time (ms) till on: 738000
 OFF    6406100    6202500     840000 -- Fan is off! Time (ms) till on: 636400
 OFF    6507800    6202500     840000 -- Fan is off! Time (ms) till on: 534700
 OFF    6609400    6202500     840000 -- Fan is off! Time (ms) till on: 433100
 OFF    6710900    6202500     840000 -- Fan is off! Time (ms) till on: 331600
 OFF    6812500    6202500     840000 -- Fan is off! Time (ms) till on: 230000
 OFF    6914200    6202500     840000 -- Fan is off! Time (ms) till on: 128300
 OFF    7015800    6202500     840000 -- Fan is off! Time (ms) till on: 26700
 OFF    7117300    6202500     840000 -- Fan is off! Time (ms) till on: 4294892496
 on     7219400    7117300      60000 -- Fan is on! Time (ms) till off: 4294925196
 OFF    7321300    7219400     840000 -- Fan is off! Time (ms) till on: 738100
 OFF    7422900    7219400     840000 -- Fan is off! Time (ms) till on: 636500
 OFF    7524600    7219400     840000 -- Fan is off! Time (ms) till on: 534800
 OFF    7626200    7219400     840000 -- Fan is off! Time (ms) till on: 433200
 OFF    7727800    7219400     840000 -- Fan is off! Time (ms) till on: 331600
 OFF    7829500    7219400     840000 -- Fan is off! Time (ms) till on: 229900

my version of your code

#define MyHW
#ifdef MyHW
enum { DHT11 };
struct DHT {
    DHT (int a, int b)  { }
    void begin (void) { }
    int  readHumidity (void) { return 1; }
    int  readTemperature (void) { return 1; }
};
#define DEBUG

char s [80];

#else
#include "DHT.h"
#include <stdint.h>
#include <stdlib.h>
#endif

#define RELAY_FAN_PIN (7UL)
#define RELAY_HUMID_PIN (5UL)
#define SENSOR_HUMID_PIN (6UL)
#define MILLISECONDS_PER_MINUTE (60000UL)
//#define DEBUG
const uint32_t STATE_OFF = LOW;
const uint32_t STATE_ON = HIGH;
typedef enum
{
    PER_10_MINUTES = 10,
    PER_15_MINUTES = 15,
    PER_30_MINUTES = 30,
    PER_40_MINUTES = 40,
    PER_45_MINUTES = 45,
    PER_HOUR = 60
} timeframe_t;

typedef struct
{
    uint32_t currentState;
    uint32_t controlPin;
    uint32_t onPeriodMs;
    uint32_t offPeriodMs;
    uint32_t onTimeMs;
    uint32_t offTimeMs;
} fanData_t;

typedef struct
{
    uint32_t currentState;
    float rhUpperBound;
    float rhLowerBound;
    uint32_t controlPin;
    DHT *sensor;
} humidifierData_t;

DHT dht(SENSOR_HUMID_PIN, DHT11);
fanData_t fanInstance;
humidifierData_t hmdInstance;
void initFan(fanData_t *fan, uint32_t controlPin, uint32_t onTimeMinutesPerUnit, timeframe_t timeframe);
void initHumidifier(humidifierData_t *hmd, uint32_t controlPin, DHT *sensor, float rhLowerBound, float rhUpperBound);
void manageFan(fanData_t *fan);
void manageHumidity(humidifierData_t *hmd);

// -----------------------------------------------------------------------------
void setup() {
    // put your setup code here, to run once:
    initHumidifier(&hmdInstance, RELAY_HUMID_PIN, &dht, 87.F, 90.F);
    initFan(&fanInstance, RELAY_FAN_PIN, 1, PER_15_MINUTES);
    #ifdef DEBUG
    Serial.begin(9600);
    #endif
}

// -----------------------------------------------------------------------------
void loop() {
    // put your main code here, to run repeatedly:
    manageFan(&fanInstance);
 // manageHumidity(&hmdInstance);
    delay(1000);
}

// -----------------------------------------------------------------------------
void initHumidifier(humidifierData_t *hmd, uint32_t controlPin, DHT *sensor, float rhLowerBound, float rhUpperBound)
{
    hmd->currentState = STATE_ON;
    hmd->rhUpperBound = rhUpperBound;
    hmd->rhLowerBound = rhLowerBound;
    hmd->controlPin = controlPin;
    hmd->sensor = sensor;
    sensor->begin();
    pinMode(hmd->controlPin, OUTPUT);
}

void initFan(fanData_t *fan, uint32_t controlPin, uint32_t onTimeMinutesPerUnit, timeframe_t timeframe)
{
    fan->currentState = STATE_ON;
    fan->onPeriodMs = onTimeMinutesPerUnit * MILLISECONDS_PER_MINUTE;
    if (onTimeMinutesPerUnit < timeframe)
    {
        fan->offPeriodMs = (timeframe - onTimeMinutesPerUnit)* MILLISECONDS_PER_MINUTE;
    }
    else
    {
        fan->offPeriodMs = 0UL;
    }
    fan->controlPin = controlPin;
    fan->offTimeMs = millis();
    pinMode(fan->controlPin, OUTPUT);
    fan->onTimeMs = millis();
}

void manageFan(fanData_t *fan)
{
    uint32_t currentTime = 100 * millis();

    if (fan->currentState == STATE_ON)
    {
        sprintf (s, " on  %10ld %10ld %10ld -- ",
            currentTime, fan->onTimeMs, fan->onPeriodMs);
        Serial.print (s);

        #ifdef DEBUG
        Serial.print("Fan is on! Time (ms) till off: ");
        Serial.println(fan->onPeriodMs - (currentTime - fan->onTimeMs));
        #endif
        if ((currentTime - fan->onTimeMs) >= fan->onPeriodMs)
        {
            fan->currentState = STATE_OFF;
            fan->offTimeMs    = currentTime;
        }
    }
    else if (fan->currentState == STATE_OFF)
    {
        #ifdef DEBUG
        sprintf (s, " OFF %10ld %10ld %10ld -- ",
            currentTime, fan->offTimeMs, fan->offPeriodMs);
        Serial.print (s);

        Serial.print("Fan is off! Time (ms) till on: ");
        Serial.println(fan->offPeriodMs - (currentTime - fan->offTimeMs));
        #endif
        if ((currentTime - fan->offTimeMs) >= fan->offPeriodMs)
        {
            fan->currentState = STATE_ON;
            fan->onTimeMs     = currentTime;
        }
    }
    digitalWrite(fan->controlPin, fan->currentState);
}

void manageHumidity(humidifierData_t *hmd)
{
    float currentRh = hmd->sensor->readHumidity();
    if (hmd->currentState == STATE_ON)
    {
        #ifdef DEBUG
        Serial.print("Humidifier is on! ");
        #endif
        if (currentRh >= hmd->rhUpperBound)
        {
            hmd->currentState = STATE_OFF;
        }
    }
    else
    {
        //Serial.print("Humidifier is off! ");
        if (currentRh <= hmd->rhLowerBound)
        {
            hmd->currentState = STATE_ON;
        }
    }
    digitalWrite(hmd->controlPin, hmd->currentState);
    #ifdef DEBUG
    Serial.print("Current Humidity: ");
    Serial.print(currentRh);
    Serial.print(" Humidity Lower: ");
    Serial.print(hmd->rhLowerBound);
    Serial.print(" Humidity Upper: ");
    Serial.println(hmd->rhUpperBound);
    Serial.print("Current Temp: ");
    float currentTemp = hmd->sensor->readTemperature();
    Serial.println(currentTemp);
    #endif
}

Add pullup/pulldown resistors that keep the relays inactive with a missing or resetting controller.

Der Schutzkontakt darf nicht mit der Schaltung verbunden werden. Er geht nur zu metallischen Gehäusen die keine Spannung führen dürfen (Berührungsschutz).

Sorry, let me correct myself - I didnt' mean Earth, I meant null line.

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