Close

NRF2401 Alive!

A project log for Electric Longboard

Can we build a great board for under $400 in less than 3 weeks? (using a few pre-made kit parts)

dudeskidaddydudeskidaddy 06/23/2016 at 17:060 Comments

Finally we got our 2401s working!

Pros:

Cons:

How we finally got it working:

Code: A few details

The Payload Optimization: We're sending a 3 byte payload over the air. 2 bytes for potentiometer's 16-bit integer and 1 bytes for the state of 3 buttons. (cruise control, emergency brake in case the potentiometer fails and maybe something for the future like lights, a horn, turbo mode...who knows). This kind of compression is probably overkill but it's more fun and might save a bit of battery power on the remote.

Setup Mode: ESC setup requires moving the throttle from full high to full low in a second or two...our calibrated throttle response is too slow for that since we've programmed it to take about 6 seconds. We created "Setup mode" to bypass the spool up/dn and directly send the throttle position to the ESC. In this mode it can go from 0 to full power in about 1 second! For safety, no matter what button you push on the remote, setup mode only works in the first 10 seconds of operation.

Throttle Response, Braking: In testing under load, we found the first 25% of throttle doesn't produce much acceleration. So we changed the spool up to go from 0 to about 25% throttle pretty quickly...under 0.5 second. Ideally we're just talking about a a non-linear response. Could have come up with a nice smooth polynomial for this, but we're still prototyping and just picked a few values we thought would work.

Cruise Control: If cruise button is pressed, throttle only works when the stick is full aft/fwd. Brake button still active.

NRF2401 Transmitter. 1 Pot value (0-1023) and 3 buttons (bool)

/*
  NRF2401 - Arduino Nano
   1 - GND  to GND 
   2 - VCC  to 3.3V 
   3 - CE   to D7
   4 - CSN  to D8
   5 - SCK  to D13
   6 - MOSI to D11
   7 - MISO to D12
   8 - floating
*/

#include <SPI.h>   
#include "RF24.h"  

RF24 myRadio (7, 8); 
byte address[8] = {"abc123"}; // Pick Your own unique address here 
byte data[3] = {0,0,0}; // 1st 2 bytes are pot, second byte is the 3 buttons (3 bits)
int pot_pin = A0;
int pot_val=0;
int button1_pin = 2;
int button1_val = 0;
int button2_pin = 3;
int button2_val = 0;
int button3_pin = 4;
int button3_val = 0;

void setup()
{
  //Serial.begin(115200);
  //delay(500);
  //Serial.println(F("Starting..."));
  pinMode(pot_pin,INPUT);
  myRadio.begin();  
  myRadio.setChannel(181);  
  myRadio.setPALevel(RF24_PA_MAX); // Not sure if this is needed yet, MIN maybe ok.
  myRadio.openWritingPipe(address);
  delay(1000);
}

void loop()  
{
  unsigned pot = analogRead(pot_pin); // potentiometer
  data[0] = (byte) (pot & 0xFF);
  data[1] = (byte) ((pot >> 8) & 0xFF);
  bool b1 = digitalRead(button1_pin); // button1
  bool b2 = digitalRead(button2_pin); // button2
  bool b3 = digitalRead(button3_pin); // button3
  // Compress these 4 pieces of information into 3 bytes. 16bit in (pot) takes 2 bytes 
  // 3 bits (3 buttons takes the 3rd byte.  Minimize the transmission payload.
  data[2] = (byte) 0; //clear the byte..we're only going to use the 1st 3 bits.
  data[2]|= (byte) (b1<<7); // shift bit to MSB position
  data[2]|= (byte) (b2<<6); // shift bit to MSB -1 pos
  data[2]|= (byte) (b3<<5); // shift bit to MST -2 pos
  // if all 3 buttons are hi, data[2] = 11100000 or 224 viewed as an int
  myRadio.write( &data, sizeof(data) ); //  Transmit the data
  //Serial.print(F("Sent  "));
  //Serial.print(data[0]);
  delay(25); // approxx 30-40Hz frame rate...plenty
}

NRF2401 Receiver 1 Pot value (0-1023) and 3 buttons (bool)

/*
  NRF2401 - Arduino Nano
   1 - GND  to GND 
   2 - VCC  to 3.3V 
   3 - CE   to D7
   4 - CSN  to D8
   5 - SCK  to D13
   6 - MOSI to D11
   7 - MISO to D12
   8 - floating
*/
#include <SPI.h>   
#include <ServoTimer2.h> //  
#include "RF24.h"  // 

int pwm_pin=3; //digital pin3 connects to ESC
RF24 myRadio (7, 8);  
byte address[8] = {"abc123"}; // Pick Your own unique address here 
#define payloadSize 3 
// bytes
byte data[payloadSize];
bool cruise = false;
bool ebrake = false;
bool setupmode = false;
bool setupPermanetOff =  false;
int centerRest = 504; // 0-1023 (where does the pot rest)
int escMinPWM = 750;  // ms pulse width
int escMaxPWM = 2250; // ms pulse width of servoTimer2
int requestedThrottle = 0; // from 504 1023   0=rest 1023=max power
int maxThrottleRange = 1000; // throttle will map 0 to this number, then to the esc Range.
int maxEscThrottleGovernor = 750;  // 0 to maxThrotteRange
int escThrottle = 0;          // actual throttle send to ESC 0-180
int minThrottlePot = 5; // pot isn't that great should be 0
int maxThrottlePot = 1020; // ditto (should be 1023
int counter = 0;
int cruiseAccellThreshold = 950; // pot = 0 to 1023, represents very high throttle up ->slow accel 
int cruiseDecelerateThreshold = 50; // very low throttle dn command in cruise ->slow decel
int breakMin = 400; // throttle position break values
int breakMed = 250;
int breakMax = 100;
int movement = 0; // change in throttle from one iteration to the next
int incr[5] = {5,7,9,11,13}; // throttle movement increments.

