/*
Automated Dust collector control
Dust collector has a RF 433MHz remote control by Sonoff. The RF relay installed on Dust collector simply switches the motor on/off using RC button 1.
To the dust collection system added six holes sliding blast gate.
This program dedicated to remotely (by existing RF 433MHz RC) control six holes sliding blast gate.
How it works?
At Arduino startup the system finds the zero point, i.e. rotates the stepper motor clockwise to the limit (Zero) switch.
When limit switch triggered, the motor rotates to the opposite direction until bounce off limit switch.
Next, it waits for commands from the remote control.
When button pressed on RC a stepper motor moves a blast gate to the position associated with the button
and switch on a dust collector motor (if not already switched on) by sending RF command to Sonoff relay (simulating button 1 press).
Buttons 1 and 8 only works for dust collector motor switch on/off.
For button 1 only dust collector motor state changed as RF relay installed on Dust collector reacts on this button.
For button 8 the program imitates button 1 action by sending RF command to switch on/off a dust collector motor. Also changes a dust collector motor state.
For Arduino simulation used online tool wokwi.com. As a simulator do not have a RF RC component, so used IR RC.
You need to define _IRremote_h_ if simulator used. In real world need to define _RCSwitch_h_.
For motor control AccelStepper lib is used.
In some cases AccelStepper takes unpredictable steps/behavior (I do not know why?, e.g. real traveled distance from hole 1 to hole 6 in one shot is not equal to distance when moving 1-2-3-4-5-6 with stops),
so program try to set end positions any time when limit switches triggered.
Also there is added 7-segment LED display to indicate a current blast gate hole.
Karolis Stolys
@stolysstudio
[email protected]
*/
//#ifndef _IRremote_h_
#define _IRremote_h_
//#define _RCSwitch_h_
#if defined _IRremote_h_
#include <IRremote.h>
#else
#include <RCSwitch.h>
#endif
#include <AccelStepper.h>
#define DIRECTION_CCW -1
#define DIRECTION_CW 1
#define HOMING 0
#define IDLE 1
#define RECEIVED 2
#define PREPARINGMOVE 3
#define MOVING 4
#define LIMITSWITCHPRESSED 5
#define MOVED 6
#define COMPLETED 7
/*
Zero switch variables
*/
unsigned int commandStatus = HOMING;
unsigned int commandStatusOnInterupt;
const int limitSwitchLeftPin = A2;
const int limitSwitchRightPin = A1;
// Booleans for input states
volatile bool A2_state = LOW;
volatile bool A1_state = LOW;
boolean islimitSwitchPressed = false;
/*
74HC595 Shift Register with 7-segment LED display variables
Count in hex from 0-F and display on 7-segment Common Cathode LED display
*/
// Define Connections to 74HC595
// ST_CP pin 12
const int latchPin = 6;
// SH_CP pin 11
const int clockPin = 5;
// DS pin 14
const int dataPin = 4;
// Patterns for characters 0,1,2,3,4,5,6,7,8,9,A,b,C,d,E,F
int datArray[17] = {B11111100, B01100000, B11011010, B11110010, B01100110, B10110110, B10111110, B11100000, B11111110, B11110110, B11101110, B00111110, B10011100, B01111010, B10011110, B10001110, B01101110};
int datArrayRot[6] = {B11000000, B01100000, B00110000, B00011000, B00001100, B10000100};
unsigned long millisDisplay;
byte symbolToDisplay;
/*
Stepper motor variables
*/
// change this to the number of steps on your motor
//--#define STEPS 200
// create an instance of the stepper class, specifying
// the number of steps of the motor and the pins it's
// attached to
//-Stepper stepper(STEPS, 8, 9, 10, 11);
// Define a stepper and the pins it will use
// Closed loop stepper motor NEMA24 used with CL57T driver
const int stepPullPin = 8;
const int stepDirPin = 9;
const int stepEnaPin = 10;
//AccelStepper stepper(AccelStepper::FULL4WIRE, 8, 9, 10, 11); // Defaults to AccelStepper::FULL4WIRE (4 pins) on 2, 3, 4, 5
AccelStepper stepper(AccelStepper::DRIVER, stepPullPin, stepDirPin);
const int StepMultiplier = 1; // 200 steps
const int stepsPerRevolution = 200*StepMultiplier;
const int stepsMaxSpeed = 1600*StepMultiplier;
const int stepsZeroSpeed = 100*StepMultiplier;
const int stepsAcceleration = 160*StepMultiplier;
/*
RF 433Mhz / IR RC variables
*/
const int receivePin = 2; // (RF Receiver on interrupt 0 => that is pin #2) as pin 2 and pin 3 only have interruption
const int transmitPin = 12; // RF transmit on pin 12
#if defined _IRremote_h_
// Key codes on IR RC on wokwi.com
const int keyLenght = 24;
const long key1 = 48; // key 1 code. Key 1 used to switch on/off a dust collector. Dust collector receiver also reacts on this button.
const long key2 = 24; // key 2 code.
const long key3 = 122; // key 3 code.
const long key4 = 16; // key 4 code.
const long key5 = 56; // key 5 code.
const long key6 = 90; // key 6 code.
const long key7 = 66; // key 7 code.
const long key8 = 74; // Key 8 code. Key 8 used to switch on/off a dust collector. Only works from Arduino. Is needed if RC is held in hand in turn over position.
IRrecv mySwitch(receivePin);
#else
// Key codes from Sonoff eight buttons RM433 remote control
const int keyLenght = 24;
const long key1 = 5801144; // key 1 code. Key 1 used to switch on/off a dust collector. Dust collector receiver also reacts on this button.
const long key2 = 5801148; // key 2 code.
const long key3 = 5801140; // key 3 code.
const long key4 = 5801145; // key 4 code.
const long key5 = 5801138; // key 5 code.
const long key6 = 5801141; // key 6 code.
const long key7 = 5801137; // key 7 code.
const long key8 = 5801139; // Key 8 code. Key 8 used to switch on/off a dust collector. Only works from Arduino. Is needed if RC is held in hand in turn over position.
RCSwitch mySwitch = RCSwitch();
#endif
const long hole0Position =0; // Stepper motor position to Zero point
const long hole1Position =2*StepMultiplier; // Stepper motor position for dust collector sliding blast gate hole 1
const long hole2Position =6632*StepMultiplier; // Stepper motor position for dust collector sliding blast gate hole 2
const long hole3Position =13262*StepMultiplier; // Stepper motor position for dust collector sliding blast gate hole 3
const long hole4Position =19892*StepMultiplier; // Stepper motor position for dust collector sliding blast gate hole 3
const long hole5Position =26522*StepMultiplier; // Stepper motor position for dust collector sliding blast gate hole 5
const long hole6Position =33152*StepMultiplier; // Stepper motor position for dust collector sliding blast gate hole 6
boolean dustCollectorOn = 0; // Dust Collector mode. We need to know is a dust collector switched on (1) or off (0)
ISR (PCINT1_vect)
{
islimitSwitchPressed = true;
if (commandStatus != LIMITSWITCHPRESSED) {
commandStatusOnInterupt = commandStatus;
}
commandStatus = LIMITSWITCHPRESSED;
}
void displayPosition() {
if (commandStatus == MOVED) {
displaySymbol(symbolToDisplay);
}
}
void displaySymbol(byte symbol) {
// ST_CP LOW to keep LEDs from changing while reading serial data
digitalWrite(latchPin, LOW);
// Shift out the bits
shiftOut(dataPin, clockPin, MSBFIRST, symbol); // Common cathode - No invert, Common anode - invert ~symbol
// ST_CP HIGH change LEDs
digitalWrite(latchPin, HIGH);
}
void displayRotation() {
static int positionDisplay = 0;
if (commandStatus == MOVING || commandStatus == HOMING) {
if (millis() - millisDisplay > 400) {
displaySymbol(datArrayRot[positionDisplay]);
millisDisplay = millis();
positionDisplay++;
if (positionDisplay > 6) {
positionDisplay = 0;
}
}
}
}
void bounceOff() {
static long positionAfterBounce;
if (commandStatus == LIMITSWITCHPRESSED) {
stepper.stop();
stepper.enableOutputs();
delay(10);
A2_state = digitalRead(limitSwitchLeftPin);
A1_state = digitalRead(limitSwitchRightPin);
// Check status again
if (A2_state == HIGH || A1_state == HIGH) {
islimitSwitchPressed = true;
}
else {
islimitSwitchPressed = false;
}
if (islimitSwitchPressed) {
if (A2_state == HIGH) {
stepper.setSpeed(DIRECTION_CCW * stepsZeroSpeed);
stepper.move(DIRECTION_CCW * stepsPerRevolution); // Move until bounce off
positionAfterBounce = hole6Position;
}
else if (A1_state == HIGH) {
stepper.setSpeed(DIRECTION_CW * stepsZeroSpeed);
stepper.move(DIRECTION_CW * stepsPerRevolution); // Move until bounce off
positionAfterBounce = hole0Position;
}
else {
islimitSwitchPressed = false;
}
if (stepper.distanceToGo() != 0 && islimitSwitchPressed) {
stepper.runSpeed();
delay(10);
}
}
if (!islimitSwitchPressed) {
stepper.stop();
stepper.setCurrentPosition(positionAfterBounce);
commandStatus = commandStatusOnInterupt;
Serial.print("Current position after bounce off ");
Serial.println(stepper.currentPosition());
}
}
}
void moveToZero() {
commandStatus = HOMING;
Serial.println("Moving to zero");
stepper.enableOutputs();
delay(50);
stepper.setSpeed(stepsZeroSpeed);
while (!islimitSwitchPressed) {
stepper.move(DIRECTION_CCW*stepsZeroSpeed*StepMultiplier); // Move one step until reach Zero point
stepper.runSpeed();
displayRotation();
}
stepper.stop(); // Stop as fast as possible: sets new target
stepper.setCurrentPosition(hole0Position);
Serial.println("Reached the limit switch trigger position.");
while (islimitSwitchPressed) {
commandStatus = LIMITSWITCHPRESSED;
bounceOff();
}
stepper.setCurrentPosition(hole0Position);
stepper.disableOutputs();
commandStatus = MOVED;
symbolToDisplay = datArray[16];
displayPosition();
commandStatus = IDLE;
delay(50);
Serial.print("Reached the zero position ");
Serial.println(stepper.currentPosition());
Serial.println("Moved to zero");
}
void runMotor() {
if (commandStatus == MOVING) {
if (stepper.distanceToGo() != 0) {
if (!islimitSwitchPressed) {
stepper.run();
}
else {
stepper.stop(); // Stop as fast as possible: sets new target
Serial.print("Current position after limit switch stop ");
Serial.println(stepper.currentPosition());
commandStatus = LIMITSWITCHPRESSED;
}
}
else {
stepper.stop(); // Stop as fast as possible: sets new target
commandStatus = MOVED;
Serial.print("Current position after run ");
Serial.println(stepper.currentPosition());
delay(300);
stepper.disableOutputs();
}
}
}
void moveToPosition(long nPosition) {
if (commandStatus == PREPARINGMOVE) {
Serial.print("Current position is ");
Serial.println(stepper.currentPosition());
Serial.print("Start to move to the position ");
Serial.println(nPosition);
stepper.enableOutputs();
delay(50);
stepper.moveTo(nPosition);
Serial.print("Distance ToGo before run loop ");
Serial.println(stepper.distanceToGo());
}
}
void button1Action() {
if (commandStatus == RECEIVED) {
Serial.println("Button 1 pressed.");
Serial.print("Status before: ");
Serial.println(dustCollectorOn);
dustCollectorOn = !dustCollectorOn; // change a status of Dust Collector
Serial.print("Status after: ");
Serial.println(dustCollectorOn);
commandStatus = COMPLETED;
delay(300);
}
}
void button8Action() {
if (commandStatus == RECEIVED) {
Serial.println("Button 8 pressed.");
delay(100);
Serial.println("Sending Dust Collector ON/OFF command");
// Send a command to turn on/off Dust Collector motor
#if defined _IRremote_h_
delay(10);
#else
mySwitch.send(key1, keyLenght);
#endif
Serial.print("Status before: ");
Serial.println(dustCollectorOn);
dustCollectorOn = !dustCollectorOn; // change a status of Dust Collector
Serial.print("Status after: ");
Serial.println(dustCollectorOn);
commandStatus = COMPLETED;
delay(300);
}
}
void button2to7Action(int nKeyCode) {
if (commandStatus == RECEIVED) {
// do something different depending on the nKeyCode value:
commandStatus = PREPARINGMOVE;
switch (nKeyCode) {
case key2:
Serial.println("Button 2 moves to blast gate position 1");
moveToPosition(hole1Position);
symbolToDisplay = datArray[1];
break;
case key3:
Serial.println("Button 3 moves to blast gate position 2");
moveToPosition(hole2Position);
symbolToDisplay = datArray[2];
break;
case key4:
Serial.println("Button 4 moves to blast gate position 3");
moveToPosition(hole3Position);
symbolToDisplay = datArray[3];
break;
case key5:
Serial.println("Button 5 moves to blast gate position 4");
moveToPosition(hole4Position);
symbolToDisplay = datArray[4];
break;
case key6:
Serial.println("Button 6 moves to blast gate position 5");
moveToPosition(hole5Position);
symbolToDisplay = datArray[5];
break;
case key7:
Serial.println("Button 7 moves to blast gate position 6");
moveToPosition(hole6Position);
symbolToDisplay = datArray[6];
break;
}
commandStatus = MOVING;
}
}
void turnOnDustCollector() {
if (commandStatus == MOVED) {
Serial.print("Status before: ");
Serial.println(dustCollectorOn);
if (dustCollectorOn == 0) {
Serial.println("Sending Dust Collector ON command");
// Send a command to turn on Dust Collector motor
#if defined _IRremote_h_
delay(10);
#else
mySwitch.send(key1, keyLenght);
#endif
dustCollectorOn = !dustCollectorOn; // change a status of Dust Collector
}
Serial.print("Status after: ");
Serial.println(dustCollectorOn);
commandStatus = COMPLETED;
delay(300);
}
}
void processingControlCommand () {
if (commandStatus == IDLE) {
#if defined _IRremote_h_
// Checks received an IR signal
if (mySwitch.decode()) {
Serial.print("Received ");
Serial.println(mySwitch.decodedIRData.command);
commandStatus = RECEIVED;
if (mySwitch.decodedIRData.command == key1) {
button1Action();
} else if (mySwitch.decodedIRData.command == key8) {
button8Action();
} else {
button2to7Action(mySwitch.decodedIRData.command);
}
}
#else
if (mySwitch.available()) {
Serial.print("Received ");
Serial.print(mySwitch.getReceivedValue());
Serial.print(" / ");
Serial.print(mySwitch.getReceivedBitlength());
Serial.print(" bit ");
Serial.print(" Protocol: ");
Serial.print(mySwitch.getReceivedProtocol());
Serial.print(" Delay: ");
Serial.println(mySwitch.getReceivedDelay());
commandStatus = RECEIVED;
if (mySwitch.getReceivedValue() == key1) {
button1Action();
} else if (mySwitch.getReceivedValue() == key8) {
button8Action();
} else {
button2to7Action(mySwitch.getReceivedValue());
}
}
#endif
}
}
void resumeControlCommand () {
if (commandStatus == COMPLETED) {
#if defined _IRremote_h_
mySwitch.resume(); // Receive the next value
#else
mySwitch.resetAvailable();
#endif
stepper.disableOutputs();
commandStatus = IDLE;
}
}
void setup() {
Serial.begin(9600);
// Setup pins as Outputs for 74HC595 Shift Register
pinMode(latchPin, OUTPUT);
pinMode(clockPin, OUTPUT);
pinMode(dataPin, OUTPUT);
pinMode(limitSwitchRightPin, INPUT);
pinMode(limitSwitchLeftPin, INPUT);
// Enable PCIE1 Bit2 = 1 (Port C)
PCICR |= B00000010;
// Enable PCINT9 & PCINT10 (Pins A1 & A2)
PCMSK1 |= B00000110;
#if defined _IRremote_h_
Serial.println("Enabling Receive");
mySwitch.enableIRIn(); // Start the receiver
Serial.println("Receive enabled");
#else
Serial.println("Enabling Receive");
mySwitch.enableReceive(digitalPinToInterrupt(receivePin)); // Receiver on interrupt 0 => that is pin #2
Serial.println("Receive enabled");
Serial.println("Enabling Transmit");
mySwitch.enableTransmit(transmitPin); // Transmitter is connected to Arduino Pin #12
// Optional set protocol (default is 1, will work for most outlets)
// mySwitch.setProtocol(2);
//mySwitch.setPulseLength(320);
//mySwitch.setRepeatTransmit(15);
Serial.println("Transmit enabled");
#endif
// set the speed of the motor to stepsMaxSpeed
Serial.println("Seting motor driver EnablePin (ENA-)");
stepper.setEnablePin(stepEnaPin);
#if defined _IRremote_h_
stepper.setPinsInverted(true, false, true);
#endif
Serial.println("Seting motor max speed");
stepper.setMaxSpeed(stepsMaxSpeed);
Serial.print("Motor max speed ");
Serial.println(stepsMaxSpeed);
stepper.setAcceleration(stepsAcceleration);
millisDisplay = millis();
A2_state = digitalRead(limitSwitchLeftPin);
A1_state = digitalRead(limitSwitchRightPin);
if (A2_state == HIGH || A1_state == HIGH) islimitSwitchPressed = true;
moveToZero();
}
void loop() {
processingControlCommand();
runMotor();
bounceOff();
displayRotation();
displayPosition();
turnOnDustCollector();
resumeControlCommand();
}