Go Down

Topic: How to send negative numbers over SPI (Read 810 times) previous topic - next topic

RoboNegro

Greetings all,

Does anyone know how to transfer a negative number over the SPI bus?

I am using an ArduinoMEGA, as slave, and ChipKIT Max32 as master. The Arduino calculates a value that varies from -61 to positive 61, in a sinusoidal fashion. When connected over SPI the ChipKIT receives the positive values, no problem. However the negative values are incorrect. Below is an example of the numbers received by the master:

3.00
10.00
23.00
30.00
38.00
45.00
56.00
59.00
61.00
60.00
58.00
55.00
51.00
42.00
38.00
33.00
29.00
22.00
18.00
13.00
3.00
254.00
248.00
241.00
227.00
220.00
213.00
201.00
198.00
196.00
196.00
198.00
201.00
204.00
213.00
217.00
222.00
230.00
233.00
237.00
246.00
252.00

What's going on here?

Thanks

PaulS

Quote
What's going on here?

Isn't it obvious? There's something wrong with you code. All you need to do is fix it.

You say you need help? Posting code using the invisible, 0 point font wasn't very nice.

spumanti

I agree with PaulS - difficult to help when there's no code!


A workaround the problem could be to add 100 before sending, so the values would be shifted to 39 - 161, and then on the receiving end, subtract 100 to get back to the actual values.

But I suspect there might be a more elegant solution than my kludgy approach above!  :smiley-fat:

zarobhr

subtract 255 from any result that is biiger that expected and you will get your correct negative number

of course without your code cant see how you are doing it. but i would suspect you are converting your neg number to a byte

RoboNegro

Sorry for omitting code, rookie mistake.

Here is the code for the master:

Code: [Select]
//The following code illustrates how to define initial OC1 pin state for the
//output compare toggle mode of operation in 32-bit mode

#include <plib.h>
#include <SPI.h>

int count = 0;
int curr_pos;
float CPG_setpoint;

int zero_pos, max_pos, min_pos;
float CPG_ang, min_ang, max_ang, curr_ang;
float torque;

int pwm_a = 3;  // PWM control for motor outputs 1 and 2 is on digital pin 3
int dir_a = 12;  // dir control for motor outputs 1 and 2 is on digital pin 12

void setup()
{
  Serial.begin(9600);
  pinMode(dir_a, OUTPUT);
 
  // slave select (SS) pin for SPI communications
  pinMode(53,OUTPUT);
 
  // configure module for OC1 pin low, toggle high
  OC1CON = 0x0001;
  // enable OC1 module
  OC1CONSET = 0x8000;
 
  // configure timer2 for prescaler of 2
  T2CON = 0x0018;
  // turn off OC1 while doing setup
  OC1CON = 0x0000;
  // configure for compare toggle mode
  OC1CON = 0x0023;
 
  // initialize compare register 1
  // compare register and period must be equal
  // 1 Hz = 2625a00
  // 2000 Hz = 4e20
  OC1R = 0x4e20;
  // set period
  PR2 = 0x4e20;
 
  // configure interrupt
 
  // clear the OC1 interrupt flag
  IFS0CLR = 0x00000040;
  // enable OC1 interrupt
  IEC0SET =0x00000040;
  // set OC1 interrupt priority to 7, the highest level
  IPC1SET = 0x001C0000;
 
  // set subpriority to 3, maximum
  IPC1SET = 0x00030000;
 
  // enable timer2
  T2CONSET = 0x8000;
  // enable OC1
  OC1CONSET = 0x8000;
 
  // initialize SPI
  // takes care of pin assignment MOSI, MISO, SCK
  SPI.begin();
  SPI.setBitOrder(MSBFIRST);
 
  zero_pos = analogRead(A0);
  min_pos = zero_pos - 40;
  max_pos = zero_pos + 40;
 
  min_ang = (6.28318)*((min_pos - zero_pos)/1024.0);
  max_ang = (6.28318)*((max_pos - zero_pos)/1024.0);
 
  // slight delay avoids false trigger at start.
  delay(1); 
}

void loop()
{
}

