Simple 3D-printed NRF remote - Arduino controlled

What kind of cap? Most of them is soldered on the other side tho. But worth a try :slight_smile:

1 Like

Well you could use motor KV, gearing etc. to calculate the speed, however, the speed would be a theoretical speed and not the actual speed. This would only work if no load is applied I believe. Actually, the eRPM counter is more than enough and does the job very precise (at least the VESC 6) :slight_smile:

1 Like

@Deakbannok I have lately been thinking about the issue regarding acceleration turning of while giving max or minimum throttle. At the moment the receiver uses standard PWM signal to generate the throttle signale, however shouldn’t the signal be a PPM signal? I remember something about a “Servo” library which are able to generate a true PPM signal. I will give this a try, and return my findings :slight_smile:

As far as I’m aware, receiver signal is actually PWM. I don’t know why people call it PPM. The position is not modulated, only the width. The correct way to generate a receiver signal is something like this (I made my own Arduino/NRF controller/receiver setup some time ago and this is part of my code):

#include <Servo.h>

Servo esc;

void setup() {
  esc.attach(9);
}

void loop() {
  receiveFromController();
  int val = map(throttle, 0, 1023, 900, 2100);
  esc.writeMicroseconds(val);
}

This post is quite useful (and I relied on it heavily when designing some code for @MysticalDork to control some LED headlights from a receiver signal): https://ryanboland.com/blog/reading-rc-receiver-values/

5 Likes

Is 180 degree but you have to compromising the negative output numbers min/max of Hall sensor. I am not super confident if that is right. But no worry I have fixed and added adjustable options to your code awhile ago. You will see it soon Right now I am looking on this glitchy RF24 code. Try to understand the limitation and how this thing works.

I am new to this program stuffs so it will take awhile for me to catch up. My work is piling up in the office so I hardly get time to continue working on your code except the weekend. I took a week off from work to Spain last week and tested there. Had a good running so far… but here are a few safety concerns i have found and added to the code. I will try to push out as soon as i have tested myself that everything is working correct before let the public try it out

Hi, Ervinelin! What exactly kind of magnet do you use? Like this one? aliexpress.com/item/50pcs-8mmx5mm-Strong-Round-Cylinder-Magnets-8x5-mm-Rare-Earth-Neodymium-N52-Permanent-Magnet-Powerful-Magnet/32789112260.html I’m asking because link in your BOM is on 5x5mm magnet.

I ordered this, U-JOVAN 20pcs N35 5 x 8 mm Mini Strong Round Cylinder Neodymium Magnets Disc Rare Earth Manget 5*8mm

(from AliExpress Android) but it’s not here yet.

In the meanwhile I stacked my 5x5 with 3pcs of 1mm magnets to get 5x8mm.

1 Like

is it possible to build yours without the custom pcb? good job btw

I think so, might be a little tricky keeping everything together but I suppose it should be doable.

1 Like

I forked SolidGeek’s Github page, all available files for my trigger style remote are now available including the PCB files (I believe you can order it yourself from EasyEDA with the links).

5 Likes

as soon as i confirm my remote is working im gonna try your version too. pcbs cost $12 + shipping thanks for the contribution i feel like trigger style is better for me because original case is somewhat ucomfortable for small hands. holding down the trigger and braking at the same time is somewhat difficult

1 Like

come back with updates when you would have some, i’m pretty interested. As well interested in buying steroids online. Any update would be much appreciated.

Hi guys,

another feature requeset… I like do switch on / off the lights of my board with the help of this remote. My idea was to add an extra point in the setting menu, where you can select light on or off. I tried to implement this in the code from @solidgeek but I’m an absolutely newbie in arduino programming. So I didn’t master it.

At the transmitter I added / changed this:

1. struct settings { byte triggerMode; …
int lightValue; };

2. const byte numOfSettings = 12;

String settingPages[numOfSettings][2] = { {“Trigger”, “”}, … {“Light”, “”} };

