I'm trying to drive an N20 motor with encoder, using the Adafruit motor shield V3.
When running the example code that comes with the package, encoderMotorRPM, the motor does turn, but the serial only prints the initial messages followed by Direction: forward @ 0 RPM repeatedly, regardless the actual direction and speed of the motor.
The code below is directly copied from the example. The only modification I made is commenting out all the lines pertaining to the OLED screen, since I don't have one.
I've never worked with encoder motors or the attachInterrupt() command, so I don't know where to start troubleshooting this code.
#include <Wire.h>
#include <Adafruit_MotorShield.h>
// #include <Adafruit_SSD1306.h>
// Connect to the two encoder outputs!
#define ENCODER_A 12
#define ENCODER_B 11
// These let us convert ticks-to-RPM
#define GEARING 20
#define ENCODERMULT 12
// Create the motor shield object with the default I2C address
Adafruit_MotorShield AFMS = Adafruit_MotorShield();
// And connect a DC motor to port M1
Adafruit_DCMotor *myMotor = AFMS.getMotor(1);
// We'll display the speed/direction on the OLED
// Adafruit_SSD1306 display = Adafruit_SSD1306(128, 32, &Wire);
volatile float RPM = 0;
volatile uint32_t lastA = 0;
volatile bool motordir = FORWARD;
void interruptA() {
motordir = digitalRead(ENCODER_B);
digitalWrite(LED_BUILTIN, HIGH);
uint32_t currA = micros();
if (lastA < currA) {
// did not wrap around
float rev = currA - lastA; // us
rev = 1.0 / rev; // rev per us
rev *= 1000000; // rev per sec
rev *= 60; // rev per min
rev /= GEARING; // account for gear ratio
rev /= ENCODERMULT; // account for multiple ticks per rotation
RPM = rev;
}
lastA = currA;
digitalWrite(LED_BUILTIN, LOW);
}
void setup() {
Serial.begin(9600); // set up Serial library at 9600 bps
pinMode(LED_BUILTIN, OUTPUT);
digitalWrite(LED_BUILTIN, LOW);
pinMode(ENCODER_B, INPUT_PULLUP);
pinMode(ENCODER_A, INPUT_PULLUP);
attachInterrupt(ENCODER_A, interruptA, RISING);
delay(100);
// SSD1306_SWITCHCAPVCC = generate display voltage from 3.3V internally
// if(!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) { // Address 0x3C for 128x32
// Serial.println(F("SSD1306 allocation failed"));
// for(;;); // Don't proceed, loop forever
// }
// display.display();
// display.setTextSize(2);
// display.setTextColor(WHITE);
//while (!Serial) delay(1);
Serial.println("MMMMotor party!");
if (!AFMS.begin()) { // create with the default frequency 1.6KHz
// if (!AFMS.begin(1000)) { // OR with a different frequency, say 1KHz
Serial.println("Could not find Motor Shield. Check wiring.");
while (1);
}
Serial.println("Motor Shield found."); Serial.println("Begun");
// turn on motor M1
myMotor->setSpeed(0);
}
void printRPM() {
// display.clearDisplay();
// display.setCursor(0,0);
Serial.print("Direction: ");
if (motordir) {
// display.println("Forward");
Serial.println("forward @ ");
} else {
// display.println("Backward");
Serial.println("backward @ ");
}
// display.print((int)RPM); display.println(" RPM");
Serial.print((int)RPM); Serial.println(" RPM");
// display.display();
}
int i;
void loop() {
delay(50);
myMotor->run(FORWARD);
for (i=0; i<255; i++) {
myMotor->setSpeed(i);
delay(20);
printRPM();
}
for (i=255; i!=0; i--) {
myMotor->setSpeed(i);
delay(20);
printRPM();
}
myMotor->run(BACKWARD);
for (i=0; i<255; i++) {
myMotor->setSpeed(i);
delay(20);
printRPM();
}
for (i=255; i!=0; i--) {
myMotor->setSpeed(i);
delay(20);
printRPM();
}
}
Hi there! I'm currently working on this for my own project as I had the same issues with the example code. I will update as I go along, but here is what I have found so far.
I am using the Adafruit Motor Shield V3 with an Arduino UNO R3 and a N20 (6V, 1:50 Gear Ratio). My Arduino and Motor Shield are powered by two independent sources.
See below for my completed code. It's not optimal, but it is sufficient for my purposes.
My components are:
Arduino UNO R3
Adafruit Motor Shield V3
N20 DC Motor (6V with 1:50 Gear Ratio)
I'm powering the motor shield with 5 AA batteries. The Arduino is powered by USB from my laptop.
The wiring for the DC motor is as follows:
Red wire - M1 Power
White wire - M1 Ground
Black wire - Arduino 3V
Blue wire - Arduino Ground
Yellow wire - Arduino Pin 3
Code:
#include <Adafruit_MotorShield.h>
// create motor shield object and dc motor object
Adafruit_MotorShield AFMS = Adafruit_MotorShield();
Adafruit_DCMotor *leftMotor = AFMS.getMotor(1);
#define ENCODER_L 3 // encoder wire on pin 3
#define TICKS_PER_REV 350 // number of ticks per revolution
int ticks = 0; // counts ticks from last interval
int speed = 0; // speed of motor, from 0 to 255
bool faster = true; // denotes whether motor is accelerating or decelerating
// counts ticks
void interrupt_L() {
++ticks;
}
void setup() {
// start serial
Serial.begin(9600);
Serial.println("Start.");
// check if motor shield is connected
if (!AFMS.begin()) {
Serial.println("Motor Shield not found.");
while (1);
}
Serial.println("Motor Shield found.");
// set encoder pin for left motor
pinMode(ENCODER_L, INPUT_PULLUP);
attachInterrupt(digitalPinToInterrupt(ENCODER_L), interrupt_L, RISING);
}
void loop() {
// every 50 ms, use number of ticks to determine rev/sec
delay(50);
Serial.print("Revolutions per second: ");
Serial.println(ticks/(TICKS_PER_REV * 0.05));
ticks = 0;
// increase and decrease motor speed
if (faster) {
speed += 1;
if (speed >= 250) {
faster = false;
}
}
else {
speed -= 1;
if (speed <= 0) {
faster = true;
}
}
// run motor
leftMotor->setSpeed(speed);
leftMotor->run(FORWARD);
}
The program counts the number of encoder ticks in a 50 millisecond interval and uses that to estimate the angular velocity. The resolution is high enough for my project, but there are other ways to calculate the angular velocity (e.g. measure time between two ticks and use that to find the velocity). There's only two pins that support interrupts on my Arduino, and since I need to use two motors, I am reserving Pin 2 for my other motor. Unfortunately this means I cannot determine direction using the second encoder wire (green wire), as I have nowhere to wire it to, so I can't supply that code for you.
Let me know if you have any questions/comments/suggestions.
Interesting! Thank you for that. I kept hearing about a v3 motor shield from Adafruit but this is the first time I've actually seen one. I see that their v2.3 page has been updated with pictures of the v3, but not the tutorial (which also still has a v2.2 schematic).
With the Arduino Uno, you can use pin change interrupts. It's quite simple if you use a library.
There are two I would recommend, and they are both available through the library manager.
There is Nico Hood's library PinChangeInterrupt which uses very similar syntax to the external interrupt code you are with attachInterrupt() replaced with attachPCINT()
There is also GreyGnome's library EnableInterrupt.
To answer your why: I still need more practice. I'm working on getting better, but that's the level I'm at now. Thank you for your suggestion of watching soldering videos; I have found them helpful in the past.
Great info! Thank you so much for sharing and for all of the detailed information; I hadn't realized this was an option. I will definitely look into the libraries you linked!
You should pratice before you perform... or bad things happen. I would put the project on hold, read what happens during soldering and practice good soldering techniques.
Thanks for the warning! I'll double back and give it another shot before I move on. Unfortunately, I have a deadline that won't give me too much time to practice, but I will do my best to fix what I have now and be better in the future.
Thanks for the tip! It seems as if I do need to to declare the variable as volatile. To confirm the reasoning behind your suggestion, the motivation to use volatile is because the interrupt is implemented as a concurrent thread, executing in parallel with the code run in the loop, and the compiler might optimize it so that the interrupt only modifies the variable in the register, so if the main code calls the variable, it may use an older, un-updated version of the variable stored in the memory? By using volatile, we remove this optimization and force the assembly instructions to always load and store the variable directly from and into memory every time the variable is used?