Skip to main content
Corrected abbreviation for milliseconds (ms).
Source Link
Nick Gammon
  • 38.9k
  • 13
  • 70
  • 126
const byte potpin = A0;  // analog pin used to connect the potentiometer
 
const unsigned long PRESCALER = 8;         // Timer 1 prescaler
const float PULSE_PERIOD = 0.020;          // 20 mSms
const float ZERO_POSITION_WIDTH = 0.0005;  // 0.5 mSms
const float FULL_POSITION_WIDTH = 0.0024;  // 2.4 mSms

// how far apart the pulses are
const unsigned long PULSE_WIDTH_COUNT = F_CPU / PRESCALER * PULSE_PERIOD;
// minimum pulse width (-45 degrees)
const unsigned long ZERO_POSITION_COUNT = F_CPU / PRESCALER * ZERO_POSITION_WIDTH;
// minimum pulse width (+45 degrees)
const unsigned long FULL_POSITION_COUNT = F_CPU / PRESCALER * FULL_POSITION_WIDTH;

void setup()
  {
  TCCR1A = 0;          // disable all PWM on Timer1 whilst we set it up
  ICR1 = PULSE_WIDTH_COUNT - 1;   // frequency is every 20ms (zero-relative)
  
  // Configure timer 1 for Fast PWM mode using ICR1, with 8x  prescaling
  TCCR1A = bit (WGM11);
  TCCR1B = bit (WGM13) | bit (WGM12) |  bit (CS11);  // fast PWM top at ICR1
  TCCR1A |= bit (COM1A1);  // Clear OC1A/OC1B on Compare Match,
  pinMode (9, OUTPUT);
  }   // end of setup
 
void loop()
{
  int val = analogRead(potpin);  // reads the value of the potentiometer (value between 0 and 1023)
  OCR1A = ZERO_POSITION_COUNT + (val * (FULL_POSITION_COUNT - ZERO_POSITION_COUNT) / 1024) - 1;
  delay(15);                     // wait for the servo to get there
} // end of loop
#include <SPI.h>

const unsigned long PRESCALER = 8;         // Timer prescaler
const float PULSE_PERIOD = 0.020;          // 20 mSms
const float ZERO_POSITION_WIDTH = 0.0005;  // 0.5 mSms
const float FULL_POSITION_WIDTH = 0.0024;  // 2.4 mSms

// how far apart the pulses are
const unsigned long PULSE_WIDTH_COUNT = F_CPU / PRESCALER * PULSE_PERIOD;
// minimum pulse width (-45 degrees)
const unsigned long ZERO_POSITION_COUNT = F_CPU / PRESCALER * ZERO_POSITION_WIDTH;
// minimum pulse width (+45 degrees)
const unsigned long FULL_POSITION_COUNT = F_CPU / PRESCALER * FULL_POSITION_WIDTH;

const byte NO_DATA_YET = 0xFF;

volatile unsigned char spiBuffer[6];  // '<' xH xL yH yL '>'
volatile byte buffPos = NO_DATA_YET;
volatile bool process_spiBuffer = false;

void setup()
  {
  Serial.begin (115200);   // debugging
  
  // Turn on SPI in slave mode
  SPCR |= bit(SPE);

  // Send on master in, *slave out*
  pinMode(MISO, OUTPUT);

  // Turn on interrupts
  SPI.attachInterrupt();
  
  TCCR1A = 0;          // disable all PWM on Timer1 whilst we set it up
  ICR1 = PULSE_WIDTH_COUNT - 1;   // frequency is every 20ms (zero-relative)
  
  // Configure timer 1 for Fast PWM mode using ICR1, with 8x prescaling
  TCCR1A = bit (WGM11);
  TCCR1B = bit (WGM13) | bit (WGM12) |  bit (CS11);  // fast PWM top at ICR1
  TCCR1A |= bit (COM1A1);  // Clear OC1A/OC1B on Compare Match,

  #ifdef __AVR_ATmega2560__

    TCCR3A = 0;          // disable all PWM on Timer3 whilst we set it up
    ICR3 = PULSE_WIDTH_COUNT - 1;   // frequency is every 20ms (zero-relative)
    
    // Configure timer 3 for Fast PWM mode using ICR3, with 8x prescaling
    TCCR3A = bit (WGM31);
    TCCR3B = bit (WGM33) | bit (WGM32) |  bit (CS31);  // fast PWM top at ICR3
    TCCR3A |= bit (COM3A1);  // Clear OC1A/OC1B on Compare Match,
  
    pinMode (11, OUTPUT);   // OC1A is D11 on Mega2560
    pinMode (5, OUTPUT);    // OC3A is D5  on Mega2560
  #else
    pinMode (9, OUTPUT);    // OC1A is D9  on Uno
  #endif
  }   // end of setup

