my blog

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.

Nathaniel Berman1 Comment