Project tutorial
Dosing Peristaltic Pump (PWM SSR), HX711 Scale and an FSM

Dosing Peristaltic Pump (PWM SSR), HX711 Scale and an FSM © GPL3+

Finite state machine, DC motor variable speed (PWM, solid state relay), software timer objects, HX711 scale and WawiLib. Tutorial value!

  • 243 views
  • 0 comments
  • 2 respects

Components and supplies

Ph a000067 iso (1) wzec989qrf
Arduino Mega 2560
×1
M6hlqueeutbzfkhid4ph
Arduino Ethernet Shield 2
×1
5292110
Bench Power Supply, DC
×1
Digital Load Cell Weight Sensor HX711 AD Converter Breakout Module 5KG Portable Electronic Kitchen Scale for Arduino Scale new Digital Load Cell Weight Sensor HX711 AD Converter Breakout Module 5KG Portable Electronic Kitchen Scale for Arduino Sca
×1
DC Small Dosing Pump 3V 6V 12V 24V Micro Self priming Mute Mini Peristaltic Liquid
×1
OOTDTY 2 Channel SSR Solid State Relay High Lever Trigger 5A 5v12v For Uno R3 APR11_10
×1
Mini 5V Traffic Light LED Display Module for Arduino Red Yellow Green 5mm LED RG
×1

Apps and online services

About this project

This application shows a dosing application using 2 peristaltic pumps (PWM driven via solid state relays), 1 digital weight transducer (using a HX711), a LED traffic light and an Arduino Mega2560 with an Ethernet shield.

First the vessel on the scale is filled in 15 ml steps up to 100ml and then it is emptied. Commands to start and stop are issued via Ethernet using WawiLib to read and write sketch variables. The state changes of the state machine are reflected to the output window of WawiLib.

There are 3 movies documenting the application:

Application demonstration.

The hardware explained (SSR and flywheel diode concepts)

Software with WawiLib variable access, C++ timer objects, PWM Solid State Relay and FSM

Technologies used and explained in detail:

// Finite state machine code expamle:
   //+++++++++++++++++++++++++++++++++++++++
   case FsmState::STEP3_WAIT_CMD_FILL:    
   {
     if (cmdFill) 
       FsmGoTo(FsmState::STEP4_P_FILL_FAST); 
   }
   break;  

Arduino "millis()" based software timers (as a C++ object in a separate file in the sketch). The use is different from what you normally see (millis() is encapsulated in the C++ object and the object is added in a separate file to the ide). All is explained step by step in the.pdf that can be downloaded from sylvestersolutions.com/documentation.

// blinking 2 timers at the same time with variable time period:
#include "WawiTimer.h"
// blinker bit helper timers(software timers):
WawiTimer tBlink250; // 250 ms on
WawiTimer tBlink500; // 500 ms
// blinking bits that toggle:
bool blink250;
bool blink500;
//------------------------------------------------------------
// create blinkbits:
void Loop()
{
  if (tBlink250.isZero())
  {
      tBlink250.setMs(250);
      blink250 = !blink250;
  }
  if
  {
      tBlink500.setMs(500);
      blink500 = !blink500;
   }
   tBlink250.loop(); // 250 ms 
   tBlink500.loop(); // 500 ms
}

Forget about millis()! Add your C++ timer object that is counts down so your loop is not interrupted: the only thing you need to do is add 2 files to your sketch! (The documentation explains in detail how.)

- PWM (pulse width modulation) via SSR (solid state relay) on a DC motor.

Pulse width modulation is a technique used to control (e.g.) the speed of an DC motor. Some of the Arduino outputs can switch on and of very fast (hardware based) at multiple kHz. If you use this function to regulate the current to a DC motor you can make it run at variable speed. The movie and documents explain in detail how to do this.

A DC motor contains a coil. As a coil is an inductance. You must make sure that its current flow is not interrupted when the relay opens. A diode is the ideal component to do this. This tutorial explains the concept of a flywheel diode in detail.

- The AppNotePeriPump.pdf detailed tutorial can be downloaded from www.sylvestersolutions.com explaining the details step by step. Go to the documentation page of the Sylvester Solutions website.

Sylvester solutions site.

If you do not know how to program an automation task properly (i.e. without delay statements). Look at this tutorial and you will learn a lot. All concepts are in detail explained in the pdf and the.ino source code is also included.

The code can be used as a framework for many other applications as it is structured like the pro's do.

Have fun and learn!

Code

The final program.Arduino
//------------------------------------------------------------
#include <WawiEthernet.h>
// the media access control (ethernet hardware) address for the shield:
byte mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0x88 }; 

// the IP address of Arduino shield:
byte ipArd[] = { 192, 168, 0, 88 };    

// the communication port Arduino side for WawiLib communication
unsigned int port = 49152;

// the router's gateway address:
byte gateway[] = { 10, 0, 0, 1 };

// the network subnet:
byte subnet[] = { 255, 255, 225, 0 };