int xValue,
    yValue;
    
void loop()
{

  if (process_spiBuffer)
    {
    if (spiBuffer [0] == '<' && spiBuffer [5] == '>')
      {
      xValue = makeWord (spiBuffer[1], spiBuffer[2]);
      yValue = makeWord (spiBuffer[3], spiBuffer[4]);
      }

    // ready for another interrupt
    buffPos = NO_DATA_YET;
    process_spiBuffer = false;
     
    }

  // adjust PWM amount
  OCR1A = ZERO_POSITION_COUNT + (xValue * (FULL_POSITION_COUNT - ZERO_POSITION_COUNT) / 1024) - 1;

  #ifdef __AVR_ATmega2560__
    OCR3A = ZERO_POSITION_COUNT + (yValue * (FULL_POSITION_COUNT - ZERO_POSITION_COUNT) / 1024) - 1;
  #endif

} // end of loop


ISR(SPI_STC_vect)
{
  byte c = SPDR;   // get byte from SPI hardware

  // exit if haven't processed last one
  if (process_spiBuffer)
    return;
    
  if (c == '<')
    buffPos = 0;
  else if (buffPos == NO_DATA_YET)
    return;  // ignore until we get a '<'

  if (buffPos < sizeof spiBuffer)
    {
    spiBuffer[buffPos++] = c;

    // End of message is ">" 
    if (c == '>') 
      process_spiBuffer = true;

   }  // end of room in buffer
} // end of ISR(SPI_STC_vect)
const byte potpin = A0;  // analog pin used to connect the potentiometer
 
const unsigned long PRESCALER = 8;         // Timer 1 prescaler
const float PULSE_PERIOD = 0.020;          // 20 mS
const float ZERO_POSITION_WIDTH = 0.0005;  // 0.5 mS
const float FULL_POSITION_WIDTH = 0.0024;  // 2.4 mS

// how far apart the pulses are
const unsigned long PULSE_WIDTH_COUNT = F_CPU / PRESCALER * PULSE_PERIOD;
// minimum pulse width (-45 degrees)
const unsigned long ZERO_POSITION_COUNT = F_CPU / PRESCALER * ZERO_POSITION_WIDTH;
// minimum pulse width (+45 degrees)
const unsigned long FULL_POSITION_COUNT = F_CPU / PRESCALER * FULL_POSITION_WIDTH;

void setup()
  {
  TCCR1A = 0;          // disable all PWM on Timer1 whilst we set it up
  ICR1 = PULSE_WIDTH_COUNT - 1;   // frequency is every 20ms (zero-relative)
  
  // Configure timer 1 for Fast PWM mode using ICR1, with 8x  prescaling
  TCCR1A = bit (WGM11);
  TCCR1B = bit (WGM13) | bit (WGM12) |  bit (CS11);  // fast PWM top at ICR1
  TCCR1A |= bit (COM1A1);  // Clear OC1A/OC1B on Compare Match,
  pinMode (9, OUTPUT);
  }   // end of setup
 
void loop()
{
  int val = analogRead(potpin);  // reads the value of the potentiometer (value between 0 and 1023)
  OCR1A = ZERO_POSITION_COUNT + (val * (FULL_POSITION_COUNT - ZERO_POSITION_COUNT) / 1024) - 1;
  delay(15);                     // wait for the servo to get there
} // end of loop
#include <SPI.h>

const unsigned long PRESCALER = 8;         // Timer prescaler
const float PULSE_PERIOD = 0.020;          // 20 mS
const float ZERO_POSITION_WIDTH = 0.0005;  // 0.5 mS
const float FULL_POSITION_WIDTH = 0.0024;  // 2.4 mS

