Close

NRF2401 & our tran/rec code.

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/15/2016 at 21:410 Comments

Radio Choice: NRF2401

We've been playing with a variety of transmitter-receiver hardware and settled on the Nrf24 for a few reasons. One, there is a very well documented and simple Arduino library for this: Arduino NRF2401 Library Second, we want the throttle control logic to reside on the skateboard micro, not in the remote. So rather than have the throttle send a single PWM signal straight to the ESC (using an stx882 for example), it will be sending the state of the throttle, and buttons as byte stream (currently 3 bytes). The idea is that in the event of a remote failure, the system has the opportunity to respond the way I want it to.. i.e. slow down. We can also add as many controls as I want to the remote..lights for example, because it's not limited to a single PWM signal.

(Update 8/6/2016 Turns out most ESCs handle a "loss of signal" by shutting down...which means free-wheeling. In the case of the VESC you can even program in a breaking force.)


Braking

After playing around with our $30 ESC we realized it has no brakes...waiting for out VESC. In the meantime were going to use it, moving on and getting this thing working and going for a ride with no brakes. Can't wait!

(update 6/20. Yes, riding with no brakes is fine for some situations...but after finally getting a VESC with brakes...there is just no going back. If you are thinking of building an electric longboard, you absolutely need to use a VESC with brakes. A hobby ESC is great for learning the electronics part, getting a board up an running really quickly and cheaply, but after that...you'll want one. A VESC is less than $90 now...not much more than a hobby ESC)

Features implemented in the code:

Inputs: Throttle Potentiometer and 2 buttons.

Setup Mode: Enables setting min/max throttle on the ESC

Acceleration Control: Be able to slow down throttle response to something reasonable for a skateboard. For example spool up/dn

Emergency Brake: Paranoid...in case throttle potentiometers goes nuts or you can't turn off power on remote.

Cruise: Maintains speed, only full throw of the throttle increases or decreases speed slowly.


Arduino Code:

We've been riding for a few weeks now, this setup seems to be pretty good. Use at your own risk.

Transmitter using Nrf2401 and Arduino

/*

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] = {"myaddress"}; // 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; // Arduino Nano

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

myRadio.setPALevel(RF24_PA_MAX);

myRadio.openWritingPipe(address);

myRadio.stopListening();

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

b1=0;

b2=0;

b3=0;

// 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

Serial.println("About to write");

myRadio.write( &data, sizeof(data) ); // Transmit the data

Serial.print(F("Sent "));

Serial.println(pot);

delay(25); // approxx 30-40Hz frame rate...plenty

}

Receiver using Nrf2401 and Arduino
/*

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] = {"myaddress"}; // 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.

unsigned last_good_signal;

ServoTimer2 myservo;

void setup()

{

Serial.begin(115200);

delay(1000);

Serial.println(F("eStakeboard Receiver Bootup"));

myservo.attach(pwm_pin);

myRadio.begin();

myRadio.setChannel(111);

myRadio.setPALevel(RF24_PA_MAX); // Not sure if this is needed yet, MIN maybe ok.

myRadio.openReadingPipe(1, address);

myRadio.startListening();

last_good_signal = millis() ;

}

void loop() {

if ( myRadio.available()) {

last_good_signal = millis();

while (myRadio.available()) {

myRadio.read( &data, sizeof(data));

counter = 0;

}

Serial.print("GOOD TRANS ");

Serial.print(last_good_signal);

Serial.print(" ");

// 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 {

unsigned now = millis();

// expecting a signal every 25ms or so

if ( abs(now - last_good_signal) > 35 ) {

last_good_signal = now -36; // int will overflow eventually and give errorneous results

adjustSpeed (centerRest,0,1,0); // sending neutral throttle signal

Serial.print("Signal loss ");

Serial.print(now);

Serial.print(" ");

delay(25); // same delay as transmitter. Slow down to 0 should be same.

}

}

}

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