// declare communication object:
EthernetServer server = EthernetServer(port);

// WawiLib communications object:
WawiEthernet WawiSrv;

//------------------------------------------------------------
// HX711 scale load cell:
#include "HX711.h" // credits to Bogdan Necula
HX711 scale;
const int LOADCELL_DOUT_PIN = 6; // HX711 data pin
const int LOADCELL_SCK_PIN = 7;  // HX711 clock pin
bool triggerTare;

#include "timer.h"
WawiTimer tWdScale; // watchdog timer scale communication
bool scaleOk=false; // scale communication status

//------------------------------------------------------------
// Finite state machine variables:
enum class FsmState       { STEP0_IOTEST=0, STEP1_IDLE=1, STEP2_TARRA=2, STEP3_WAIT_CMD_FILL=3, STEP4_P_FILL_FAST=4, STEP5_P_FILL_SLOW=5, STEP6_WAIT=6, STEP7_WAIT_CMD_EMPTY=7, STEP8_P_EMPTY_FAST=8, STEP9_P_EMTPY_TIMEBASED=9, STEP10_ERROR=10 };
const char* stepStrings[] { "Step 0: Iotest", "Step 1: Idle", "Step 2: Tarra scale", "Step 3: Await cmd fill", "Step 4: Fill fast", "Step 5: Fill slow", "Step 6: Wait time", "Step 7: Await cmd empty", "Step 8: Empty fast", "Step 9: Empty time based", "Step 10: Error" };
char stepString[30];
FsmState stepCurrent,stepPrev;
bool firstNewStep;

// commands for finite state machine:
bool cmdTarra;
bool cmdFill;
bool cmdEmpty;
bool cmdClrError;

// status variables
bool errScale, errTimeout;

// fill parameters
float wFillMax=100; 
float wFillBatchFast=10; 
float wFillBatchSlow=5; 
float wActual, wTargetStep;

unsigned long long tWdFillTimeout=30l*1000l; // 30 sec
unsigned long long tWdEmptyTimeout=300l*1000l; // 300 sec

WawiTimer tWdFsm; // watchdog timers for FSM time-out
bool ledR=0,ledY=0,ledG=0; // LED control
//------------------------------------------------------------
// digital outputs connected to solid state relays 1 and 2:
#define DO_SSR_P1 8
#define DO_SSR_P2 9

// PWM ratio control pump operation:
unsigned short pump1PwmFast=255;
unsigned short pump2PwmFast=255;
unsigned short pump1PwmSlow=128;
unsigned short pump2PwmSlow=128;
// actual value of SSR PWM setpoints:
unsigned short pump1Pwm=0;
unsigned short pump2Pwm=0;

//------------------------------------------------------------
// digital outputs connected to solid state relays 1 and 2:
#define DO_LED_RED 49
#define DO_LED_YELLOW 47
#define DO_LED_GREEN 45

// blinker bit helper timers (software timers):
WawiTimer tBlink250; // 250 ms on
WawiTimer tBlink500; // 500 ms
// blinking bits:
bool blink250;
bool blink500;

//------------------------------------------------------------
// make variables of interest known to WawiLib:
// this function is used in WawiSrv.begin(....)
void wawiVarDef()
{
  WawiSrv.wawiVar(triggerTare);
  WawiSrv.wawiVar(scaleOk);
  WawiSrv.wawiVar(tWdScale.m_act);
  
  WawiSrv.wawiVar(cmdTarra);
  WawiSrv.wawiVar(cmdFill);
  WawiSrv.wawiVar(cmdEmpty);
  WawiSrv.wawiVar(cmdClrError);
  
  WawiSrv.wawiVar(errScale);
  WawiSrv.wawiVar(errTimeout);
 
  WawiSrv.wawiVar(wFillMax);
  WawiSrv.wawiVar(wFillBatchFast);
  WawiSrv.wawiVar(wFillBatchSlow);
  WawiSrv.wawiVar(wActual);
  WawiSrv.wawiVar(wTargetStep);

  WawiSrv.wawiVar(tWdFillTimeout);
  WawiSrv.wawiVar(tWdEmptyTimeout);
  WawiSrv.wawiVar(tWdFsm);

  WawiSrv.wawiVar(stepCurrent);
  WawiSrv.wawiVar(stepPrev);
  WawiSrv.wawiVarArray(stepString);
  WawiSrv.wawiVar(stepString);
  WawiSrv.wawiVar(tWdFsm.m_act);

  WawiSrv.wawiVar(pump1PwmFast);
  WawiSrv.wawiVar(pump2PwmFast);
  WawiSrv.wawiVar(pump1PwmSlow);
  WawiSrv.wawiVar(pump2PwmSlow);
  WawiSrv.wawiVar(pump1Pwm);
  WawiSrv.wawiVar(pump2Pwm);

  WawiSrv.wawiVar(tBlink250.m_act);
  WawiSrv.wawiVar(tBlink500.m_act);
  WawiSrv.wawiVar(ledR);
  WawiSrv.wawiVar(ledG);
  WawiSrv.wawiVar(ledY);
}

