My Arduino MEGA clone (ATMEGA 2650) reads SPI data as a slave using an interrupt.
There are also two servos that make an arm (shoulder and elbow).
When I comment out the line that attaches the SPI interrupt, the servos work properly.
When the SPI interrupt is attached, three strange things happen:
1) The PWM of the elbow remains at a fixed duty cycle. The shoulder works fine.
2) The shoulder PWM is somewhat jittery time-wise, I assume though that this is unavoidable with interrupts.
3) Writing an angle to either servo, (appears to) write to both servos! (In the software anyway.) So if I only write to the elbow, the shoulder will have the same value ( printed with shoulder.read() ). And vise-versa.
Again, none of those problems occur when I comment out the SPI interrupt attach.
It doesn't make sense to me though. Why would attaching an interrupt make two servos have the same value? And how can the values of the servos be the same, when only one PWM duty cycle actually changes!?
I wonder if this is a timer issue. Maybe the SPI interrupt interferes with the servo timers or something.
How can I receive SPI messages while also controlling two servos?
#include <Servo.h>
#include <SPI.h>
const float pi = 3.14159;
const float L1 = 27;
const float L2 = 27;
float c2;
float s2;
float psi;
float theta;
float angle_shoulder = 90;
float angle_elbow = 90;
volatile int arm_x;
volatile int arm_y;
volatile int puck_x;
volatile int puck_y;
Servo shoulder;
Servo elbow;
int sweepDir = 0;
int sweep1 = 0;
unsigned char spiBuffer[5];
volatile byte buffPos;
volatile bool process_spiBuffer;
void setup()
{
Serial.begin(115200); //Initialize serial communication
Serial.println("### SETUP ###");
shoulder.attach(25); //5
elbow.attach(43); //3
// BEGIN SPI SETUP
// Turn on SPI in slave mode
SPCR |= bit(SPE);
// Send on master in, *slave out*
pinMode(MISO, OUTPUT);
// Get ready for an interrupt
buffPos = 0; // buffer empty
process_spiBuffer = false;
// Turn on interrupts
SPI.attachInterrupt();
// END SPI SETUP
} // END SETUP()
void loop()
{
arm_y = 30;
if (process_spiBuffer)
{
spiBuffer[buffPos] = 0;
// X is a two-byte value, SPI only sends one byte at a time, so merge the bits upon receipt
puck_x = (spiBuffer[1] << 8) | spiBuffer[2];
puck_y = spiBuffer[3];
//Check if received message is acceptable, else throw warning and reject message
if (spiBuffer[0] != '<' || spiBuffer[4] != '>' || puck_x < 0 || puck_x > 320 || puck_y < 0 || puck_y > 240)
{ //Message received was corrupted
Serial.print("SPI: Bad MSG Data!:\"");
//Serial.print(spiBuffer);
}
else
{ //Message received "OK"
Serial.print("SPI MSG Received:\"");
//Serial.print(spiBuffer);
Serial.println("\"");
Serial.print("SPI puck coords:(");
Serial.print(puck_x);
Serial.print(",");
Serial.print(puck_y);
Serial.println(")");
// Decide on new paddle position. (x,y) of puck is 90degrees CW from AI (arm) POV, so x and y are swapped. Arm origin (0,0) is at bottom-right corner from the AI POV.
arm_x = map((240 - puck_y), 1, 320, 5, 49); // map(value, fromLow, fromHigh, toLow, toHigh)
}
buffPos = 0;
process_spiBuffer = false;
}
crunchAngles();
moveArm();
} // END loop()
void crunchAngles()
{
c2 = (sq(arm_x) + sq(arm_y) - sq(L1) - sq(L2)) / (2 * L1 * L2);
s2 = sqrt(1 - sq(c2));
angle_elbow = acos((sq(arm_x) + sq(arm_y) - sq(L1) - sq(L2)) / (2 * L1 * L2)) * (180 / pi); // theta?
angle_shoulder = asin((arm_y * (L1 + L2 * c2) - arm_x * L2 * s2) / (sq(arm_x) + sq(arm_y))) * (180 / pi); // psi?
}
// Update the servos
void moveArm()
{
// TODO: Clip servo output with serial warning
if (angle_shoulder < -10) { angle_shoulder = -10; } // Actual min angle is about -18deg (Model HS-225BB)
else if (angle_shoulder > 179) { angle_shoulder = 179; } // Actual max angle is about 181deg (Model HS-225BB)
shoulder.write(180 - angle_shoulder + 2); // Shoulder increments clockwise, so subtract from 180
if (angle_elbow < 0) { angle_elbow = 0; } // Actual max angle is about -5deg (Model SG90)
else if (angle_elbow > 170) { angle_elbow = 170; } // Actual max angle is about 190deg (Model SG90)
elbow.write(angle_elbow + 3); // Elbow increments clowise from
}
ISR(SPI_STC_vect)
{
byte c = SPDR;
if (buffPos < sizeof spiBuffer)
{
spiBuffer[buffPos++] = c;
// End of message is ">"
if (c == '>') // if (c == '\n')
process_spiBuffer = true;
}
}
process_spiBuffer
. As a test you could make that flag be set by closing a switch (which you test inloop
) and see if the same problem arises. That would rule in or out the interrupts. Meanwhile you could also comment out your debugging displays and see what happens.spiBuffer[buffPos] = 0;
- you are writing past the end of the buffer.sizeof (buf) - 1
.spiBuffer[sizeof(buf) - 1] = 0;
?