// how far apart the pulses are
const unsigned long PULSE_WIDTH_COUNT = F_CPU / PRESCALER * PULSE_PERIOD;
// minimum pulse width (-45 degrees)
const unsigned long ZERO_POSITION_COUNT = F_CPU / PRESCALER * ZERO_POSITION_WIDTH;
// minimum pulse width (+45 degrees)
const unsigned long FULL_POSITION_COUNT = F_CPU / PRESCALER * FULL_POSITION_WIDTH;

const byte NO_DATA_YET = 0xFF;

volatile unsigned char spiBuffer[6];  // '<' xH xL yH yL '>'
volatile byte buffPos = NO_DATA_YET;
volatile bool process_spiBuffer = false;

void setup()
  {
  Serial.begin (115200);   // debugging
  
  // Turn on SPI in slave mode
  SPCR |= bit(SPE);

  // Send on master in, *slave out*
  pinMode(MISO, OUTPUT);

  // Turn on interrupts
  SPI.attachInterrupt();
  
  TCCR1A = 0;          // disable all PWM on Timer1 whilst we set it up
  ICR1 = PULSE_WIDTH_COUNT - 1;   // frequency is every 20ms (zero-relative)
  
  // Configure timer 1 for Fast PWM mode using ICR1, with 8x prescaling
  TCCR1A = bit (WGM11);
  TCCR1B = bit (WGM13) | bit (WGM12) |  bit (CS11);  // fast PWM top at ICR1
  TCCR1A |= bit (COM1A1);  // Clear OC1A/OC1B on Compare Match,

  #ifdef __AVR_ATmega2560__

    TCCR3A = 0;          // disable all PWM on Timer3 whilst we set it up
    ICR3 = PULSE_WIDTH_COUNT - 1;   // frequency is every 20ms (zero-relative)
    
    // Configure timer 3 for Fast PWM mode using ICR3, with 8x prescaling
    TCCR3A = bit (WGM31);
    TCCR3B = bit (WGM33) | bit (WGM32) |  bit (CS31);  // fast PWM top at ICR3
    TCCR3A |= bit (COM3A1);  // Clear OC1A/OC1B on Compare Match,
  
    pinMode (11, OUTPUT);   // OC1A is D11 on Mega2560
    pinMode (5, OUTPUT);    // OC3A is D5  on Mega2560
  #else
    pinMode (9, OUTPUT);    // OC1A is D9  on Uno
  #endif
  }   // end of setup

int xValue,
    yValue;
    
void loop()
{

  if (process_spiBuffer)
    {
    if (spiBuffer [0] == '<' && spiBuffer [5] == '>')
      {
      xValue = makeWord (spiBuffer[1], spiBuffer[2]);
      yValue = makeWord (spiBuffer[3], spiBuffer[4]);
      }

    // ready for another interrupt
    buffPos = NO_DATA_YET;
    process_spiBuffer = false;
     
    }

  // adjust PWM amount
  OCR1A = ZERO_POSITION_COUNT + (xValue * (FULL_POSITION_COUNT - ZERO_POSITION_COUNT) / 1024) - 1;

  #ifdef __AVR_ATmega2560__
    OCR3A = ZERO_POSITION_COUNT + (yValue * (FULL_POSITION_COUNT - ZERO_POSITION_COUNT) / 1024) - 1;
  #endif

} // end of loop


ISR(SPI_STC_vect)
{
  byte c = SPDR;   // get byte from SPI hardware

  // exit if haven't processed last one
  if (process_spiBuffer)
    return;
    
  if (c == '<')
    buffPos = 0;
  else if (buffPos == NO_DATA_YET)
    return;  // ignore until we get a '<'

  if (buffPos < sizeof spiBuffer)
    {
    spiBuffer[buffPos++] = c;

    // End of message is ">" 
    if (c == '>') 
      process_spiBuffer = true;

   }  // end of room in buffer
} // end of ISR(SPI_STC_vect)
const byte potpin = A0;  // analog pin used to connect the potentiometer
 
const unsigned long PRESCALER = 8;         // Timer 1 prescaler
const float PULSE_PERIOD = 0.020;          // 20 ms
const float ZERO_POSITION_WIDTH = 0.0005;  // 0.5 ms
const float FULL_POSITION_WIDTH = 0.0024;  // 2.4 ms