//------------------------------------------------------------
// set finite state machine to other step:
void FsmGoTo(FsmState newStep) 
{
  stepCurrent = newStep;
  WawiSrv.println((String) "New step = "+ stepString + " (current weight=" +(String) wActual+")");
}

//------------------------------------------------------------
// processing of scale (0..5KG) communication:
void ScaleLoop()
{
  // read weight, if available:
  if (scale.is_ready())
  {
    // read weight
    wActual = scale.get_units(1);
    // set watchdog:    
    tWdScale.setMs(1000);
    // process tare cmd if required:
    if (triggerTare)
    {
      // reset cmd to Tare:
      triggerTare=0;
      scale.tare();  
      WawiSrv.print("Scale tare operation completed. ");
    }
  }  
  
  // process watchdog status:
  if (scaleOk != !tWdScale.isZero())
  {
    scaleOk = !tWdScale.isZero();
    if (scaleOk)
      WawiSrv.println("Weight processing restarted, scale ok. ");
    else
      WawiSrv.println("Weight processing stopped, scale communication fault. ");
  }

  // loop part:
  tWdScale.loop();
}

//------------------------------------------------------------
// Finite state machine:
void FsmLoop() 
{
  switch(stepCurrent)
  {
    //+++++++++++++++++++++++++++++++++++++++
    case FsmState::STEP0_IOTEST: 
    {
      // no control of IO (for test purposes)
      break;
    }
    //+++++++++++++++++++++++++++++++++++++++
    case FsmState::STEP1_IDLE: 
    {
      if (cmdFill && !cmdTarra) FsmGoTo(FsmState::STEP3_WAIT_CMD_FILL); 
      if (cmdTarra) FsmGoTo(FsmState::STEP2_TARRA); 
    }
    break;
    
    //+++++++++++++++++++++++++++++++++++++++
    case FsmState::STEP2_TARRA:    
    {
      // send cmd to ScaleLoop()
      if (firstNewStep)
      {
        tWdFsm.setMs(1000);
        triggerTare=true;
      }
      // cmd completed OK:
      if (triggerTare==false)
        FsmGoTo(FsmState::STEP3_WAIT_CMD_FILL); 
      
      if (tWdFsm.isZero())
      {
        errTimeout=true;
        WawiSrv.println("Error in step tarra scale: timout.");
        FsmGoTo(FsmState::STEP10_ERROR); 
      }
      if (scaleOk==false)
      {
        errScale=true;
        WawiSrv.println("Error in step tarra scale: communication with scale failure.");
        FsmGoTo(FsmState::STEP10_ERROR); 
      }
    }
    break;
    
    //+++++++++++++++++++++++++++++++++++++++
    case FsmState::STEP3_WAIT_CMD_FILL:    
    {
      if (cmdFill) 
        FsmGoTo(FsmState::STEP4_P_FILL_FAST); 
    }
    break;
    
    //+++++++++++++++++++++++++++++++++++++++
    case FsmState::STEP4_P_FILL_FAST:    
    {
      if (firstNewStep) 
      {
        tWdFsm.setMs(tWdFillTimeout); // timeout value for slow and fast filling
        wTargetStep=wActual+wFillBatchFast;
      }

      if (wActual>wFillMax-wFillBatchSlow || wActual>wTargetStep) 
        FsmGoTo(FsmState::STEP5_P_FILL_SLOW); 

      if (tWdFsm.isZero())
      {
        errTimeout=true;
        WawiSrv.println("Error in step fill fast: timeout.");
        FsmGoTo(FsmState::STEP10_ERROR); 
      }
    }
    break;
    
    //+++++++++++++++++++++++++++++++++++++++
    case FsmState::STEP5_P_FILL_SLOW:    
    {
      if (firstNewStep) 
      {
        wTargetStep=wActual+wFillBatchSlow;
      }

      if (wActual>wFillMax || wActual>wTargetStep) 
        FsmGoTo(FsmState::STEP6_WAIT); 

      if (tWdFsm.isZero())
      {
        errTimeout=true;
        WawiSrv.println("Error in step fill slow: timeout.");
        FsmGoTo(FsmState::STEP10_ERROR); 
      }
    }
    break;
    
    //+++++++++++++++++++++++++++++++++++++++
    case FsmState::STEP6_WAIT:    
    {
      if (firstNewStep) 
        tWdFsm.setMs(20000);

      if (tWdFsm.isZero() && wActual<wFillMax) FsmGoTo(FsmState::STEP4_P_FILL_FAST); 
      if (tWdFsm.isZero() && wActual>=wFillMax) FsmGoTo(FsmState::STEP7_WAIT_CMD_EMPTY); 
    }
    break;

    //+++++++++++++++++++++++++++++++++++++++
    case FsmState::STEP7_WAIT_CMD_EMPTY:    
    {
      if (cmdEmpty) FsmGoTo(FsmState::STEP8_P_EMPTY_FAST); 
    }
    break;

    //+++++++++++++++++++++++++++++++++++++++
    case FsmState::STEP8_P_EMPTY_FAST:    
    {
      if (firstNewStep) 
        tWdFsm.setMs(tWdEmptyTimeout);

      if (wActual<5) 
        FsmGoTo(FsmState::STEP9_P_EMTPY_TIMEBASED); 
      
      if (tWdFsm.isZero())
      {
        errTimeout=true;
        WawiSrv.println("Error in step empty fast: timeout.");
        FsmGoTo(FsmState::STEP10_ERROR); 
      }
    }
    break;

    //+++++++++++++++++++++++++++++++++++++++
    case FsmState::STEP9_P_EMTPY_TIMEBASED:    
    {
      if (firstNewStep) tWdFsm.setMs(10000); // 10 seconds
      
      if (tWdFsm.isZero())
        FsmGoTo(FsmState::STEP3_WAIT_CMD_FILL); 
    }
    break;

    //+++++++++++++++++++++++++++++++++++++++
    case FsmState::STEP10_ERROR:    
    {
      if (cmdClrError) 
      {
        FsmGoTo(FsmState::STEP1_IDLE); 
        errTimeout=false;
        errScale=false;
        cmdClrError=false;
      }
    }
    break;

    //+++++++++++++++++++++++++++++++++++++++
    default:
    {
      stepCurrent = FsmState::STEP1_IDLE;
      break;
    }
  }  
  firstNewStep=(stepCurrent!=stepPrev);
  stepPrev=stepCurrent;
  strcpy(stepString,stepStrings[(int) stepCurrent]);
  tWdFsm.loop();
}