ServoTimer2  myservo;

void setup()   
{
  Serial.begin(115200);
  delay(1000);
  Serial.println(F("eStakeboard Receiver Bootup"));
  myservo.attach(pwm_pin); 
  
  myRadio.begin();  
  myRadio.setChannel(181); 
  myRadio.setPALevel(RF24_PA_MAX); // Not sure if this is needed yet, MIN maybe ok.
  myRadio.openReadingPipe(1, address); 
  myRadio.startListening();

}

void loop()  {
  
  if ( myRadio.available()) {
    while (myRadio.available())  {
      myRadio.read( &data, sizeof(data)); 
      counter = 0;
    } 
    // decode the 3 bytes into an Int and 3 bits
    // 1st two bytes contain an Int.  Shift them appropriately
    unsigned pot = (data[1] << 8) + (data[0] << 0);
    // 3 bits.  Mask and shift right to get 0/1 in the LSB position
    // 0x80 = 10000000 (128 as an Int) 0x40 = 01000000 0x20 = 00100000  
    bool button1 = ((data[2] & 0x80) >> 7); // 1st bit of this byte
    bool button2 = ((data[2] & 0x40) >> 6); // 2nd
    bool button3 = ((data[2] & 0x20) >> 5); // 3rd
    adjustSpeed (pot,button1,button2,button3);
    Serial.print(pot);
    Serial.print(" ");
    Serial.print(button1);
    Serial.print(" ");
    Serial.print(button2);
    Serial.print(" ");
    Serial.print(button3);
    Serial.print(" ");
    Serial.println("");
  } else {
      // no signal from motor...send stop signal
      // Wait 50 ms for a transmission, else send fail signal to ESC
      // If just failed, only wait another 25 ms to send the next Fail signal 
      counter +=1;
      if (counter > 50) {
        adjustSpeed (centerRest,0,1,0); // sending neutral throttle signal
        Serial.println("Signal loss");
        counter = 25; // don't reset counter to 0...we seem to have transmission probs.
      }
      delay(5); // wait a bit for a good transmission
  }
}


void adjustSpeed(int pot,bool setupModeButton, bool ebrake, bool cruise) {
    if (millis() > 10000) {
      setupPermanetOff =  true;
    }
    // setup mode only allowed in the fist 10 seconds with button1 hi
    // in setup mode throttle position = esc throttle position...no delay
    if (setupModeButton == true && !setupPermanetOff) {
        setupmode = true;  
    } else {
        setupmode = false;
    }
    
    if (setupmode) {
        // no governor
        requestedThrottle = map(pot,centerRest+5,maxThrottlePot,0,maxThrottleRange);
    } else {
        requestedThrottle = map(pot,centerRest+5,maxThrottlePot,0,maxEscThrottleGovernor);
    }
    if (ebrake) {
        // as if there is no throttle input
        requestedThrottle = 0;
    }
    // normal mode: accelerate only when requested throttle is higher than esc
    // cruise mode: accelerate only when requested throttle is fully pegged hi/lo
    if ((!cruise && escThrottle < requestedThrottle) ||
        (cruise && requestedThrottle > cruiseAccellThreshold)) {   
        movement = incr[0]; // lowest of the increments 
        // When ESC is at a low setting...under a load we need more throttle response
        // lets get it moving along quicker then.
        if (escThrottle < 25) {
            movement = incr[4]; // start a bit quicker..use max increment
        } else if (escThrottle < 50) {
            movement = incr[3]; // 
        }
    } else if ((!cruise && escThrottle > requestedThrottle) || 
                (cruise && requestedThrottle < cruiseDecelerateThreshold))   {
        movement = -incr[0];
        // brakes: if the pot is < centerRest, start coming off the gas quickly 
        if (pot < breakMax) {
            movement -=incr[4]; 
        } else if (pot < breakMed) {
           movement -=incr[2];
        } else if (pot < breakMin) {
           movement -=incr[1];
        }
    } else {
        movement = 0; // no change in speed requested
    }
   
    escThrottle += movement; // apply change
    if (setupmode) {
       // constrain throttle to min/max
       escThrottle = constrain(escThrottle,0,maxThrottleRange);
    } else {
       escThrottle = constrain(escThrottle,0,maxEscThrottleGovernor);
    }
    // finally map to the esc PWM
    int pwmVal = map(escThrottle,0,maxThrottleRange,escMinPWM,escMaxPWM);
    myservo.write(pwmVal);
    Serial.print("Setup: ");
    Serial.print(setupmode);
    Serial.print("   ESC Throttle: ");
    Serial.print(escThrottle);
    Serial.print("   PWM : ");
    Serial.println(pwmVal);
    
}

Discussions