Jo Fairfax Studio – Motor controller
I’ve been working with light artist, maker and NESTA fellow Jo Fairfax, (of Jo Fairfax Studio) on an interactive wall prototype.
He needed to have control over loads of motors which all move circular disks a set distance.
I designed a stepper motor controller unit to control up to 12 motors. These can be repeated to produce very large arrays of stepper motors.
The final effect is amazing, as shown in the videos here.
The final results look great and the unit in these videos has 6 of the motor controllers to control 72 small stepper motors.
Design brief
The design brief was to create a unit to control the rotation of a number of circular disks. The unit needed to be easily reprogrammable (so that different effects could be easily created), daisy-chainable (so that many motors could be controlled), detect movement (to activate different motion patterns) and relatively low-cost (as this was a prototype).
Design overview
There were a number of design decisions made through a number of prototype steps. Jo was actively engaged in the design process (as he knew the final effect he was wanting) and he also wanted to be able to reprogram the units easily, to speed up the development time.
We eventually settled upon:
- Arduino compatible Nano based controller unit
- Controls up to 12 small stepper motors, which can be configured into two banks of 6.
- Stepper motors all have individual controllers.
- Ultrasconic distance sensors detect movement.
- This meant we could program specific distances for the unit to react to and we found much more reliable than a PIR sensor.
- DC-DC converter power supply.
- For input voltages up to 12V DC.
We also implemented a reset to known position on start up so that the pattern stays true, even with a power loss. This was done by monitoring the input voltage (using a potential divider) and storing the position of the motors into EEPROM just before it powers down. This worked very well compared to our other options of mechanical reset switches.
The PCB for the motor controller.
The populated PCB.
The unit is based on the Nano (Arduino Clone).
A DC-DC converter is used for powering the unit.
This means we can power the boards at 12V, but the motors run at 5V.
The prototype on the test bench.
Lots of motors (72 in total) controlled by the boards (6 boards in total). This prototype version is a bit of a mess.
Ultrasonic sensors control the unit.
Arduino Code
We wrote some Arduino code to control the motors, which is based upon AccelStepper stepper motor controller code.
/* Stepper Motor Controller This runs a stepper motor (or motors) from an Arduino Pro 5V ATMEGA328 16MHz (12MHz?) I'm using the 28BYJ-48 stepper motor with driver This Motor has a Gear ratio of 64 , and Stride Angle 5.625° so this motor has a 4096 Steps . Lots more info here: http://42bots.com/tutorials/28byj-48-stepper-motor-with-uln2003-driver-and-arduino-uno/ Gear ratio is: 64 approx :1 Actual gear ratio: 63.68395 : 1 The PCB has the following wiring: Stepper Motor Set 1 (6 x motors) is on: D5,D6,D7,D8 Stepper Motor Set 2 (6 x motors) is on: D9,D10,D11,D12 Connectors K1/K2/K3/K4 decide if all 12 Motors do the same thing or if there are two sets of 6 motors which can do different things. INPUTS: RESET D2 SW1 D3 SW2 D4 DETECT_IN1 A0 A1 TrigPin A2 EchoPin A3 This is the input voltage with a 100k/10k divider. This code has the following functions: STEPPED: Each time the PIR is triggered then the motors will move one position forwards. When all positions are complete then the motor will be back to position 0 There are 4096 (approx) steps 1 rotation (360degrees). There are four positions we can move to. The end position wil always be zero. The input voltage is monitored all the time. This is used to record the last position of the motor before shutting down. This means the unit can reset when powered up. This example code is in the public domain. */ #include <EEPROM.h> // For writing values to the EEPROM #include <avr/eeprom.h> #include <AccelStepper.h> #define HALFSTEP 8 // Motor pin definitions #define motorPin1 5 // IN1 on the ULN2003 driver 1 #define motorPin2 6 // IN2 on the ULN2003 driver 1 #define motorPin3 7 // IN3 on the ULN2003 driver 1 #define motorPin4 8 // IN4 on the ULN2003 driver 1 #define trigPin A2 // The connections for the ultrasonic distance sensor #define echoPin A1 #define voltageMonitor A3 // This is the pin to measure the voltage on the input // ****************USER VARIABLES**************************************** // ********************************************************************** // ************** MODE ************************ // STEPPED = stepps through 4 different locations String mode = "STEPPED"; // ************** STEPPED INPUT ******************** // These are the three positions the unit will move into before returning to 0 in the fourth // The positions are steps where 4096 = 360 degrees (approx) int step1 = 1024; int step2 = 2048; int step3 = -1024; int previousStep = 0; // A holder for the position the unit was left in. // This means we can reset to zero when powered up. // Ultrasonic distance sensor variables int minDistance = 10; // Minimum distance (cm) for the Ultrasonic Sensor int maxDistance = 100; // Maximum distance (cm) for the Ultrasonic Sensor // Stepper motor variables int maxSpeed = 800; // The maximum speed for the motor (typical values 200-800) int acceleration = 100; // Acceleration of the motor (typical values (50 - 400) // ********************* END OF USER VARIABLES*************************** // ********************************************************************** int RESET = 2; int SW1 = 3; int SW2 = 4; int led = 13; // LED on pin 13 - flash to make it obvious boolean pirTriggered = LOW; // This is a flag to stop things repeating boolean stepTrigger = LOW; // This is to set off the step trigger int stepCase = 0; // This is the next step movement to do. int modeCase = 0; // This is the main mode to do. // Varibales for writing to EEPROM int hiByte; // These are used to store longer variables into EEPROM int loByte; int sensorValue = 0; // For the distance sensor long duration, distance;// For the distance sensor // Initialize with pin sequence IN1-IN3-IN2-IN4 for using the AccelStepper with 28BYJ-48 AccelStepper stepper1(HALFSTEP, motorPin1, motorPin3, motorPin2, motorPin4); void setup() { // Initialise the PIR as a input with a pull up resistor pinMode(RESET, INPUT_PULLUP); pinMode(SW1, INPUT_PULLUP); pinMode(SW2, INPUT_PULLUP); pinMode(voltageMonitor, INPUT); pinMode(led,OUTPUT); // Have the LED display an output pinMode(trigPin, OUTPUT); pinMode(echoPin, INPUT); Serial.begin(115200); stepper1.setMaxSpeed(maxSpeed); // usual value = 500 stepper1.setSpeed(maxSpeed); // usual value = 200 stepper1.setAcceleration(acceleration); // usual value = 100 // Retrieve the setpoints from EEPROM hiByte = EEPROM.read(0); loByte = EEPROM.read(1); previousStep = (hiByte << 8)+loByte; // Get the previous step value Serial.print("Previous Step was:"); Serial.println(previousStep); // We want to move to zero again to start once more. stepper1.setCurrentPosition(previousStep); stepper1.moveTo(0); while(stepper1.run()) { } // Switch off the motors digitalWrite(motorPin1,LOW); digitalWrite(motorPin2,LOW); digitalWrite(motorPin3,LOW); digitalWrite(motorPin4,LOW); if(mode=="STEPPED") { modeCase = 0; } }//--(end setup )--- void loop() { switch(modeCase) { case 0: if(stepTrigger==HIGH) { Serial.println("Button PRESSED!"); Serial.println(stepper1.currentPosition()); // do something different depending on the case: switch (stepCase) { case 0: // ******* STEPPED MODE ****************** stepper1.moveTo(step1); while(stepper1.run()) { // We want to read the voltage during every step // If the voltage drops below a certain amount // We want to store the voltage and stop the movement of the motor if(analogRead(voltageMonitor)<=200) { // This means the voltage is around 10V on the input previousStep = stepper1.currentPosition(); // Store the value to EEPROM // Save this value before all the power fails EEPROM.write(0, previousStep >> 8); // Do this seperately EEPROM.write(1, previousStep & 0xff); // Write it to serial for DEBUG Serial.println("LOW VOLTAGE"); Serial.println(previousStep); break; } } Serial.print("Moved to:"); Serial.println(step1); break; case 1: stepper1.moveTo(step2); while(stepper1.run()) { Serial.println(stepper1.currentPosition()); // This displays the current position all the time // We want to read the voltage during every step // If the voltage drops below a certain amount // We want to store the voltage and stop the movement of the motor if(analogRead(voltageMonitor)<=200) { // This means the voltage is around 10V on the input previousStep = stepper1.currentPosition(); // Store the value to EEPROM // Save this value before all the power fails EEPROM.write(0, previousStep >> 8); // Do this seperately EEPROM.write(1, previousStep & 0xff); // Write it to serial for DEBUG Serial.println("LOW VOLTAGE"); Serial.println(previousStep); break; } } Serial.print("Moved to:"); Serial.println(step2); break; case 2: stepper1.moveTo(step3); while(stepper1.run()) { Serial.println(stepper1.currentPosition()); // This displays the current position all the time // We want to read the voltage during every step // If the voltage drops below a certain amount // We want to store the voltage and stop the movement of the motor if(analogRead(voltageMonitor)<=200) { // This means the voltage is around 10V on the input previousStep = stepper1.currentPosition(); // Store the value to EEPROM // Save this value before all the power fails EEPROM.write(0, previousStep >> 8); // Do this seperately EEPROM.write(1, previousStep & 0xff); // Write it to serial for DEBUG Serial.println("LOW VOLTAGE"); Serial.println(previousStep); break; } } Serial.print("Moved to:"); Serial.println(step3); break; case 3: stepper1.moveTo(0); while(stepper1.run()) { Serial.println(stepper1.currentPosition()); // This displays the current position all the time // We want to read the voltage during every step // If the voltage drops below a certain amount // We want to store the voltage and stop the movement of the motor if(analogRead(voltageMonitor)<=200) { // This means the voltage is around 10V on the input previousStep = stepper1.currentPosition(); // Store the value to EEPROM // Save this value before all the power fails EEPROM.write(0, previousStep >> 8); // Do this seperately EEPROM.write(1, previousStep & 0xff); // Write it to serial for DEBUG Serial.println("LOW VOLTAGE"); Serial.println(previousStep); break; } } Serial.print("Moved to:"); Serial.println("0"); break; } // Switch off the motors digitalWrite(motorPin1,LOW); digitalWrite(motorPin2,LOW); digitalWrite(motorPin3,LOW); digitalWrite(motorPin4,LOW); // Move to the next step stepCase++; if(stepCase>=4) { stepCase=0; // return to zero } } break; // ******** END OF STEEPED MODE ********************* } // ********** SENSOR READS ************************************ // // This is always within the loop //***** UltraSonic Distance Sensor **********// // This sction of code reads the ultrasonic distance sensor delay for(int i = 0; i<=5; i++) { digitalWrite(trigPin, LOW); // Added this line delayMicroseconds(2); // Added this line digitalWrite(trigPin, HIGH); delayMicroseconds(1000); // Added this line digitalWrite(trigPin, LOW); duration = pulseIn(echoPin, HIGH); distance += (duration/2) / 29.1; delay(2); } distance = distance/5; // Averaging if((distance<=maxDistance&&distance>=minDistance)||digitalRead(SW1)==LOW) { stepTrigger = HIGH; } else { stepTrigger = LOW; } Serial.print("Ultrasonic:"); Serial.print(distance); Serial.println(" cm"); // We want to read the voltage during every step // If the voltage drops below a certain amount // We want to store the voltage and stop the movement of the motor if(analogRead(voltageMonitor)<=200) { // This means the voltage is around 10V on the input previousStep = stepper1.currentPosition(); // Store the value to EEPROM // Save this value before all the power fails EEPROM.write(0, previousStep >> 8); // Do this seperately EEPROM.write(1, previousStep & 0xff); // Write it to serial for DEBUG Serial.println("LOW VOLTAGE"); Serial.println(previousStep); } }
The prototype has already been shown at an exhibition in Wakefield.