//----------------------------------------------------------------
// control I/O based on FSM step
void IoLoop()
{
  ledR=0;
  switch(stepCurrent)
  {
    //+++++++++++++++++++++++++++++++++++++++
    case FsmState::STEP0_IOTEST: 
      // blink fast all leds = IO check
      ledR=blink250;   
      ledY=blink250;   
      ledG=blink250;   
      break;
     
    //+++++++++++++++++++++++++++++++++++++++
     case FsmState::STEP1_IDLE:
     case FsmState::STEP3_WAIT_CMD_FILL:    
     case FsmState::STEP7_WAIT_CMD_EMPTY: 
      // yellow blink is wait for input/user intervention:   
      ledG=0;   
      ledR=0;
      ledY=blink500; 
      pump1Pwm=0;
      pump2Pwm=0;
      break;
    //+++++++++++++++++++++++++++++++++++++++
    case FsmState::STEP4_P_FILL_FAST:    
      pump1Pwm=pump1PwmFast;
      pump2Pwm=0;
      ledG=blink250;   
      ledR=0;
      ledY=0;
      break;
   
    //+++++++++++++++++++++++++++++++++++++++
    case FsmState::STEP5_P_FILL_SLOW:    
      // green slow = pump slow
      pump1Pwm=pump1PwmSlow;
      pump2Pwm=0;
      ledG=blink500;   
      ledR=0;
      ledY=0;
      break;
    
    //+++++++++++++++++++++++++++++++++++++++
    case FsmState::STEP6_WAIT:    
      pump1Pwm=0;
      pump2Pwm=0;
      ledG=1;   
      ledR=0;
      break;
    //+++++++++++++++++++++++++++++++++++++++
    case FsmState::STEP8_P_EMPTY_FAST:    
      pump1Pwm=0;
      pump2Pwm=pump2PwmFast;
      ledG=blink250;   
      ledR=0;
      ledY=0;
      break;

    //+++++++++++++++++++++++++++++++++++++++
    case FsmState::STEP9_P_EMTPY_TIMEBASED:    
      pump1Pwm=0;
      pump2Pwm=pump2PwmSlow;
      ledG=blink500;   
      ledR=0;
      ledY=0;
      break;

    //+++++++++++++++++++++++++++++++++++++++
    case FsmState::STEP10_ERROR:    
      pump1Pwm=0;
      pump2Pwm=0;
      ledG=blink500;   
      ledR=0;
      ledY=0;
      break;

    //+++++++++++++++++++++++++++++++++++++++
    default:
      pump1Pwm=0;
      pump2Pwm=0;
      ledR=250;
      ledY=0;
      ledG=0;
      break;
  }  
  
  // limit pumpxPwm to range 0..255
  pump1Pwm=min(pump1Pwm,255);
  pump2Pwm=min(pump2Pwm,255);

  // send pump value to IO:
  analogWrite(DO_SSR_P1, 255-pump1Pwm);
  analogWrite(DO_SSR_P2, 255-pump2Pwm);
  
  // control the LED's
  digitalWrite(DO_LED_RED,ledR);
  digitalWrite(DO_LED_YELLOW,ledY);
  digitalWrite(DO_LED_GREEN,ledG);
}