extern "C"
{
void __ISR(_OUTPUT_COMPARE_1_VECTOR,ipl7) OC1_IntHandler(void)
  {
    // clear interrupt flag
    IFS0CLR = 0x0040;   
   
    // read current position
    curr_pos = analogRead(A0);
    curr_ang = (100.0)*(6.28318)*((curr_pos - zero_pos)/1024.0);
   
    // send current position to CPG at 50 Hz
    // receive CPG setpoint from ArduinoMEGA at 50 Hz
    if(count%40 == 0){
      // direct write pin 53 (SS) LOW
      LATGCLR = 1000000000;
      CPG_setpoint = SPI.transfer(curr_pos);
      // direct write pin 53 (SS) HIGH
      LATGSET = 1000000000;
      //CPG_ang = map(CPG_setpoint,0.0,122.0,100.0*min_ang,100.0*max_ang);
      Serial.println(CPG_setpoint);     
    }
   
    // calculate torque to be sent to motor
    torque = (CPG_ang - curr_ang);
   
    //Serial.println(CPG_ang);
    //Serial.println(curr_pos);
    //Serial.println(torque);
   
    // set direction pin of h-bridge
    // PIN1 LO PIN2 HI - CW
//    if (torque > 0)
//        //{digitalWrite(dir_a, LOW);}
//        {LATACLR = 100;}     
//    if (torque < 0)
//        //{digitalWrite(dir_a, HIGH);}
//        {LATASET = 100;}
//       
    //torque = round(torque);
    //torque = abs(torque);
    //Serial.println(torque);
   
    // map torque value to PWM
    //torque = map(torque,0,24,0,100);
   
    // apply PWM signal to h-bridge to drive motor
    //analogWrite(pwm_a, torque);
   
    ++count;   
  }//ISR
}//extern "C"


and here is the code for the slave:

Code: [Select]

#include <stdio.h>
#include <avr/io.h>
#include <SPI.h>
// define the pin outs

int pwm_a = 3;  //PWM control for motor outputs 1 and 2 is on digital pin 3
int dir_a = 12;  //dir control for motor outputs 1 and 2 is on digital pin 12

float c = 1.0, tau1 = 0.10, tau2 = 2.0*tau1;
float CPG;
float CPG_pos;
float h;
float I1 = 1.0, I2 = 1.0, I3 = 1.0, I4 = 1.0;
float IC[] = {0.01,0.01,0.01,0}, K[] = {0,0,0,0};
float CPG_mapped;

byte CPG_byte;

int zero_pos, max_pos, min_pos;
float CPG_ang, min_ang, max_ang;

int count = 0;

float B = 2.5, Y = 2.5, timestep  = 0.020, M = 0.5*timestep;

// SPI data recieved, current CPG position
float curr_pos;

void setup()
{
  Serial.begin(9600);

  // initialize Timer1
  cli();          // disable global interrupts
  TCCR5A = 0;     // set entire TCCR1A register to 0
  TCCR5B = 0;     // same for TCCR1B

  // set compare match register to desired timer count:
  //313 = 50 Hz
  //156 = 100 Hz
  //80 = 200 Hz
  OCR5A = 313;
 
  // turn on CTC mode:
  TCCR5B |= (1 << WGM52);
  // Set CS50, CS51, CS52 bits for prescaler value: 1024
  TCCR5B |= (1 << CS50);
  TCCR5B |= (0 << CS51);
  TCCR5B |= (1 << CS52);
  // enable timer compare interrupt:
  TIMSK5 |= (1 << OCIE5A);
  sei(); // enable global interrupts
   
  //send data on master in, slave out (MISO)
  pinMode(MISO,OUTPUT);
  pinMode(53,INPUT);
 
  Serial.println("Enabling SPI in slave mode");
  Serial.println("SPI Slave, at your service.");
 
  // enable SPI in slave mode
  SPCR = 0x40;
  SPI.setBitOrder(MSBFIRST);
  delay(500);
 
  min_ang = (6.28318)*((min_pos - zero_pos)/1024.0);
  max_ang = (6.28318)*((max_pos - zero_pos)/1024.0);
}

void loop()
{
  curr_pos = SPI.transfer(CPG);
  //if(SS == LOW){
    //Serial.println("High");}
}

ISR(TIMER5_COMPA_vect)
{
  CPG = CPG_function(); 
  //Serial.println(CPG_byte);
}

