Automated Blinds Part 3: Firmware Building Blocks
As promised, I got through some of the more basic firmware to control the motors! I actually ended up spending a lot more time chasing down bugs than I expected. I spent probably 2 hours trying to figure out why I couldn’t run my steppers backwards, convincing myself it was because I didn’t know the AccelStepper library well enough. Eventually I just swapped in a new stepper driver module and sure enough that did the trick. I’m guessing I shorted something on the board when I was setting the motor current and it broke the direction pin. Super frustrating to have spent that much time debugging a hardware issue but such is life.
Once I got the driver working properly, the code works pretty well! On initial boot, the blinds do a homing procedure where they drive up until the hit a limit switch, then back off until the limit switch triggers back LOW. Once homed, you can drive the blinds to any position between 0 steps (fully closed) and 52000 steps (fully open) using the serial monitor. I’ll do my best to describe what’s happening below.
First, we bring in the AccelStepper library (documentation here) which allows some pretty useful control functions and position tracking. Then we setup the proper output pin variables for the step and direction pins. Followed by the creation of the endstop and creating some loop status tracking variables.
#include "AccelStepper.h"
// Define stepper motor connections and motor interface type (A4988)
#define dirPin 6
#define stepPin 7
#define motorInterfaceType 1
// Create motor instances
AccelStepper stepperA = AccelStepper(motorInterfaceType, stepPin, dirPin);
// Define endstops
#define endstopA 5 // Pin 5 connected to endstop
// Create travel variables
long travelA;
int move_doneA=1; // Used to check if move is completed
long home_pos=-1; // Used to Home at start
Next, in the setup loop, we start the serial interface and setup the endstop pin as an INPUT_PULLUP which allows us to read the state as a 1 or 0. The max speed and acceleration is then set for homing, which is a little lower than the actual control loop in order to make the homing measurements more accurate.
To do the actual homing, the motor is driven CCW until the endstop is triggered, at which point the motor is driven back CW until the endstop trigger reads low again. Once it does, we set the current position to 0 and create an interface to enter new position targets.
void setup() {
Serial.begin(9600); // Start the Serial monitor
delay(1000); //
pinMode(endstopA, INPUT_PULLUP);
//Set homing speeds
stepperA.setMaxSpeed(1000); // Set Max Speed of Stepper
stepperA.setAcceleration(5000); // Set Acceleration of Stepper
// Start Homing
Serial.println("Stepper is Homing...");
while (digitalRead(endstopA) == 1) { // Move up until endstop hit
stepperA.moveTo(home_pos); // Move motor to home_pause
home_pos--; // Reduce home_pos by 1
stepperA.run(); // Run step
}
Serial.println("Hit!");
home_pos = 1; //Set positive home_pos direction (down)
while (digitalRead(endstopA) == 0) { // Move down until no hit
stepperA.move(home_pos); // Move motor to home_pause
home_pos++; // Decrease by 1 for next move if needed
stepperA.run(); // Start moving the stepper
}
stepperA.setCurrentPosition(0);
Serial.println("Homed Successfully");
// Print out Instructions on the Serial Monitor at Start
Serial.println("Enter new position (positive = down, negative = up)");
}
At this point, the serial interface can be used to enter new target positions (in steps) from the absolute reference point we obtained in the homing sequence. The serial parser captures the entered integer and applies it to the new target position. It runs the motors until the steps to target position is 0. After that, the serial interface opens back up and you can start from the top!
void loop() {
stepperA.setMaxSpeed(3000); // Set Max Speed of Stepper
stepperA.setAcceleration(4000); // Set Acceleration of Stepper
while (Serial.available()>0) { // Check if values are available in the Serial Buffer
move_doneA = 0; // Set variable for checking move of the Stepper
travelA = Serial.parseInt(); // Put numeric value from buffer in travelA variable
if(travelA != 0){
Serial.print("Moving stepper into position: ");
Serial.println(travelA);
stepperA.moveTo(travelA); // Set new target position
Serial.print("Distance to go: ");
Serial.println(stepperA.distanceToGo());
delay(1000); // Wait 1 seconds before moving the Stepper
while((stepperA.distanceToGo() != 0)) {
stepperA.run(); // Move Stepper into position
}
//If move is completed display message on Serial Monitor
if ((move_doneA == 0) && (stepperA.distanceToGo() == 0)) {
Serial.println("Moved to: ");
Serial.println(stepperA.currentPosition());
Serial.println("");
Serial.println("Enter new position (positive = down, negative = up)");
move_doneA=1; // Reset move variable
travelA = 0;
}
}
}
}
From here I’ll probably look at breaking out some of the functionality into separate callable functions which will help keep things organized when I start implementing web-based control.