//------------------------------------------------------------
// create blinkbits:
void UtilsLoop()
{
  if (tBlink250.isZero())
  {
    tBlink250.setMs(250);
    blink250=!blink250;  
  }
  if (tBlink500.isZero())
  {
    tBlink500.setMs(500);
    blink500=!blink500;  
  }

  tBlink250.loop(); // 250 ms on
  tBlink500.loop(); // 500 ms
}

//------------------------------------------------------------
void setup() 
{
  // init ethernet and WawiLib:
  Ethernet.begin(mac, ipArd, gateway, subnet);
  server.begin();
  WawiSrv.begin(wawiVarDef, server, "MyArduino");

  // init finit State machine:
  FsmGoTo(FsmState::STEP1_IDLE);
  stepPrev=FsmState::STEP0_IOTEST;

  // init scale HX711:
  scale.begin(LOADCELL_DOUT_PIN, LOADCELL_SCK_PIN);
  scale.set_scale(417.f); 

  // init PWM IO:
  pinMode(DO_SSR_P1, OUTPUT);
  pinMode(DO_SSR_P2, OUTPUT);

  // LED IO
  pinMode(DO_LED_RED,OUTPUT);
  pinMode(DO_LED_YELLOW,OUTPUT);
  pinMode(DO_LED_GREEN,OUTPUT);

  // reduce PWM frequency to 30 hz
  TCCR2B = TCCR2B & B11111000 | B00000111; // D9 to 30 Hz
  TCCR4B = TCCR4B & B11111000 | B00000101; // D8 to 30 Hz
}
//------------------------------------------------------------
void loop()
{
  // read weight of scale:
  ScaleLoop();
  // finite state machine loop:
  FsmLoop();
  // sent results to IO:
  IoLoop();
  // varia (blinkbits ...)
  UtilsLoop();
  // wawilib communication update:
  WawiSrv.loop();
}
THE ProgramArduino
//------------------------------------------------------------
#include <WawiEthernet.h>
// the media access control (ethernet hardware) address for the shield:
byte mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0x88 }; 

// the IP address of Arduino shield:
byte ipArd[] = { 192, 168, 0, 88 };    

// the communication port Arduino side for WawiLib communication
unsigned int port = 49152;

// the router's gateway address:
byte gateway[] = { 10, 0, 0, 1 };

// the network subnet:
byte subnet[] = { 255, 255, 225, 0 };

// declare communication object:
EthernetServer server = EthernetServer(port);

// WawiLib communications object:
WawiEthernet WawiSrv;

//------------------------------------------------------------
// HX711 scale load cell:
#include "HX711.h" // credits to Bogdan Necula
HX711 scale;
const int LOADCELL_DOUT_PIN = 6; // HX711 data pin
const int LOADCELL_SCK_PIN = 7;  // HX711 clock pin
bool triggerTare;

#include "timer.h"
WawiTimer tWdScale; // watchdog timer scale communication
bool scaleOk=false; // scale communication status

//------------------------------------------------------------
// Finite state machine variables:
enum class FsmState       { STEP0_IOTEST=0, STEP1_IDLE=1, STEP2_TARRA=2, STEP3_WAIT_CMD_FILL=3, STEP4_P_FILL_FAST=4, STEP5_P_FILL_SLOW=5, STEP6_WAIT=6, STEP7_WAIT_CMD_EMPTY=7, STEP8_P_EMPTY_FAST=8, STEP9_P_EMTPY_TIMEBASED=9, STEP10_ERROR=10 };
const char* stepStrings[] { "Step 0: Iotest", "Step 1: Idle", "Step 2: Tarra scale", "Step 3: Await cmd fill", "Step 4: Fill fast", "Step 5: Fill slow", "Step 6: Wait time", "Step 7: Await cmd empty", "Step 8: Empty fast", "Step 9: Empty time based", "Step 10: Error" };
char stepString[30];
FsmState stepCurrent,stepPrev;
bool firstNewStep;

// commands for finite state machine:
bool cmdTarra;
bool cmdFill;
bool cmdEmpty;
bool cmdClrError;

// status variables
bool errScale, errTimeout;

// fill parameters
float wFillMax=100; 
float wFillBatchFast=10; 
float wFillBatchSlow=5; 
float wActual, wTargetStep;

unsigned long long tWdFillTimeout=30l*1000l; // 30 sec
unsigned long long tWdEmptyTimeout=300l*1000l; // 300 sec

WawiTimer tWdFsm; // watchdog timers for FSM time-out
bool ledR=0,ledY=0,ledG=0; // LED control
//------------------------------------------------------------
// digital outputs connected to solid state relays 1 and 2:
#define DO_SSR_P1 8
#define DO_SSR_P2 9

// PWM ratio control pump operation:
unsigned short pump1PwmFast=255;
unsigned short pump2PwmFast=255;
unsigned short pump1PwmSlow=128;
unsigned short pump2PwmSlow=128;
// actual value of SSR PWM setpoints:
unsigned short pump1Pwm=0;
unsigned short pump2Pwm=0;