3. int settingRules[numOfSettings][3] { {0, 0, 3}, // 0 Killswitch, 1 cruise & 2 data toggle … {1, 0, 1}, // On or Off };

3. int getSettingValue(int index) { int value; switch (index) { case 0: value = remoteSettings.triggerMode; break; … case 11: value = remoteSettings.lightValue; break;

4. void setSettingValue(int index, int value) { switch (index) { case 0: remoteSettings.triggerMode = value; break; … case 11: remoteSettings.lightValue = value; break; }

5. void transmitToVesc() { // Transmit once every 50 millisecond if (millis() - lastTransmission >= 50) { lastTransmission = millis();
radio.write(&lightValue, sizeof(lightValue)); boolean sendSuccess = false; // Transmit the speed value (0-255). sendSuccess = radio.write(&throttle, sizeof(throttle)); radio.write(&lightValue, sizeof(lightValue));

And at the receiver I added / changed this:

1. int motorSpeed = 127; int timeoutMax = 500; int speedPin = 5; int lightPin = 7; int lightValue;

2. pinMode(speedPin, OUTPUT); analogWrite(speedPin, motorSpeed); pinMode(lightPin, OUTPUT);

3. void loop() { … getVescData(); // If transmission is available if (radio.available()) { … radio.read(&lightValue, sizeof(lightValue)); if (lightValue==1){ digitalWrite(lightPin, HIGH); } else { digitalWrite(lightPin, LOW); }

Compiling and upload to the arduinos works. However this code does not work in a proper way, unfortunately. I guess I made something wrong because the transmission of lightValue does not work. Could some please help me? I guess it is an easy task but I just to inexperienced.

THX in advance

Try using the </> button so your code is actually readable :wink:

1 Like

Okay, here is the full code in the right format:

TRANSMITTER:

#include <U8g2lib.h>
#include <Wire.h>
#include <SPI.h>
#include <EEPROM.h>
#include "RF24.h"
#include "VescUart.h"

// #define DEBUG

#ifdef DEBUG
  #define DEBUG_PRINT(x)  Serial.println (x)
  #include "printf.h"
#else
  #define DEBUG_PRINT(x)
#endif

int lightValue;

// Defining the type of display used (128x32)
U8G2_SSD1306_128X32_UNIVISION_1_HW_I2C u8g2(U8G2_R0, U8X8_PIN_NONE);

static unsigned char logo_bits[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7e, 0x00, 0x80, 0x3c,     0x01, 0xe0, 0x00, 0x07, 0x70, 0x18, 0x0e, 0x30, 0x18, 0x0c, 0x98, 0x99, 0x19, 0x80, 0xff, 0x01, 0x04, 0xc3, 0x20, 0x0c, 0x99, 0x30, 0xec, 0xa5, 0x37, 0xec, 0xa5, 0x37, 0x0c, 0x99, 0x30, 0x04, 0xc3, 0x20, 0x80, 0xff, 0x01, 0x98, 0x99, 0x19, 0x30, 0x18, 0x0c, 0x70, 0x18, 0x0e, 0xe0, 0x00, 0x07, 0x80, 0x3c, 0x01, 0x00, 0x7e,     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };

static unsigned char signal_transmitting_bits[] = {
  0x18, 0x00, 0x0c, 0x00, 0xc6, 0x00, 0x66, 0x00, 0x23, 0x06, 0x33, 0x0f,
  0x33, 0x0f, 0x23, 0x06, 0x66, 0x00, 0xc6, 0x00, 0x0c, 0x00, 0x18, 0x00
};

static unsigned char signal_connected_bits[] = {
  0x18, 0x00, 0x0c, 0x00, 0xc6, 0x00, 0x66, 0x00, 0x23, 0x06, 0x33, 0x09,
  0x33, 0x09, 0x23, 0x06, 0x66, 0x00, 0xc6, 0x00, 0x0c, 0x00, 0x18, 0x00
};

static unsigned char signal_noconnection_bits[] = {
 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x09,
  0x00, 0x09, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
};

// Defining struct to hold UART data.
struct vescValues {
  float ampHours;
  float inpVoltage;
  long rpm;
  long tachometerAbs;
};

// Defining struct to hold stats 
struct stats {
  float maxSpeed;
  long maxRpm;
  float minVoltage;
  float maxVoltage;
};

// Defining struct to hold setting values while remote is turned on.
struct settings {
  byte triggerMode;
  byte batteryType;
  byte batteryCells;
  byte motorPoles;
  byte motorPulley;
  byte wheelPulley;
  byte wheelDiameter;
  bool useUart;
  int minHallValue;
  int centerHallValue;
  int maxHallValue;
  int lightValue;
};

// Defining variables for speed and distance calculation
float gearRatio;
float ratioRpmSpeed;
float ratioPulseDistance;

byte currentSetting = 0;
const byte numOfSettings = 12;

String settingPages[numOfSettings][2] = {
  {"Trigger",         ""},
  {"Battery type",    ""},
  {"Battery cells",   "S"},
  {"Motor poles",     ""},
  {"Motor pulley",    "T"},
  {"Wheel pulley",    "T"},
  {"Wheel diameter",  "mm"},
  {"UART data",       ""},
  {"Throttle min",    ""},
  {"Throttle center", ""},
  {"Throttle max",    ""},
  {"Light",           ""}
};

// Setting rules format: default, min, max.
int settingRules[numOfSettings][3] {
  {0, 0, 3}, // 0 Killswitch, 1 cruise & 2 data toggle
  {0, 0, 1}, // 0 Li-ion & 1 LiPo
  {10, 0, 12},
  {14, 0, 250},
  {15, 0, 250},
  {40, 0, 250},
  {83, 0, 250},
  {1, 0, 1}, // Yes or no
  {0, 0, 1023},
  {512, 0, 1023},
  {1023, 0, 1023},
  {1, 0, 1} // on or off
};

struct vescValues data;
struct settings remoteSettings;

// Pin defination
const byte triggerPin = 4;
const int chargeMeasurePin = A1;
const int batteryMeasurePin = A2;
const int hallSensorPin = A3;

// Battery monitering
const float minVoltage = 3.2;
const float maxVoltage = 4.1;
const float refVoltage = 5.0; // Set to 4.5V if you are testing connected to USB, otherwise 5V (or the supply         voltage)

// Defining variables for Hall Effect throttle.
short hallMeasurement, throttle;
byte hallCenterMargin = 4;

// Defining variables for NRF24 communication
bool connected = false;
short failCount;
const uint64_t pipe = 0xE8E8F0F0E1LL; // If you change the pipe, you will need to update it on the receiver         to.
unsigned long lastTransmission;

// Defining variables for OLED display
char displayBuffer[20];
String displayString;
short displayData = 0;
unsigned long lastSignalBlink;
unsigned long lastDataRotation;

// Instantiating RF24 object for NRF24 communication
RF24 radio(9, 10);

// Defining variables for Settings menu
bool changeSettings = false;
bool changeSelectedSetting = false;

bool settingsLoopFlag = false;
bool settingsChangeFlag = false;
bool settingsChangeValueFlag = false;


void setup() {
  // setDefaultEEPROMSettings(); // Call this function if you want to reset settings

  #ifdef DEBUG
    Serial.begin(9600);
  #endif

  loadEEPROMSettings();

  pinMode(triggerPin, INPUT_PULLUP);
  pinMode(hallSensorPin, INPUT);
  pinMode(batteryMeasurePin, INPUT);

  u8g2.begin();

  drawStartScreen();

  if (triggerActive()) {
    changeSettings = true;
    drawTitleScreen("Remote Settings");
  }

  // Start radio communication
  radio.begin();
  radio.setPALevel(RF24_PA_MAX);
  radio.enableAckPayload();
  radio.enableDynamicPayloads();
  radio.openWritingPipe(pipe);

  #ifdef DEBUG
    printf_begin();
    radio.printDetails();
  #endif
}

void loop() {

  calculateThrottlePosition();

  if (changeSettings == true) {
        // Use throttle and trigger to change settings
    controlSettingsMenu();
  }
  else
  {
    // Use throttle and trigger to drive motors
    if (triggerActive())
    {
      throttle = throttle;
    }
    else
    {
      // 127 is the middle position - no throttle and no brake/reverse
      throttle = 127;
    }
    // Transmit to receiver
    transmitToVesc();
  }

  // Call function to update display and LED
  updateMainDisplay();
}

void controlSettingsMenu() {
  if (triggerActive()) {
    if (settingsChangeFlag == false) {

      // Save settings to EEPROM
      if (changeSelectedSetting == true) {
        updateEEPROMSettings();
      }

      changeSelectedSetting = !changeSelectedSetting;
      settingsChangeFlag = true;
    }
  } else {
    settingsChangeFlag = false;
  }

  if (hallMeasurement >= (remoteSettings.maxHallValue - 150) && settingsLoopFlag == false) {
    // Up
    if (changeSelectedSetting == true) {
      int val = getSettingValue(currentSetting) + 1;

      if (inRange(val, settingRules[currentSetting][1], settingRules[currentSetting][2])) {
        setSettingValue(currentSetting, val);
        settingsLoopFlag = true;
      }
    } else {
      if (currentSetting != 0) {
        currentSetting--;
        settingsLoopFlag = true;
      }
    }
  }
      else if (hallMeasurement <= (remoteSettings.minHallValue + 150) && settingsLoopFlag == false) {
    // Down
    if (changeSelectedSetting == true) {
      int val = getSettingValue(currentSetting) - 1;

      if (inRange(val, settingRules[currentSetting][1], settingRules[currentSetting][2])) {
        setSettingValue(currentSetting, val);
        settingsLoopFlag = true;
     }
    } else {
      if (currentSetting < (numOfSettings - 1)) {
        currentSetting++;
        settingsLoopFlag = true;
      }
    }
  } else if (inRange(hallMeasurement, remoteSettings.centerHallValue - 50, remoteSettings.centerHallValue + 50)) {
    settingsLoopFlag = false;
  }
}

void drawSettingNumber() {
  // Position on OLED
  int x = 2; int y = 10;

  // Draw current setting number box
  u8g2.drawRFrame(x + 102, y - 10, 22, 32, 4);

  // Draw current setting number
  displayString = (String)(currentSetting + 1);
  displayString.toCharArray(displayBuffer, displayString.length() + 1);

  u8g2.setFont(u8g2_font_profont22_tn);
  u8g2.drawStr(x + 108, 22, displayBuffer);
}

void drawSettingsMenu() {
  // Position on OLED
  int x = 0; int y = 10;

  // Draw setting title
  displayString = settingPages[currentSetting][0];
  displayString.toCharArray(displayBuffer, displayString.length() + 1);

  u8g2.setFont(u8g2_font_profont12_tr);
  u8g2.drawStr(x, y, displayBuffer);

  int val = getSettingValue(currentSetting);

  displayString = (String)val + "" + settingPages[currentSetting][1];
  displayString.toCharArray(displayBuffer, displayString.length() + 1);
  u8g2.setFont(u8g2_font_10x20_tr  );

  if (changeSelectedSetting == true) {
    u8g2.drawStr(x + 10, y + 20, displayBuffer);
  } else {
    u8g2.drawStr(x, y + 20, displayBuffer);
  }
}

void setDefaultEEPROMSettings() {
  for (int i = 0; i < numOfSettings; i++) {
    setSettingValue(i, settingRules[i][0]);
 }

  updateEEPROMSettings();
}

void loadEEPROMSettings() {
  // Load settings from EEPROM to custom struct
  EEPROM.get(0, remoteSettings);

  bool rewriteSettings = false;

      // Loop through all settings to check if everything is fine
  for (int i = 0; i < numOfSettings; i++) {
    int val = getSettingValue(i);

    if (! inRange(val, settingRules[i][1], settingRules[i][2])) {
      // Setting is damaged or never written. Rewrite default.
      rewriteSettings = true;
      setSettingValue(i, settingRules[i][0]);
    }
  }

  if (rewriteSettings == true) {
    updateEEPROMSettings();
  } else {
    // Calculate constants
    calculateRatios();
  }
}

// Write settings to the EEPROM then exiting settings menu.
void updateEEPROMSettings() {
  EEPROM.put(0, remoteSettings);
  calculateRatios();
}

// Update values used to calculate speed and distance travelled.
void calculateRatios() {
  gearRatio = (float)remoteSettings.motorPulley / (float)remoteSettings.wheelPulley;

  ratioRpmSpeed = (gearRatio * 60 * (float)remoteSettings.wheelDiameter * 3.14156) /     (((float)remoteSettings.motorPoles / 2) * 1000000); // ERPM to Km/h

  ratioPulseDistance = (gearRatio * (float)remoteSettings.wheelDiameter * 3.14156) /             
(((float)remoteSettings.motorPoles * 3) * 1000000); // Pulses to km travelled
}

// Get settings value by index (usefull when iterating through settings).
int getSettingValue(int index) {
  int value;
  switch (index) {
    case 0: value = remoteSettings.triggerMode;     break;
    case 1: value = remoteSettings.batteryType;     break;
    case 2: value = remoteSettings.batteryCells;    break;
    case 3: value = remoteSettings.motorPoles;      break;
    case 4: value = remoteSettings.motorPulley;     break;
    case 5: value = remoteSettings.wheelPulley;     break;
    case 6: value = remoteSettings.wheelDiameter;   break;
    case 7: value = remoteSettings.useUart;         break;
    case 8: value = remoteSettings.minHallValue;    break;
    case 9: value = remoteSettings.centerHallValue; break;
    case 10: value = remoteSettings.maxHallValue;   break;
  }
  return value;
}

// Set a value of a specific setting by index.
void setSettingValue(int index, int value) {
  switch (index) {
    case 0: remoteSettings.triggerMode = value;     break;
    case 1: remoteSettings.batteryType = value;     break;
    case 2: remoteSettings.batteryCells = value;    break;
    case 3: remoteSettings.motorPoles = value;      break;
    case 4: remoteSettings.motorPulley = value;     break;
    case 5: remoteSettings.wheelPulley = value;     break;
    case 6: remoteSettings.wheelDiameter = value;   break;
    case 7: remoteSettings.useUart = value;         break;
    case 8: remoteSettings.minHallValue = value;    break;
    case 9: remoteSettings.centerHallValue = value; break;
    case 10: remoteSettings.maxHallValue = value;   break;
  }
}

// Check if an integer is within a min and max value
bool inRange(int val, int minimum, int maximum) {
  return ((minimum <= val) && (val <= maximum));
}

// Return true if trigger is activated, false otherwice
boolean triggerActive() {
  if (digitalRead(triggerPin) == LOW)
    return true;
  else
    return false;
}

// Function used to transmit the throttle value, and receive the VESC realtime data.
void transmitToVesc() {
  // Transmit once every 50 millisecond
  if (millis() - lastTransmission >= 50) {

    lastTransmission = millis();

    boolean sendSuccess = false;
    // Transmit the speed value (0-255).
    sendSuccess = radio.write(&throttle, sizeof(throttle));

    radio.write(&lightValue, sizeof(lightValue));

    // Listen for an acknowledgement reponse (return of VESC data).
    while (radio.isAckPayloadAvailable()) {
      radio.read(&data, sizeof(data));
  
    }

    if (sendSuccess == true)
    {
      // Transmission was a succes
      failCount = 0;
      sendSuccess = false;

      DEBUG_PRINT("Transmission succes");
    } else {
      // Transmission was not a succes
      failCount++;

      DEBUG_PRINT("Failed transmission");
    }

    // If lost more than 5 transmissions, we can assume that connection is lost.
    if (failCount < 5) {
     connected = true;
    } else {
      connected = false;
    }
  }
}

void calculateThrottlePosition() {
  // Hall sensor reading can be noisy, lets make an average reading.
  int total = 0;
  for (int i = 0; i < 10; i++) {
    total += analogRead(hallSensorPin);
  }
  hallMeasurement = total / 10;

  DEBUG_PRINT( (String)hallMeasurement );

  if (hallMeasurement >= remoteSettings.centerHallValue) {
    throttle = constrain(map(hallMeasurement, remoteSettings.centerHallValue,     remoteSettings.maxHallValue, 127, 255), 127, 255);
  } else {
throttle = constrain(map(hallMeasurement, remoteSettings.minHallValue, remoteSettings.centerHallValue, 0, 127), 0, 127);
  }

  // removeing center noise
  if (abs(throttle - 127) < hallCenterMargin) {
    throttle = 127;
  }
}

// Function used to indicate the remotes battery level.
int batteryLevel() {
  float voltage = batteryVoltage();

  if (voltage <= minVoltage) {
    return 0;
  } else if (voltage >= maxVoltage) {
    return 100;
  } else {
    return (voltage - minVoltage) * 100 / (maxVoltage - minVoltage);
  }
}

// Function to calculate and return the remotes battery voltage.
float batteryVoltage() {
  float batteryVoltage = 0.0;
  int total = 0;

  for (int i = 0; i < 10; i++) {
    total += analogRead(batteryMeasurePin);
  }

  batteryVoltage = (refVoltage / 1024.0) * ((float)total / 10.0);

  return batteryVoltage;
}

void updateMainDisplay() {

  u8g2.firstPage();
  do {

    if (changeSettings == true) {
      drawSettingsMenu();
      drawSettingNumber();
    } else {
      drawThrottle();
      rawPage();
      drawBatteryLevel();
      drawSignal();
    }

  } while ( u8g2.nextPage() );
}

void drawStartScreen() {
  u8g2.firstPage();
  do {
    u8g2.drawXBM( 4, 4, 24, 24, logo_bits);

    displayString = "Esk8 remote";
    displayString.toCharArray(displayBuffer, 12);
    u8g2.setFont(u8g2_font_helvR10_tr  );
    u8g2.drawStr(34, 22, displayBuffer);
  } while ( u8g2.nextPage() );
  delay(1500);
}

void drawTitleScreen(String title) {
  u8g2.firstPage();
  do {
    title.toCharArray(displayBuffer, 20);
    u8g2.setFont(u8g2_font_helvR10_tr  );
    u8g2.drawStr(12, 20, displayBuffer);
  } while ( u8g2.nextPage() );
  delay(1500);
}

void drawPage() {
  int decimals;
  float value;
  String suffix;
  String prefix;

  int first, last;

  int x = 0;
  int y = 16;

  // Rotate the realtime data each 4s.
  if ((millis() - lastDataRotation) >= 4000) {

    lastDataRotation = millis();
    displayData++;

    if (displayData > 2) {
      displayData = 0;
    }
  }

  switch (displayData) {
    case 0:
      value = ratioRpmSpeed * data.rpm;
     suffix = "KMH";
     prefix = "SPEED";
      decimals = 1;
      break;
    case 1:
      value = ratioPulseDistance * data.tachometerAbs;
      suffix = "KM";
      prefix = "DISTANCE";
      decimals = 2;
     break;
    case 2:
      value = data.inpVoltage;
      suffix = "V";
      prefix = "BATTERY";
      decimals = 1;
     break;
  }

  // Display prefix (title)
  displayString = prefix;
  displayString.toCharArray(displayBuffer, 10);
  u8g2.setFont(u8g2_font_profont12_tr);
  u8g2.drawStr(x, y - 1, displayBuffer);

  // Split up the float value: a number, b decimals.
  first = abs(floor(value));
  last = value * pow(10, 3) - first * pow(10, 3);

  // Add leading zero
  if (first <= 9) {
    displayString = "0" + (String)first;
  } else {
    displayString = (String)first;
  }

  // Display numbers
  displayString.toCharArray(displayBuffer, 10);
  u8g2.setFont(u8g2_font_logisoso22_tn );
  u8g2.drawStr(x + 55, y + 13, displayBuffer);

  // Display decimals
  displayString = "." + (String)last;
  displayString.toCharArray(displayBuffer, decimals + 2);
  u8g2.setFont(u8g2_font_profont12_tr);
  u8g2.drawStr(x + 86, y - 1, displayBuffer);

  // Display suffix
  displayString = suffix;
  displayString.toCharArray(displayBuffer, 10);
  u8g2.setFont(u8g2_font_profont12_tr);
  u8g2.drawStr(x + 86 + 2, y + 13, displayBuffer);

}

void drawThrottle() {
  int x = 0;
  int y = 18;

  // Draw throttle
  u8g2.drawHLine(x, y, 52);
  u8g2.drawVLine(x, y, 10);
  u8g2.drawVLine(x + 52, y, 10);
  u8g2.drawHLine(x, y + 10, 5);
  u8g2.drawHLine(x + 52 - 4, y + 10, 5);

  if (throttle >= 127) {
    int width = map(throttle, 127, 255, 0, 49);

    for (int i = 0; i < width; i++) {
      //if( (i % 2) == 0){
      u8g2.drawVLine(x + i + 2, y + 2, 7);
      //}
    }
  } else {
    int width = map(throttle, 0, 126, 49, 0);
    for (int i = 0; i < width; i++) {
      //if( (i % 2) == 0){
      u8g2.drawVLine(x + 50 - i, y + 2, 7);
      //}
    }

  }
}

bool signalBlink = false;

void drawSignal() {
  // Position on OLED
  int x = 114; int y = 17;

  if (connected == true) {
    if (triggerActive()) {
      u8g2.drawXBM(x, y, 12, 12, signal_transmitting_bits);
    } else {
      u8g2.drawXBM(x, y, 12, 12, signal_connected_bits);
    }
  } else {
    if (millis() - lastSignalBlink > 500) {
      signalBlink = !signalBlink;
      lastSignalBlink = millis();
    }

    if (signalBlink == true) {
      u8g2.drawXBM(x, y, 12, 12, signal_connected_bits);
    } else {
      u8g2.drawXBM(x, y, 12, 12, signal_noconnection_bits);
    }
  }
}

void drawBatteryLevel() {
  int level = batteryLevel();

  // Position on OLED
  int x = 108; int y = 4;

  u8g2.drawFrame(x + 2, y, 18, 9);
  u8g2.drawBox(x, y + 2, 2, 5);

  for (int i = 0; i < 5; i++) {
    int p = round((100 / 5) * i);
    if (p <= level)
    {
      u8g2.drawBox(x + 4 + (3 * i), y + 2, 2, 5);
    }
  }
}

and RECEIVER:

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

#define SERIALO
#define SERIALIO Serial

struct vescValues {
  float ampHours;
  float inpVoltage;
  long rpm;
  long tachometerAbs;
};

RF24 radio(9, 10);
const uint64_t pipe = 0xE8E8F0F0E1LL;

bool recievedData = false;
uint32_t lastTimeReceived = 0;

int motorSpeed = 127;
int timeoutMax = 500;
int speedPin = 5;
int lightPin = 7;
int lightValue;

struct bldcMeasure measuredValues;

struct vescValues data;
unsigned long lastDataCheck;

void setup() {
  SERIALIO.begin(115200);

  radio.begin();
  radio.enableAckPayload();
  radio.enableDynamicPayloads();
  radio.openReadingPipe(1, pipe);
  radio.startListening();

  pinMode(speedPin, OUTPUT);
  analogWrite(speedPin, motorSpeed);
  pinMode(lightPin, OUTPUT);
}

void loop() {


  getVescData();
  // If transmission is available
  if (radio.available())
  {
    // The next time a transmission is received on pipe, the data in gotByte will be sent back in the                     acknowledgement (this could later be changed to data from VESC!)
    radio.writeAckPayload(pipe, &data, sizeof(data));

    // Read the actual message
    radio.read(&motorSpeed, sizeof(motorSpeed));   
    recievedData = true;

    radio.read(&lightValue, sizeof(lightValue));

    if (lightValue==1){
      digitalWrite(lightPin, HIGH);
    }
    else {
      digitalWrite(lightPin, LOW);
    }
  }

  if (recievedData == true)
  {
    // A speed is received from the transmitter (remote).

    lastTimeReceived = millis();
    recievedData = false;

    // Write the PWM signal to the ESC (0-255).
    analogWrite(speedPin, motorSpeed);
  }
  else if ((millis() - lastTimeReceived) > timeoutMax)
  {
    // No speed is received within the timeout limit.
    motorSpeed = 127;
    analogWrite(speedPin, motorSpeed);
  }
}

void getVescData() {

  if (millis() - lastDataCheck >= 250) {

    lastDataCheck = millis();

    // Only transmit what we need
    if (VescUartGetValue(measuredValues)) {
      data.ampHours = measuredValues.ampHours;
      data.inpVoltage = measuredValues.inpVoltage;
      data.rpm = measuredValues.rpm;
      data.tachometerAbs = measuredValues.tachometerAbs;
    } else {
      data.ampHours = 0.0;
      data.inpVoltage = 0.0;
      data.rpm = 0;
      data.tachometerAbs = 0;
    }
  }
}
4 Likes

Thanks for that hint… unfortunately there isn’t such a mod :frowning:

I believe the problem in your code is that the receiver never knows if its a lightValue or a throttle value it receives. I would add both throttle and lightvalue to a struct, and then transmit that whole struct. In this way you only have to do one radio.write(&package, sizeof(package)).

struct transmit {
  int throttle;
  bool light;
} package;

And then of course save the lightValue to the struct before transmitting it:

package.throttle = throttle;
package.light = lightValue;

sendSuccess = radio.write(&package, sizeof(package));
6 Likes

Cap? what cap? And where should it be?

THANKS!

What should I write in the receiver code? This does not work :frowning:

 radio.read(&package, sizeof(package));
if (lightValue==1){
  digitalWrite(lightPin, HIGH);
}
else {
  digitalWrite(lightPin, LOW);
}

https://arduino-info.wikispaces.com/Nrf24L01-2.4GHz-HowTo <== nice colorful write up (although it appears site is closing soon).

“Solder a 100nF ceramic cap across the gnd and 3.3v pins direct on the nrf24l01+ modules!Some have used a 1uF to 10uF capacitor.”

SIDE NOTE; on any remote style used, be sure you have the right corresponding “nrf24 library” and change the OG pipe#.

1 Like