// how far apart the pulses are
const unsigned long PULSE_WIDTH_COUNT = F_CPU / PRESCALER * PULSE_PERIOD;
// minimum pulse width (-45 degrees)
const unsigned long ZERO_POSITION_COUNT = F_CPU / PRESCALER * ZERO_POSITION_WIDTH;
// minimum pulse width (+45 degrees)
const unsigned long FULL_POSITION_COUNT = F_CPU / PRESCALER * FULL_POSITION_WIDTH;

void setup()
  {
  TCCR1A = 0;          // disable all PWM on Timer1 whilst we set it up
  ICR1 = PULSE_WIDTH_COUNT - 1;   // frequency is every 20ms (zero-relative)
  
  // Configure timer 1 for Fast PWM mode using ICR1, with 8x  prescaling
  TCCR1A = bit (WGM11);
  TCCR1B = bit (WGM13) | bit (WGM12) |  bit (CS11);  // fast PWM top at ICR1
  TCCR1A |= bit (COM1A1);  // Clear OC1A/OC1B on Compare Match,
  pinMode (9, OUTPUT);
  }   // end of setup
 
void loop()
{
  int val = analogRead(potpin);  // reads the value of the potentiometer (value between 0 and 1023)
  OCR1A = ZERO_POSITION_COUNT + (val * (FULL_POSITION_COUNT - ZERO_POSITION_COUNT) / 1024) - 1;
  delay(15);                     // wait for the servo to get there
} // end of loop
#include <SPI.h>

const unsigned long PRESCALER = 8;         // Timer prescaler
const float PULSE_PERIOD = 0.020;          // 20 ms
const float ZERO_POSITION_WIDTH = 0.0005;  // 0.5 ms
const float FULL_POSITION_WIDTH = 0.0024;  // 2.4 ms

// how far apart the pulses are
const unsigned long PULSE_WIDTH_COUNT = F_CPU / PRESCALER * PULSE_PERIOD;
// minimum pulse width (-45 degrees)
const unsigned long ZERO_POSITION_COUNT = F_CPU / PRESCALER * ZERO_POSITION_WIDTH;
// minimum pulse width (+45 degrees)
const unsigned long FULL_POSITION_COUNT = F_CPU / PRESCALER * FULL_POSITION_WIDTH;

const byte NO_DATA_YET = 0xFF;

volatile unsigned char spiBuffer[6];  // '<' xH xL yH yL '>'
volatile byte buffPos = NO_DATA_YET;
volatile bool process_spiBuffer = false;

void setup()
  {
  Serial.begin (115200);   // debugging
  
  // Turn on SPI in slave mode
  SPCR |= bit(SPE);

  // Send on master in, *slave out*
  pinMode(MISO, OUTPUT);

  // Turn on interrupts
  SPI.attachInterrupt();
  
  TCCR1A = 0;          // disable all PWM on Timer1 whilst we set it up
  ICR1 = PULSE_WIDTH_COUNT - 1;   // frequency is every 20ms (zero-relative)
  
  // Configure timer 1 for Fast PWM mode using ICR1, with 8x prescaling
  TCCR1A = bit (WGM11);
  TCCR1B = bit (WGM13) | bit (WGM12) |  bit (CS11);  // fast PWM top at ICR1
  TCCR1A |= bit (COM1A1);  // Clear OC1A/OC1B on Compare Match,

  #ifdef __AVR_ATmega2560__

    TCCR3A = 0;          // disable all PWM on Timer3 whilst we set it up
    ICR3 = PULSE_WIDTH_COUNT - 1;   // frequency is every 20ms (zero-relative)
    
    // Configure timer 3 for Fast PWM mode using ICR3, with 8x prescaling
    TCCR3A = bit (WGM31);
    TCCR3B = bit (WGM33) | bit (WGM32) |  bit (CS31);  // fast PWM top at ICR3
    TCCR3A |= bit (COM3A1);  // Clear OC1A/OC1B on Compare Match,
  
    pinMode (11, OUTPUT);   // OC1A is D11 on Mega2560
    pinMode (5, OUTPUT);    // OC3A is D5  on Mega2560
  #else
    pinMode (9, OUTPUT);    // OC1A is D9  on Uno
  #endif
  }   // end of setup

int xValue,
    yValue;
    