//------------------------------------------------------------
// digital outputs connected to solid state relays 1 and 2:
#define DO_LED_RED 49
#define DO_LED_YELLOW 47
#define DO_LED_GREEN 45

// blinker bit helper timers (software timers):
WawiTimer tBlink250; // 250 ms on
WawiTimer tBlink500; // 500 ms
// blinking bits:
bool blink250;
bool blink500;

//------------------------------------------------------------
// make variables of interest known to WawiLib:
// this function is used in WawiSrv.begin(....)
void wawiVarDef()
{
  WawiSrv.wawiVar(triggerTare);
  WawiSrv.wawiVar(scaleOk);
  WawiSrv.wawiVar(tWdScale.m_act);
  
  WawiSrv.wawiVar(cmdTarra);
  WawiSrv.wawiVar(cmdFill);
  WawiSrv.wawiVar(cmdEmpty);
  WawiSrv.wawiVar(cmdClrError);
  
  WawiSrv.wawiVar(errScale);
  WawiSrv.wawiVar(errTimeout);
 
  WawiSrv.wawiVar(wFillMax);
  WawiSrv.wawiVar(wFillBatchFast);
  WawiSrv.wawiVar(wFillBatchSlow);
  WawiSrv.wawiVar(wActual);
  WawiSrv.wawiVar(wTargetStep);

  WawiSrv.wawiVar(tWdFillTimeout);
  WawiSrv.wawiVar(tWdEmptyTimeout);
  WawiSrv.wawiVar(tWdFsm);

  WawiSrv.wawiVar(stepCurrent);
  WawiSrv.wawiVar(stepPrev);
  WawiSrv.wawiVarArray(stepString);
  WawiSrv.wawiVar(stepString);
  WawiSrv.wawiVar(tWdFsm.m_act);

  WawiSrv.wawiVar(pump1PwmFast);
  WawiSrv.wawiVar(pump2PwmFast);
  WawiSrv.wawiVar(pump1PwmSlow);
  WawiSrv.wawiVar(pump2PwmSlow);
  WawiSrv.wawiVar(pump1Pwm);
  WawiSrv.wawiVar(pump2Pwm);

  WawiSrv.wawiVar(tBlink250.m_act);
  WawiSrv.wawiVar(tBlink500.m_act);
  WawiSrv.wawiVar(ledR);
  WawiSrv.wawiVar(ledG);
  WawiSrv.wawiVar(ledY);
}

//------------------------------------------------------------
// set finite state machine to other step:
void FsmGoTo(FsmState newStep) 
{
  stepCurrent = newStep;
  WawiSrv.println((String) "New step = "+ stepString + " (current weight=" +(String) wActual+")");
}

//------------------------------------------------------------
// processing of scale (0..5KG) communication:
void ScaleLoop()
{
  // read weight, if available:
  if (scale.is_ready())
  {
    // read weight
    wActual = scale.get_units(1);
    // set watchdog:    
    tWdScale.setMs(1000);
    // process tare cmd if required:
    if (triggerTare)
    {
      // reset cmd to Tare:
      triggerTare=0;
      scale.tare();  
      WawiSrv.print("Scale tare operation completed. ");
    }
  }  
  
  // process watchdog status:
  if (scaleOk != !tWdScale.isZero())
  {
    scaleOk = !tWdScale.isZero();
    if (scaleOk)
      WawiSrv.println("Weight processing restarted, scale ok. ");
    else
      WawiSrv.println("Weight processing stopped, scale communication fault. ");
  }

  // loop part:
  tWdScale.loop();
}

