Finally we got our 2401s working!
Pros:
- Range seems to be much better than the 433 MHz transmitter we have been using so far. 50 feet vs about 6 feet. This means less dropped packets, faster update rate, more options for larger transmission payloads (more buttons).
Cons:
- No external antenna on the 2401. The ESC seems to interfere with the radio at high power settings. Everything is packing into the same aluminum enclosure...have to find a way to work around this problem...maybe move the radio to the outside of the box.
- NRF2401 Uses 5 pins. So can't use my existing stock of Attiny85 (6 available besides vcc/gnd) to drive this and still have pins left over for buttons. Right now we using an Arduino Nano which needs 5v and presents a bit of a battery problem...using 5v means 4 AA...so the remote is big and heavy. Ideally my remote will work on a single 3.6v LIPO cell. To do this, we'll need to move from the Arduino micro-controller to to Attiny 84 micro or the 85 that has more pins.
How we finally got it working:
- Using this updated NRF24 library and not this NRF library from Arduino. The pin mapping used by the updated library is completely different.
- 10uF cap soldered directly on the PWR/GND pin of the NRF24
- Arduino Nano 3.3v on-board regulator seems to have sufficient power for the NRF2401 even in MAX transmit mode. Even works with the notoriously high output resistance 9v battery!
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
Become a Hackaday.io Member
Create an account to leave a comment. Already have an account? Log In.