void loop()
{

  if (process_spiBuffer)
    {
    if (spiBuffer [0] == '<' && spiBuffer [5] == '>')
      {
      xValue = makeWord (spiBuffer[1], spiBuffer[2]);
      yValue = makeWord (spiBuffer[3], spiBuffer[4]);
      }

    // ready for another interrupt
    buffPos = NO_DATA_YET;
    process_spiBuffer = false;
     
    }

  // adjust PWM amount
  OCR1A = ZERO_POSITION_COUNT + (xValue * (FULL_POSITION_COUNT - ZERO_POSITION_COUNT) / 1024) - 1;

  #ifdef __AVR_ATmega2560__
    OCR3A = ZERO_POSITION_COUNT + (yValue * (FULL_POSITION_COUNT - ZERO_POSITION_COUNT) / 1024) - 1;
  #endif

} // end of loop


ISR(SPI_STC_vect)
{
  byte c = SPDR;   // get byte from SPI hardware

  // exit if haven't processed last one
  if (process_spiBuffer)
    return;
    
  if (c == '<')
    buffPos = 0;
  else if (buffPos == NO_DATA_YET)
    return;  // ignore until we get a '<'

  if (buffPos < sizeof spiBuffer)
    {
    spiBuffer[buffPos++] = c;

    // End of message is ">" 
    if (c == '>') 
      process_spiBuffer = true;

   }  // end of room in buffer
} // end of ISR(SPI_STC_vect)
Added more explanations.
Source Link
Nick Gammon
  • 38.9k
  • 13
  • 70
  • 126

Example code

After considerable mucking around, I've got a simpler case working reliably.

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()

That's exactly what happened to me! Until ... I realized I should power the servos independently. As soon as I connected both servo's +5V and Gnd to an external power supply (and connected the ground of the power supply to the Mega) it all started working properly. So, lesson #1 is: power your motors properly!

I made up a test "driver" on my Uno using the code below:

#include <SPI.h>

void setup (void)
{
  digitalWrite(SS, HIGH);  // ensure SS stays high
  SPI.begin ();
  SPI.setClockDivider(SPI_CLOCK_DIV32);
}


void loop (void)
{
  int val;
  
  // enable Slave Select
  digitalWrite(SS, LOW);

  SPI.transfer ('<');
  val = analogRead (0);    // read A0
  SPI.transfer (highByte(val));
  SPI.transfer (lowByte (val));
  val = analogRead (1);    // read A1
  SPI.transfer (highByte(val));
  SPI.transfer (lowByte (val));
  SPI.transfer ('>');
  
  // disable Slave Select
  digitalWrite(SS, HIGH);

  delay (100);
}

That reads a pot on A0 and A1 and sends the full value (2 bytes) over SPI (which is slightly different to what you are doing).

Then the receiving end (on a Mega2560) has this code:

#include <SPI.h>

const unsigned long PRESCALER = 8;         // Timer prescaler
const float PULSE_PERIOD = 0.020;          // 20 mS
const float ZERO_POSITION_WIDTH = 0.0005;  // 0.5 mS
const float FULL_POSITION_WIDTH = 0.0024;  // 2.4 mS

// how far apart the pulses are
const unsigned long PULSE_WIDTH_COUNT = F_CPU / PRESCALER * PULSE_PERIOD;
// minimum pulse width (-45 degrees)
const unsigned long ZERO_POSITION_COUNT = F_CPU / PRESCALER * ZERO_POSITION_WIDTH;
// minimum pulse width (+45 degrees)
const unsigned long FULL_POSITION_COUNT = F_CPU / PRESCALER * FULL_POSITION_WIDTH;

const byte NO_DATA_YET = 0xFF;

volatile unsigned char spiBuffer[6];  // '<' xH xL yH yL '>'
volatile byte buffPos = NO_DATA_YET;
volatile bool process_spiBuffer = false;

