In this Instructable we will be learning how to use interrupts on the Arduino to track the hall effect sensors in a motor to track position. This is Part 1 of a 3 part series, next week we will learn about synchronizing the speed of two motors so they move together and in the third part we will cover more advanced PID control.
Part 2: https://www.instructables.com/id/Hall-Effect-Sensor...
Part 3: https://www.instructables.com/id/Hall-Effect-Sensors-3-PID-Control/
For this Instructable, we will need:
- Actuator with Hall Effect sensor
- MegaMoto motor control shield
Let's get started!
Hall effect sensors measure the strength of a nearby magnetic field. By attaching a magnet to the shaft of the motor, the sensors can detect when the shaft is parallel to them. Using a small circuit board, this information can be output as a square wave, which can be counted as a string of pulses. By counting these pulses you can keep track of how many times the motor has spun and how the motor moves.
Some hall effect boards have multiple hall effect sensors on them. It is common for them to have 2 sensors (A and B) at 90 degrees. By counting these pulses and seeing which comes first (A before B, or B before A) you can tell the direction that the motor is spinning. Or you can just monitor both sensors and get more counts for more precise control.
The hall effect sensors have 4 wires: 5V, GND, and 2 Signal wires. Each signal wire gives out pulses as the motor spins. There are also the two actuator wires to connect to the MegaMoto. We are only using one of the hall effect signals.
Connect as follows:
- Red wire to 5V
- Black wire to GND
- Yellow/Orange wire to Arduino pin 2 or 3 (Important)
- Actuator red wire to MOTA of the MegaMoto
- Actuator black wire to MOTB of the MegaMoto
Make sure to check the beginning of the code in the next step. Ensure that the jumpers on the MegaMoto and the hall effect sensors are all set to the correct pins. Ensure that "hall0" and "hall1" correspond to the correct MegaMotos (PWMA0 and PWMA1 respectively).
Once the motors are wired correctly to the boards, wire as follows:
- Connect 12V to BAT+
- Connect GND to BAT-
- Connect 12V to Vin on the Due
- Wire two buttons between pins 7 and 8, connecting them to GND
The Arduino pin selection of 2 or 3 is crucial. The Arduino Uno has 2 interrupt pins that can be used. If you have an Arduino Mega you have 6 interrupts that can be used (2, 3, 18, 19, 20, 21) and with a Due you can use every pin as an interrupt. In the next step we will look at the programming that will enable these pins as interrupts.
Using Ardunio pins 2 and 3 on the Uno is very important. We are going to be using the interrupt function of the Arduino.
Interrupts are a type of subroutine, usually very small. They should only be a few lines of code and they need to execute as fast as possible. Usually when code runs it goes through line by line, running the instructions sequentially. When you use interrupts, interrupt the code and execute as soon as the trigger condition is true. We are going to trigger an interrupt whenever the encoder gives pulses and use that to keep count. This allows us not to lose track of counts.
There are 4 types of triggers for the interrupt: Rising, Falling, High, and Low. By changing the trigger you can adjust when the interrupt happens. Rising is whenever the pin sees a transition from low to high, falling is when it sees high to low, low is when the pin is low and high is when the pin is high.
Below is a small snippet of code, showing the basic interrupt we will use. There are comments to further explain.
volatile int count = 0;//if the interrupt will change this value, it must be volatile void setup() { pinMode(2, INPUT); set as input digitalWrite(2, HIGH);//enable internal pullup resistor attachInterrupt(digitalPinToInterrupt(2), interruptName, RISING);//Interrupt initialization Serial.begin(9600); }//end setup void loop() { Serial.println(count);//see the counts advance delay(100);//Delays usually can't be interfered with, here we will see the interrupt work }//end loop void interruptName() { count = count+1; }//end Interrupt Service Routine (ISR)
Usually it is poor form to use a Serial.print() in an interrupt. Serial.prints() are a very computational intense task, they take a long time to happen. When you are in an interrupt you cannot be interrupted by a second one. If you are counting pulses and the interrupt is too long you will lose counts.
For more details, see this page: www.arduino.cc/en/Reference/AttachInterrupt
Now that we've seen a basic interrupt we can expand it to control the motor. We are going to use a single variable and then depending if the actuator is going forwards or backwards we will add or subtract from the value.
The subroutine speed0() will be linked to the interrupt. By knowing the current position and calculating the destination position we know which direction to send the motor. Keep in mind that the counts may not be perfect. If you run the motor for a long time back and forth you may lose a count here and there and slowly lose track of where you are. We will use a homing routine (see next step) to help keep everything in check.
The attached code will move the actuator forwards or backwards by a set amount when you press the buttons on pin 7 or 8 (active LOW). You can test it by using a wire attached to GND.
See the attached code below for more details.
#define PWMA0 6 #define PWMB0 5 #define enable0 13 //pins for first MegaMoto #define switch0 7 //Up button #define switch1 8 //Down button #define hall0 2 //interrupt pins for hall effect sensors int enable = 0; //enable pin for megaMoto int count[] = {0};//Actuator int sw[] = {1, 1}; //switch up, switch down int prev[] = {0, 0};//previous switch state int currentPos = 0;//current position int threshold = 1; int destination = 0; bool forwards = false; bool backwards = false;// motor states bool firstRun = true;//first run of the motor once the button is pushed void setup() { pinMode(PWMA0, OUTPUT); pinMode(PWMB0, OUTPUT);//set PWM outputs pinMode(enable0, OUTPUT); digitalWrite(enable0, LOW);//set enable and turn board OFF pinMode(switch0, INPUT); pinMode(switch1, INPUT); digitalWrite(switch0, HIGH); digitalWrite(switch1, HIGH);//set up/down switch, enable enternal relays pinMode(hall0, INPUT); digitalWrite(hall0, LOW);//set hall, set low to start for rising edge attachInterrupt(0, speed0, RISING); //enable the hall effect interupts Serial.begin(9600); }//end setup void loop() { ReadInputs();//check input button, calculate speeds if (sw[0] == 0 && sw[1] == 1 && backwards == false) destination = currentPos - 200;//dont change destination while moving else if (sw[0] == 1 && sw[1] == 0 && forwards == false) destination = currentPos + 200;//dont change destination while moving if ((destination >= (currentPos - threshold)) && (destination <= (currentPos + threshold))) stopMoving();//stop if you're close enough else if (destination > currentPos) goForwards();//move if you need to else if (destination < currentPos) goBackwards();//move if you need to for (int i = 0; i <= 1; i++) prev[i] = sw[i]; //store switch values as previous values }//end loop void speed0() { if (forwards == true) count[0]++; //if moving forwards, add counts else if (backwards == true) count[0]--; //if moving back, subtract counts }//end speed0 void ReadInputs() { sw[0] = digitalRead(switch0), sw[1] = digitalRead(switch1);//check switches currentPos = count[0];//set where you are }//end read inputs void goForwards() { forwards = true; backwards = false;//set travel direction digitalWrite(enable0, HIGH);//enable board analogWrite(PWMA0, 255); analogWrite(PWMB0, 0);//apply speeds }//end goForwards void goBackwards() { forwards = false; backwards = true;//set travel direction digitalWrite(enable0, HIGH);//enable board analogWrite(PWMA0, 0); analogWrite(PWMB0, 255);//apply speeds }//end goBackwards void stopMoving() { forwards = false; backwards = false;//set travel direction analogWrite(PWMA0, 0); analogWrite(PWMB0, 0);//set speeds to 0 delay(10); digitalWrite(enable0, LOW);//disable board }//end stopMoving
Please ensure the MegaMoto current sense pin is on A5, and across A2, and A3.
Even when we try our best to count every pulse there may be occasions that we may miss a few and slowly lose precision over time. To minimize this we can use a homing routine! The idea is to send the actuator to a predetermined position (fully extended or fully retracted) and set the counts to a known value. Usually it is easiest to fully retract the actuator and set the counts to 0. In the code below it will reset to a maximum counts value when fully extended and reset to 0 when fully retracted.
To do so you need a way to tell when the motor is at it's limits. Here we will use the current sensing of the MegaMoto to watch when the current drops to 0. When it does we can see that the actuator has hit the limit switch and stopped moving. We have a small counter running because sometimes the current can report a false 0. By making sure that the current is 0 for a length of time we know that the motor really is at a limit and isn't getting false readings.
See attached code and comments for more information:
#define amp0 A5 #define PWMA0 6 #define PWMB0 5 #define enable0 13 //pins for first MegaMoto #define switch0 7 //Up button to add counts #define switch1 8 //Down button to subtract counts #define hall0 2 //interrupt pins for hall effect sensors int enable = 0; //enable pin for megaMoto int lowampLimit = 0;//Low limit to detect when actuator stops int amps = 0; //current readings int timedelay[] = {750, 50}; //first, regular delay for feedback int hitLimits = 0; int hitLimitsmax = 10;//value for knowing when retracted int count[] = {0};//Actuator int maxCounts = 1150;//number of counts when fully extended int sw[] = {1, 1}; //switch up, switch down int prev[] = {0, 0};//previous switch state int currentPos = 0;//current position int threshold = 1; int destination = 0; bool forwards = false; bool backwards = false;// motor states bool extended = false; bool retracted = false;//actuator positions bool firstRun = true;//first run of the motor once the button is pushed void setup() { pinMode(amp0, INPUT); digitalWrite(amp0, LOW);//set Current sensors pinMode(PWMA0, OUTPUT); pinMode(PWMB0, OUTPUT);//set PWM outputs pinMode(enable0, OUTPUT); digitalWrite(enable0, LOW);//set enable and turn board OFF pinMode(switch0, INPUT); pinMode(switch1, INPUT); digitalWrite(switch0, HIGH); digitalWrite(switch1, HIGH);//set up/down switch, enable enternal relays pinMode(hall0, INPUT); digitalWrite(hall0, LOW);//set hall, set low to start for rising edge attachInterrupt(0, speed0, RISING); //enable the hall effect interupts retracted = true;//start retracted extended = false; Serial.begin(9600); }//end setup void loop() { ReadInputs();//check input button, calculate speeds if (sw[0] == 0 && sw[1] == 1 && backwards == false) destination = currentPos - 115;//dont change destination while moving else if (sw[0] == 1 && sw[1] == 0 && forwards == false) destination = currentPos + 115;//dont change destination while moving Serial.print("count[0] "); Serial.println(count[0]); Serial.print("currentPos "); Serial.println(currentPos); Serial.print("destination "); Serial.println(destination); if ((destination >= (currentPos - threshold)) && (destination <= (currentPos + threshold))) stopMoving();//stop if you're close enough else if (destination > currentPos) goForwards();//move if you need to else if (destination < currentPos) goBackwards();//move if you need to for (int i = 0; i <= 1; i++) prev[i] = sw[i]; //store switch values as previous values }//end loop void speed0() { if (forwards == true) count[0]++; //if moving forwards, add counts else if (backwards == true) count[0]--; //if moving back, subtract counts }//end speed0 void ReadInputs() { amps = analogRead(amp0);//read current sw[0] = digitalRead(switch0), sw[1] = digitalRead(switch1);//check switches currentPos = count[0];//set where you are }//end read inputs void goForwards() { forwards = true; backwards = false;//set travel direction getFeedback();//check current draw digitalWrite(enable0, HIGH);//enable board analogWrite(PWMA0, 255); analogWrite(PWMB0, 0);//apply speeds }//end goForwards void goBackwards() { forwards = false; backwards = true;//set travel direction getFeedback();//check current draw digitalWrite(enable0, HIGH);//enable board analogWrite(PWMA0, 0); analogWrite(PWMB0, 255);//apply speeds }//end goBackwards void stopMoving() { forwards = false; backwards = false;//set travel direction analogWrite(PWMA0, 0); analogWrite(PWMB0, 0);//set speeds to 0 delay(10); digitalWrite(enable0, LOW);//disable board }//end stopMoving void getFeedback() { amps = analogRead(amp0); Serial.print(" Amp readings - "), Serial.println(amps); if (amps <= lowampLimit && hitLimits < hitLimitsmax) hitLimits = hitLimits + 1; else hitLimits = 0; if (hitLimits == hitLimitsmax && backwards == true) { Serial.println("RETRACTED"); retracted = true; count[0] = 0; //reset counter when homed destination = 0; } if (hitLimits == hitLimitsmax && forwards == true) { Serial.println("EXTENDED"); extended = true; count[0] = maxCounts; //reset counter when extended destination = maxCounts; } }//end getFeedback
In this Instructable we learned how interrupts worked and then used a hall effect sensor to track the position of an actuator. This was part 1 in a three part series, next week we will go over speed control and using multiple actuators together.
If you'd like to take a look at our selection of linear actuators, motions control systems and microcontrollers then please visit us at www.progressiveautomations.com for all your actuator needs! We can even build a custom actuator or control system for you based on your own custom specifications with the help of our highly trained staff of engineers. You can learn more about the custom order process right here!
Follow, Like and Subscribe!
Twitter -www.twitter.com/ProgAutoInc
Facebook -www.facebook.com/ProgressiveAutomations