//------------------------------------------------------------
// Finite state machine:
void FsmLoop() 
{
  switch(stepCurrent)
  {
    //+++++++++++++++++++++++++++++++++++++++
    case FsmState::STEP0_IOTEST: 
    {
      // no control of IO (for test purposes)
      break;
    }
    //+++++++++++++++++++++++++++++++++++++++
    case FsmState::STEP1_IDLE: 
    {
      if (cmdFill && !cmdTarra) FsmGoTo(FsmState::STEP3_WAIT_CMD_FILL); 
      if (cmdTarra) FsmGoTo(FsmState::STEP2_TARRA); 
    }
    break;
    
    //+++++++++++++++++++++++++++++++++++++++
    case FsmState::STEP2_TARRA:    
    {
      // send cmd to ScaleLoop()
      if (firstNewStep)
      {
        tWdFsm.setMs(1000);
        triggerTare=true;
      }
      // cmd completed OK:
      if (triggerTare==false)
        FsmGoTo(FsmState::STEP3_WAIT_CMD_FILL); 
      
      if (tWdFsm.isZero())
      {
        errTimeout=true;
        WawiSrv.println("Error in step tarra scale: timout.");
        FsmGoTo(FsmState::STEP10_ERROR); 
      }
      if (scaleOk==false)
      {
        errScale=true;
        WawiSrv.println("Error in step tarra scale: communication with scale failure.");
        FsmGoTo(FsmState::STEP10_ERROR); 
      }
    }
    break;
    
    //+++++++++++++++++++++++++++++++++++++++
    case FsmState::STEP3_WAIT_CMD_FILL:    
    {
      if (cmdFill) 
        FsmGoTo(FsmState::STEP4_P_FILL_FAST); 
    }
    break;
    
    //+++++++++++++++++++++++++++++++++++++++
    case FsmState::STEP4_P_FILL_FAST:    
    {
      if (firstNewStep) 
      {
        tWdFsm.setMs(tWdFillTimeout); // timeout value for slow and fast filling
        wTargetStep=wActual+wFillBatchFast;
      }

      if (wActual>wFillMax-wFillBatchSlow || wActual>wTargetStep) 
        FsmGoTo(FsmState::STEP5_P_FILL_SLOW); 

      if (tWdFsm.isZero())
      {
        errTimeout=true;
        WawiSrv.println("Error in step fill fast: timeout.");
        FsmGoTo(FsmState::STEP10_ERROR); 
      }
    }
    break;
    
    //+++++++++++++++++++++++++++++++++++++++
    case FsmState::STEP5_P_FILL_SLOW:    
    {
      if (firstNewStep) 
      {
        wTargetStep=wActual+wFillBatchSlow;
      }

      if (wActual>wFillMax || wActual>wTargetStep) 
        FsmGoTo(FsmState::STEP6_WAIT); 

      if (tWdFsm.isZero())
      {
        errTimeout=true;
        WawiSrv.println("Error in step fill slow: timeout.");
        FsmGoTo(FsmState::STEP10_ERROR); 
      }
    }
    break;
    
    //+++++++++++++++++++++++++++++++++++++++
    case FsmState::STEP6_WAIT:    
    {
      if (firstNewStep) 
        tWdFsm.setMs(20000);

      if (tWdFsm.isZero() && wActual<wFillMax) FsmGoTo(FsmState::STEP4_P_FILL_FAST); 
      if (tWdFsm.isZero() && wActual>=wFillMax) FsmGoTo(FsmState::STEP7_WAIT_CMD_EMPTY); 
    }
    break;

    //+++++++++++++++++++++++++++++++++++++++
    case FsmState::STEP7_WAIT_CMD_EMPTY:    
    {
      if (cmdEmpty) FsmGoTo(FsmState::STEP8_P_EMPTY_FAST); 
    }
    break;

    //+++++++++++++++++++++++++++++++++++++++
    case FsmState::STEP8_P_EMPTY_FAST:    
    {
      if (firstNewStep) 
        tWdFsm.setMs(tWdEmptyTimeout);

      if (wActual<5) 
        FsmGoTo(FsmState::STEP9_P_EMTPY_TIMEBASED); 
      
      if (tWdFsm.isZero())
      {
        errTimeout=true;
        WawiSrv.println("Error in step empty fast: timeout.");
        FsmGoTo(FsmState::STEP10_ERROR); 
      }
    }
    break;

    //+++++++++++++++++++++++++++++++++++++++
    case FsmState::STEP9_P_EMTPY_TIMEBASED:    
    {
      if (firstNewStep) tWdFsm.setMs(10000); // 10 seconds
      
      if (tWdFsm.isZero())
        FsmGoTo(FsmState::STEP3_WAIT_CMD_FILL); 
    }
    break;

    //+++++++++++++++++++++++++++++++++++++++
    case FsmState::STEP10_ERROR:    
    {
      if (cmdClrError) 
      {
        FsmGoTo(FsmState::STEP1_IDLE); 
        errTimeout=false;
        errScale=false;
        cmdClrError=false;
      }
    }
    break;

    //+++++++++++++++++++++++++++++++++++++++
    default:
    {
      stepCurrent = FsmState::STEP1_IDLE;
      break;
    }
  }  
  firstNewStep=(stepCurrent!=stepPrev);
  stepPrev=stepCurrent;
  strcpy(stepString,stepStrings[(int) stepCurrent]);
  tWdFsm.loop();
}