void setup()
  {
  Serial.begin (115200);   // debugging
  
  // Turn on SPI in slave mode
  SPCR |= bit(SPE);

  // Send on master in, *slave out*
  pinMode(MISO, OUTPUT);

  // Turn on interrupts
  SPI.attachInterrupt();
  
  TCCR1A = 0;          // disable all PWM on Timer1 whilst we set it up
  ICR1 = PULSE_WIDTH_COUNT - 1;   // frequency is every 20ms (zero-relative)
  
  // Configure timer 1 for Fast PWM mode using ICR1, with 8x prescaling
  TCCR1A = bit (WGM11);
  TCCR1B = bit (WGM13) | bit (WGM12) |  bit (CS11);  // fast PWM top at ICR1
  TCCR1A |= bit (COM1A1);  // Clear OC1A/OC1B on Compare Match,

  #ifdef __AVR_ATmega2560__

    TCCR3A = 0;          // disable all PWM on Timer3 whilst we set it up
    ICR3 = PULSE_WIDTH_COUNT - 1;   // frequency is every 20ms (zero-relative)
    
    // Configure timer 3 for Fast PWM mode using ICR3, with 8x prescaling
    TCCR3A = bit (WGM31);
    TCCR3B = bit (WGM33) | bit (WGM32) |  bit (CS31);  // fast PWM top at ICR3
    TCCR3A |= bit (COM3A1);  // Clear OC1A/OC1B on Compare Match,
  
    pinMode (11, OUTPUT);   // OC1A is D11 on Mega2560
    pinMode (5, OUTPUT);    // OC3A is D5  on Mega2560
  #else
    pinMode (9, OUTPUT);    // OC1A is D9  on Uno
  #endif
  }   // end of setup

int xValue,
    yValue;
    
void loop()
{

  if (process_spiBuffer)
    {
    if (spiBuffer [0] == '<' && spiBuffer [5] == '>')
      {
      xValue = makeWord (spiBuffer[1], spiBuffer[2]);
      yValue = makeWord (spiBuffer[3], spiBuffer[4]);
      }

    // ready for another interrupt
    buffPos = NO_DATA_YET;
    process_spiBuffer = false;
     
    }

  // adjust PWM amount
  OCR1A = ZERO_POSITION_COUNT + (xValue * (FULL_POSITION_COUNT - ZERO_POSITION_COUNT) / 1024) - 1;

  #ifdef __AVR_ATmega2560__
    OCR3A = ZERO_POSITION_COUNT + (yValue * (FULL_POSITION_COUNT - ZERO_POSITION_COUNT) / 1024) - 1;
  #endif

} // end of loop


ISR(SPI_STC_vect)
{
  byte c = SPDR;   // get byte from SPI hardware

  // exit if haven't processed last one
  if (process_spiBuffer)
    return;
    
  if (c == '<')
    buffPos = 0;
  else if (buffPos == NO_DATA_YET)
    return;  // ignore until we get a '<'

  if (buffPos < sizeof spiBuffer)
    {
    spiBuffer[buffPos++] = c;

    // End of message is ">" 
    if (c == '>') 
      process_spiBuffer = true;

   }  // end of room in buffer
} // end of ISR(SPI_STC_vect)

This incorporates my suggested use of the timer hardware to do the servo positioning, rather than the servo library. There are a couple of #ifdefs in there, so you can run it on a Uno as well (it just won't do the second servo).


Example code

After considerable mucking around, I've got a simpler case working reliably.

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()

That's exactly what happened to me! Until ... I realized I should power the servos independently. As soon as I connected both servo's +5V and Gnd to an external power supply (and connected the ground of the power supply to the Mega) it all started working properly. So, lesson #1 is: power your motors properly!

I made up a test "driver" on my Uno using the code below:

#include <SPI.h>

void setup (void)
{
  digitalWrite(SS, HIGH);  // ensure SS stays high
  SPI.begin ();
  SPI.setClockDivider(SPI_CLOCK_DIV32);
}


void loop (void)
{
  int val;
  
  // enable Slave Select
  digitalWrite(SS, LOW);

  SPI.transfer ('<');
  val = analogRead (0);    // read A0
  SPI.transfer (highByte(val));
  SPI.transfer (lowByte (val));
  val = analogRead (1);    // read A1
  SPI.transfer (highByte(val));
  SPI.transfer (lowByte (val));
  SPI.transfer ('>');
  
  // disable Slave Select
  digitalWrite(SS, HIGH);

  delay (100);
}

That reads a pot on A0 and A1 and sends the full value (2 bytes) over SPI (which is slightly different to what you are doing).

Then the receiving end (on a Mega2560) has this code:

#include <SPI.h>