float CPG_function()
{
  //solves Matsuoka equations for neuron1
  rungeKutta1(tau1,c,h,CPG_pos,I1,I2,I3,K);
  IC[0] = IC[0] + (1.0/6.0)*(K[0] + 2.0*(K[1] + K[2]) + K[3])*timestep;
  rungeKutta2(tau2,I1,I2,K);
  IC[1] = IC[1] + (1.0/6.0)*(K[0] + 2.0*(K[1] + K[2]) + K[3])*timestep;
       
  //solves Matsuoka equations for neuron2
  rungeKutta1(tau1,c,h,-(CPG_pos),I3,I4,I1,K);
  IC[2] = IC[2] + (1.0/6.0)*(K[0] + 2.0*(K[1] + K[2]) + K[3])*timestep;
  rungeKutta2(tau2,I3,I4,K);
  IC[3] = IC[3] + (1.0/6.0)*(K[0] + 2.0*(K[1] + K[2]) + K[3])*timestep;
       
  I1 = IC[0]; I2 = IC[1]; I3 = IC[2]; I4 = IC[3];
  CPG = IC[0] - IC[2];   
   
  CPG = CPG*100.0;
 
  //CPG_byte = (byte) CPG;
 
  //CPG_mapped = map(CPG,-61.0,61.0,0.0,122.0);   
}

//neuron1
float rungeKutta1(float tau1, float c, float h, float g, float IC0, float IC1, float IC2, float *K)
{
  K[0] = (1.0/tau1)*(c - IC0 - B*IC1 - Y*max(IC2,0) - h*max(g,0));
  K[1] = (1.0/tau1)*(c - (IC0 + M*K[0]) - B*(IC1 + M*K[0]) - Y*max(IC2 + M*K[0],0) - h*max((g),0));
  K[2] = (1.0/tau1)*(c - (IC0 + M*K[1]) - B*(IC1 + M*K[1]) - Y*max(IC2 + M*K[1],0) - h*max((g),0));
  K[3] = (1.0/tau1)*(c - (IC0 + timestep*K[2]) - B*(IC1 + timestep*K[2])  - Y*max(IC2 + timestep*K[2],0) - h*max((g),0)); 
}//end of rungeKutta1 function

//neuron2
float rungeKutta2(float tau2, float IC0, float IC1, float *K)
{
  K[0] = (1.0/tau2)*(max(IC0,0) - IC1);
  K[1] = (1.0/tau2)*(max(IC0 + M*K[0],0) - IC1 + M*K[0]);
  K[2] = (1.0/tau2)*(max(IC0 + M*K[1],0) - IC1 + M*K[1]);
  K[3] = (1.0/tau2)*(max(IC0 + timestep*K[2],0) - IC1 + timestep*K[2]);
}//end of rungeKutta2 function

SurferTim

I cheat.  ;)
Code: [Select]
      CPG_setpoint = (char)SPI.transfer(curr_pos);

SPI.transfer returns a byte type, no negative numbers. Typecast it to char, and it will show the correct sign.

PaulS

Code: [Select]
      Serial.println(CPG_setpoint);     
In an ISR? Apparently, you don't know what can and can not be done in an ISR. This is a can not thing.

Code: [Select]
    // set direction pin of h-bridge
    // PIN1 LO PIN2 HI - CW
//    if (torque > 0)
//        //{digitalWrite(dir_a, LOW);}
//        {LATACLR = 100;}     
//    if (torque < 0)
//        //{digitalWrite(dir_a, HIGH);}
//        {LATASET = 100;}
//       
    //torque = round(torque);
    //torque = abs(torque);
    //Serial.println(torque);
   
    // map torque value to PWM
    //torque = map(torque,0,24,0,100);
   
    // apply PWM signal to h-bridge to drive motor
    //analogWrite(pwm_a, torque);

I wonder if any of this is related to your problem. If not, why is it still in the code? Why post it?

Why is the ISR wrapped in the extern C declaration? The code is inside a file that will be compiled as C++ code, so wrapping it in C calling convention does not make sense.

RoboNegro

Why is Serial.println a "can not" thing in an ISR? I have been able to do it, as long as the interrupt isn't too long.

I am using a ChipKIT Max32, not an Arduino, the ChipKIT likes its ISRs to have the extern "C" declaration.

PaulS

Quote
I am using a ChipKIT Max32, not an Arduino, the ChipKIT likes its ISRs to have the extern "C" declaration.

Then why aren't you over there asking your questions? This is an Arduino forum, not a Chipkit forum.

retrolefty

Quote
Why is Serial.println a "can not" thing in an ISR? I have been able to do it, as long as the interrupt isn't too long.


Because starting in version 1.0 the hardware serial library was enhanced to included fully buffered and interrupt driven transmit along with the prior interrupt driven receive function it always had. So inside a ISR serial write commands are useless and will not function.

Lefty

RoboNegro

Thank you all for the info, it has helped a lot.

PaulS, lose the attitude.

Go Up