//----------------------------------------------------------------
// control I/O based on FSM step
void IoLoop()
{
  ledR=0;
  switch(stepCurrent)
  {
    //+++++++++++++++++++++++++++++++++++++++
    case FsmState::STEP0_IOTEST: 
      // blink fast all leds = IO check
      ledR=blink250;   
      ledY=blink250;   
      ledG=blink250;   
      break;
     
    //+++++++++++++++++++++++++++++++++++++++
     case FsmState::STEP1_IDLE:
     case FsmState::STEP3_WAIT_CMD_FILL:    
     case FsmState::STEP7_WAIT_CMD_EMPTY: 
      // yellow blink is wait for input/user intervention:   
      ledG=0;   
      ledR=0;
      ledY=blink500; 
      pump1Pwm=0;
      pump2Pwm=0;
      break;
    //+++++++++++++++++++++++++++++++++++++++
    case FsmState::STEP4_P_FILL_FAST:    
      pump1Pwm=pump1PwmFast;
      pump2Pwm=0;
      ledG=blink250;   
      ledR=0;
      ledY=0;
      break;
   
    //+++++++++++++++++++++++++++++++++++++++
    case FsmState::STEP5_P_FILL_SLOW:    
      // green slow = pump slow
      pump1Pwm=pump1PwmSlow;
      pump2Pwm=0;
      ledG=blink500;   
      ledR=0;
      ledY=0;
      break;
    
    //+++++++++++++++++++++++++++++++++++++++
    case FsmState::STEP6_WAIT:    
      pump1Pwm=0;
      pump2Pwm=0;
      ledG=1;   
      ledR=0;
      break;
    //+++++++++++++++++++++++++++++++++++++++
    case FsmState::STEP8_P_EMPTY_FAST:    
      pump1Pwm=0;
      pump2Pwm=pump2PwmFast;
      ledG=blink250;   
      ledR=0;
      ledY=0;
      break;

    //+++++++++++++++++++++++++++++++++++++++
    case FsmState::STEP9_P_EMTPY_TIMEBASED:    
      pump1Pwm=0;
      pump2Pwm=pump2PwmSlow;
      ledG=blink500;   
      ledR=0;
      ledY=0;
      break;

    //+++++++++++++++++++++++++++++++++++++++
    case FsmState::STEP10_ERROR:    
      pump1Pwm=0;
      pump2Pwm=0;
      ledG=blink500;   
      ledR=0;
      ledY=0;
      break;

    //+++++++++++++++++++++++++++++++++++++++
    default:
      pump1Pwm=0;
      pump2Pwm=0;
      ledR=250;
      ledY=0;
      ledG=0;
      break;
  }  
  
  // limit pumpxPwm to range 0..255
  pump1Pwm=min(pump1Pwm,255);
  pump2Pwm=min(pump2Pwm,255);

  // send pump value to IO:
  analogWrite(DO_SSR_P1, 255-pump1Pwm);
  analogWrite(DO_SSR_P2, 255-pump2Pwm);
  
  // control the LED's
  digitalWrite(DO_LED_RED,ledR);
  digitalWrite(DO_LED_YELLOW,ledY);
  digitalWrite(DO_LED_GREEN,ledG);
}


//------------------------------------------------------------
// create blinkbits:
void UtilsLoop()
{
  if (tBlink250.isZero())
  {
    tBlink250.setMs(250);
    blink250=!blink250;  
  }
  if (tBlink500.isZero())
  {
    tBlink500.setMs(500);
    blink500=!blink500;  
  }

  tBlink250.loop(); // 250 ms on
  tBlink500.loop(); // 500 ms
}

//------------------------------------------------------------
void setup() 
{
  // init ethernet and WawiLib:
  Ethernet.begin(mac, ipArd, gateway, subnet);
  server.begin();
  WawiSrv.begin(wawiVarDef, server, "MyArduino");

  // init finit State machine:
  FsmGoTo(FsmState::STEP1_IDLE);
  stepPrev=FsmState::STEP0_IOTEST;

  // init scale HX711:
  scale.begin(LOADCELL_DOUT_PIN, LOADCELL_SCK_PIN);
  scale.set_scale(417.f); 

  // init PWM IO:
  pinMode(DO_SSR_P1, OUTPUT);
  pinMode(DO_SSR_P2, OUTPUT);

  // LED IO
  pinMode(DO_LED_RED,OUTPUT);
  pinMode(DO_LED_YELLOW,OUTPUT);
  pinMode(DO_LED_GREEN,OUTPUT);

  // reduce PWM frequency to 30 hz
  TCCR2B = TCCR2B & B11111000 | B00000111; // D9 to 30 Hz
  TCCR4B = TCCR4B & B11111000 | B00000101; // D8 to 30 Hz
}
//------------------------------------------------------------
void loop()
{
  // read weight of scale:
  ScaleLoop();
  // finite state machine loop:
  FsmLoop();
  // sent results to IO:
  IoLoop();
  // varia (blinkbits ...)
  UtilsLoop();
  // wawilib communication update:
  WawiSrv.loop();
}

Schematics

SSR power section
Fig 3 imduyrwbzz

Comments

Similar projects you might like

Arduino GrowBox Controller

Project tutorial by Michele Valentini

  • 34,864 views
  • 9 comments
  • 70 respects

Submersible Pump

Project tutorial by Jelle

  • 3,628 views
  • 0 comments
  • 11 respects

PLC Training Center

Project tutorial by saifalikabi

  • 35,075 views
  • 1 comment
  • 38 respects

6DOF Robotic Arm

Project showcase by Danny Van den Heuvel

  • 26,614 views
  • 4 comments
  • 48 respects

pH Dosing Pump

by Atlas Scientific

  • 21,402 views
  • 5 comments
  • 48 respects

Dimming Lights with PWM using Push Button

Project tutorial by Phuong Vo

  • 17,188 views
  • 4 comments
  • 18 respects
Add projectSign up / Login