const unsigned long PRESCALER = 8;         // Timer prescaler
const float PULSE_PERIOD = 0.020;          // 20 mS
const float ZERO_POSITION_WIDTH = 0.0005;  // 0.5 mS
const float FULL_POSITION_WIDTH = 0.0024;  // 2.4 mS

// how far apart the pulses are
const unsigned long PULSE_WIDTH_COUNT = F_CPU / PRESCALER * PULSE_PERIOD;
// minimum pulse width (-45 degrees)
const unsigned long ZERO_POSITION_COUNT = F_CPU / PRESCALER * ZERO_POSITION_WIDTH;
// minimum pulse width (+45 degrees)
const unsigned long FULL_POSITION_COUNT = F_CPU / PRESCALER * FULL_POSITION_WIDTH;

const byte NO_DATA_YET = 0xFF;

volatile unsigned char spiBuffer[6];  // '<' xH xL yH yL '>'
volatile byte buffPos = NO_DATA_YET;
volatile bool process_spiBuffer = false;

void setup()
  {
  Serial.begin (115200);   // debugging
  
  // Turn on SPI in slave mode
  SPCR |= bit(SPE);

  // Send on master in, *slave out*
  pinMode(MISO, OUTPUT);

  // Turn on interrupts
  SPI.attachInterrupt();
  
  TCCR1A = 0;          // disable all PWM on Timer1 whilst we set it up
  ICR1 = PULSE_WIDTH_COUNT - 1;   // frequency is every 20ms (zero-relative)
  
  // Configure timer 1 for Fast PWM mode using ICR1, with 8x prescaling
  TCCR1A = bit (WGM11);
  TCCR1B = bit (WGM13) | bit (WGM12) |  bit (CS11);  // fast PWM top at ICR1
  TCCR1A |= bit (COM1A1);  // Clear OC1A/OC1B on Compare Match,

  #ifdef __AVR_ATmega2560__

    TCCR3A = 0;          // disable all PWM on Timer3 whilst we set it up
    ICR3 = PULSE_WIDTH_COUNT - 1;   // frequency is every 20ms (zero-relative)
    
    // Configure timer 3 for Fast PWM mode using ICR3, with 8x prescaling
    TCCR3A = bit (WGM31);
    TCCR3B = bit (WGM33) | bit (WGM32) |  bit (CS31);  // fast PWM top at ICR3
    TCCR3A |= bit (COM3A1);  // Clear OC1A/OC1B on Compare Match,
  
    pinMode (11, OUTPUT);   // OC1A is D11 on Mega2560
    pinMode (5, OUTPUT);    // OC3A is D5  on Mega2560
  #else
    pinMode (9, OUTPUT);    // OC1A is D9  on Uno
  #endif
  }   // end of setup

int xValue,
    yValue;
    
void loop()
{

  if (process_spiBuffer)
    {
    if (spiBuffer [0] == '<' && spiBuffer [5] == '>')
      {
      xValue = makeWord (spiBuffer[1], spiBuffer[2]);
      yValue = makeWord (spiBuffer[3], spiBuffer[4]);
      }

    // ready for another interrupt
    buffPos = NO_DATA_YET;
    process_spiBuffer = false;
     
    }

  // adjust PWM amount
  OCR1A = ZERO_POSITION_COUNT + (xValue * (FULL_POSITION_COUNT - ZERO_POSITION_COUNT) / 1024) - 1;

  #ifdef __AVR_ATmega2560__
    OCR3A = ZERO_POSITION_COUNT + (yValue * (FULL_POSITION_COUNT - ZERO_POSITION_COUNT) / 1024) - 1;
  #endif

} // end of loop


ISR(SPI_STC_vect)
{
  byte c = SPDR;   // get byte from SPI hardware

  // exit if haven't processed last one
  if (process_spiBuffer)
    return;
    
  if (c == '<')
    buffPos = 0;
  else if (buffPos == NO_DATA_YET)
    return;  // ignore until we get a '<'

  if (buffPos < sizeof spiBuffer)
    {
    spiBuffer[buffPos++] = c;

    // End of message is ">" 
    if (c == '>') 
      process_spiBuffer = true;

   }  // end of room in buffer
} // end of ISR(SPI_STC_vect)

