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
Become a Hackaday.io Member
Create an account to leave a comment. Already have an account? Log In.