This incorporates my suggested use of the timer hardware to do the servo positioning, rather than the servo library. There are a couple of #ifdefs in there, so you can run it on a Uno as well (it just won't do the second servo).

Source Link
Nick Gammon
  • 38.9k
  • 13
  • 70
  • 126

You have multiple issues here. For a start, you have a buffer (spiBuffer) of 5 bytes, into which you place <Xxy> (that's 5 bytes) and then finish off with:

    spiBuffer[buffPos] = 0;

That has now overwritten some other memory.


Next, you don't seem to be detecting the "<" symbol and resetting to the start of the buffer, so if you ever get out of sync, you will stay out of sync. I suggest something like making buffPos equal to 0xFF initially. Then in the ISR:

ISR(SPI_STC_vect)
{
    byte c = SPDR;  

    // exit if haven't processed last one
    if (process_spiBuffer)
      return;
      
    if (c == '<')
      buffPos = 0;
    else if (buffPos == 0xFF)
      return;  // ignore until we get a '<'

    if (buffPos < sizeof spiBuffer)
    {
        spiBuffer[buffPos++] = c;

        // End of message is ">" 
        if (c == '>') 
            process_spiBuffer = true;

    }  
}

Now we use 0xFF as a "flag" that we haven't received a "<" yet. When we do, we reset buffPos to zero, ready to process the next 5 bytes. In the main loop, once we are done with processing the buffer we set buffPos to 0xFF again (rather than zero). Also you might have the ISR called before you have processed the previous one, so I put in a test to exit if process_spiBuffer is true when you enter the ISR.


I found your code to work out the arm positions confusing. For debugging this issue I would replace it with simply moving the servo to the position received by SPI, eg.

   // crunchAngles();
   // moveArm();

   elbow.write (puck_x);

Now just debug that rather than getting confused about the difference between interrupt issues and logic issues. I found once I had done that, that my servo responded without any major issues (there was a bit of jittering occasionally).


I found that I got a lot of data errors until I slowed down the sender (a few microseconds delay after each SPI.transfer) because sending SPI at high speed didn't give the receiving sketch time to put the data into the buffer.


The servo library is rather dependent on interrupts (it manually does the PWM duty cycles). I wrote a sketch a while back that controls one server by using the hardware timer (Timer 1) rather than interrupts:

const byte potpin = A0;  // analog pin used to connect the potentiometer
 
const unsigned long PRESCALER = 8;         // Timer 1 prescaler
const float PULSE_PERIOD = 0.020;          // 20 mS
const float ZERO_POSITION_WIDTH = 0.0005;  // 0.5 mS
const float FULL_POSITION_WIDTH = 0.0024;  // 2.4 mS

// how far apart the pulses are
const unsigned long PULSE_WIDTH_COUNT = F_CPU / PRESCALER * PULSE_PERIOD;
// minimum pulse width (-45 degrees)
const unsigned long ZERO_POSITION_COUNT = F_CPU / PRESCALER * ZERO_POSITION_WIDTH;
// minimum pulse width (+45 degrees)
const unsigned long FULL_POSITION_COUNT = F_CPU / PRESCALER * FULL_POSITION_WIDTH;

void setup()
  {
  TCCR1A = 0;          // disable all PWM on Timer1 whilst we set it up
  ICR1 = PULSE_WIDTH_COUNT - 1;   // frequency is every 20ms (zero-relative)
  
  // Configure timer 1 for Fast PWM mode using ICR1, with 8x  prescaling
  TCCR1A = bit (WGM11);
  TCCR1B = bit (WGM13) | bit (WGM12) |  bit (CS11);  // fast PWM top at ICR1
  TCCR1A |= bit (COM1A1);  // Clear OC1A/OC1B on Compare Match,
  pinMode (9, OUTPUT);
  }   // end of setup
 
void loop()
{
  int val = analogRead(potpin);  // reads the value of the potentiometer (value between 0 and 1023)
  OCR1A = ZERO_POSITION_COUNT + (val * (FULL_POSITION_COUNT - ZERO_POSITION_COUNT) / 1024) - 1;
  delay(15);                     // wait for the servo to get there
} // end of loop

That is for the Atmega328P, not the Mega, but you could adapt it easily enough. You could use Timer 2 for the other servo (I think).

Since it doesn't use interrupts, then having interrupts going off in the background won't affect the servo.