dc motor control with rotary encoder feedback and remote NRF24L01 control.

I have been playing with this for a few days and find myself going around in circles.

The plan is a UNO server receives the required rpm, (setRpm) from a remote UNO via NRF24L01. The server then sends pwm output via pin 6 to a esc to control the speed of the motor. A rotary IR slot type encoder is used to measure the speed and the server then adjusts the esc to maintain speed. Current RPM is transmitted back to the remote client for display.

I have tried a couple of different ways of adjusting the rpm as you can see by all the commented stuff in the code. I have also reformatted the layout due to errors I could not resolve. My current unfinished code is as follows.

Server

#include <RHReliableDatagram.h>
#include <RH_NRF24.h>
#include <SPI.h>

#define CLIENT_ADDRESS 1
#define SERVER_ADDRESS 2

// Singleton instance of the radio driver
RH_NRF24 driver;
// RH_NRF24 driver(8, 7);   // For RFM73 on Anarduino Mini

// Class to manage message delivery and receipt, using the driver declared above
RHReliableDatagram manager(driver, SERVER_ADDRESS);

int speedSensor = 2;
unsigned int rpm; // rpm reading
volatile byte pulses; // number of pulses
unsigned long timeold;
// number of pulses per revolution
// based on your encoder disc
unsigned int pulsesperturn = 36;
void counter()
{
  //Update count
  pulses++;
}
//int esc = 6;
int setRpm;
byte adjustEsc;
int multiplier;

void setup() {
  Serial.begin(9600);
  if (!manager.init())
    Serial.println("init failed");
  // Defaults after init are 2.402 GHz (channel 2), 2Mbps, 0dBm

  pinMode(speedSensor, INPUT);
  //  pinMode(esc, OUTPUT);
  analogWrite(6, 0); //Sets esc to off at start up.

  //Triggers on Falling Edge (change from HIGH to LOW)
  attachInterrupt(0, counter, FALLING);
  // Initialize
  pulses = 0;
  rpm = 0;
  timeold = 0;




}

void loop() {

  /* listenForClient();
    getRequiredRpm();
    getStartStop();
    switchOnOff();//Set relay open or closed to activate/deactivate esc.
    readRpm();//Read optosensor and adjust for Rpm
    adjustEsc();//Adjust Rpm to achieve required Rpm
    sendRpmtoClient();



    }

    void listenForClient() {

    }

    void getRequiredRpm() {*/
  setRpm = 20;

  /*}

    void getStartStop() {

    }

    void switchOnOff() {

    }

    void readRpm() {*/
  if (millis() - timeold >= 1000) {
    //Don't process interrupts during calculations
    detachInterrupt(0);
    rpm = ((60 * 1000 / pulsesperturn ) / (millis() - timeold) * pulses) / 2;
    timeold = millis();
    pulses = 0;
    Serial.print("RPM = ");
    Serial.println(rpm, DEC);
    //Restart the interrupt processing
    attachInterrupt(0, counter, FALLING);
  }

  //adjustEsc is used to control the esc to maintain the setRpm.
  //setRpm divided by the rpm returns the factor by which the
  //previous adjustEsc must be multiplied by to maintain setRpm.
  //void adjustEsc() {
  //setRpm=20;


  if (rpm == 0)
  {
    rpm = 1;
  }
  /*
    if(adjustEsc==0){
    adjustEsc=setRpm*5;
    }
  */
  //multiplier = setRpm /= rpm;
  //multiplier = max(multiplier, 0);
  //adjustEsc = adjustEsc*multiplier;
  if (millis() - timeold >= 900) {
    if (rpm < setRpm) {
      adjustEsc = adjustEsc + 1;
    }
    else if (rpm > setRpm) {
      adjustEsc = adjustEsc - 1;
    }
    else {

    }
  }
  adjustEsc = min(adjustEsc, 255);
  adjustEsc = max(adjustEsc, 0);

  /*if (adjustEsc>255)
    {
    adjustEsc=255;
    }
    if (adjustEsc<1)
    {
    adjustEsc = 0;
    }*/
  analogWrite (6, adjustEsc);
  Serial.print("adjustEsc ");
  Serial.print(adjustEsc);
  Serial.print("  setRpm ");
  Serial.print(setRpm);
  Serial.print("  multiplier ");
  Serial.print(multiplier);
  Serial.print("  rpm ");
  Serial.println(rpm);
  // delay(100);


  //void sendRpmtoClient() {
  char msg[4];
  itoa(rpm, msg, 10);

  driver.send((uint8_t *)msg, strlen(msg));
  driver.waitPacketSent();
}

Client

#include <LiquidCrystal.h>

LiquidCrystal lcd(8, 9, 4, 5, 6, 7);

// define some values used by the panel and buttons

int lcd_key     = 0;
int adc_key_in  = 0;

#define btnRIGHT  0
#define btnUP     1
#define btnDOWN   2
#define btnLEFT   3
#define btnSELECT 4
#define btnNONE   5

int read_LCD_buttons() {              // read the buttons
  adc_key_in = analogRead(0);       // read the value from the sensor

  if (adc_key_in > 1000) return btnNONE;

  if (adc_key_in < 50)   return btnRIGHT;
  if (adc_key_in < 195)  return btnUP;
  if (adc_key_in < 380)  return btnDOWN;
  if (adc_key_in < 555)  return btnLEFT;
  if (adc_key_in < 790)  return btnSELECT;

  return btnNONE;                // when all others fail, return this.

#include <RHReliableDatagram.h>
#include <RH_NRF24.h>
#include <SPI.h>

#define CLIENT_ADDRESS 1
#define SERVER_ADDRESS 2

  // Singleton instance of the radio driver
  RH_NRF24 driver;
  // RH_NRF24 driver(8, 7);   // For RFM73 on Anarduino Mini

  // Class to manage message delivery and receipt, using the driver declared above
  RHReliableDatagram manager(driver, CLIENT_ADDRESS);


  void setup() {

    lcd.begin(16, 2);
    lcd.setCursor(3, 0);
    lcd.print("Lava Valley");
    lcd.setCursor(5, 1);
    lcd.print("Produce");
    delay(3000);
    lcd.clear();
    lcd.print("Seedr Controller");
    lcd.setCursor(1, 1);
    lcd.print("by Chris Dalby");
    delay(5000);
    lcd.clear();
    lcd.print("Set RPM");
    Serial.begin(9600);
    if (!manager.init())
      Serial.println("init failed");
    // Defaults after init are 2.402 GHz (channel 2), 2Mbps, 0dBm

  }

  void loop() {
    

  
  uint8_t len = sizeof(buf);
    uint8_t from;   
    if (manager.recvfromAckTimeout(buf, &len, 2000, &from))
 
Serial.println((char*)buf);
  
 // void sendDatatoServer(){
char msg[4];
  itoa(data, msg, 10);

  driver.send((uint8_t *)msg, strlen(msg));
  driver.waitPacketSent();
}

I have very crude control over the rpm at present with a long lag between speed changes due to the timing. I thought I could quickly resolve adjustments to the esc by multiplying the esc pwm output by the difference between setRpm and rpm but I could not get any meaningful output from this approach although I still think it is a better option.

Obviously I am a noob and find myself in need of guidance from anyone who has the time and patience to spare.

I have very crude control over the rpm at present with a long lag between speed changes due to the timing.

Due to the timing of what?

What do client and server mean? Which one is doing all the work? Which one is having problems?

    detachInterrupt(0);
    rpm = ((60 * 1000 / pulsesperturn ) / (millis() - timeold) * pulses) / 2;
    timeold = millis();
    pulses = 0;
    Serial.print("RPM = ");
    Serial.println(rpm, DEC);
    //Restart the interrupt processing
    attachInterrupt(0, counter, FALLING);

Detaching the interrupt handler is wrong. Disable interrupts. Copy the important data. Enable interrupts. Perform the calculations.

  driver.send((uint8_t *)msg, strlen(msg));
  driver.waitPacketSent();

Waiting for the packet to be sent can’t be a good thing. Sending the message on EVERY pass through loop can’t be a good thing.

volatile byte pulses; // number of pulses
unsigned long timeold;
// number of pulses per revolution
// based on your encoder disc
unsigned int pulsesperturn = 36;

With 36 pulses per turn, the byte will overflow in less than 8 turns. 8 turns per second is 480 RPM. 480 RPM is very slow for something controlled by an ESC.

Many more details about your project need to be forthcoming.

Hi Paul, thanks for your quick response.

PaulS: Due to the timing of what?

The 1000 ms between rpm reads and 900ms between esc writes.

PaulS: What do client and server mean? Which one is doing all the work? Which one is having problems?

Client and server are taken from NRF24L01 example codes. Server is connected to the dc motor and handles the adjustment to esc, on/off switch, and transmits rpm to client. Client is hand held remote where current rpm is received from server and displayed on lcd, required rpm and on/off state is selected and then transmitted to server.

PaulS:    detachInterrupt(0);    rpm = ((60 * 1000 / pulsesperturn ) / (millis() - timeold) * pulses) / 2;    timeold = millis();    pulses = 0;    Serial.print("RPM = ");    Serial.println(rpm, DEC);    //Restart the interrupt processing    attachInterrupt(0, counter, FALLING);

Detaching the interrupt handler is wrong. Disable interrupts. Copy the important data. Enable interrupts. Perform the calculations.

Copied from here, http://arduinoprojects101.com/arduino-rpm-counter-tachometer/rpm and here, http://www.electroschematics.com/12275/motor-speed-sensor-module-circuit/ I am reading up on what you have suggested, thanks.

PaulS:  driver.send((uint8_t *)msg, strlen(msg));  driver.waitPacketSent();

Waiting for the packet to be sent can't be a good thing. Sending the message on EVERY pass through loop can't be a good thing.

OK, I can see that now, thanks.

PaulS: volatile byte pulses; // number of pulses unsigned long timeold; // number of pulses per revolution // based on your encoder disc unsigned int pulsesperturn = 36;

With 36 pulses per turn, the byte will overflow in less than 8 turns. 8 turns per second is 480 RPM. 480 RPM is very slow for something controlled by an ESC.

The motor has a gearbox attached after which the encoder is placed. 60RPM is flat out.

PaulS: Many more details about your project need to be forthcoming.

The server is a uno connected to the dc motor via an esc (https://www.motiondynamics.com.au/12v-48v-dc-speed-control-single-direction-25a-pcb-model.html) reads the rpm of the motor via a Slot Type IR Optocoupler Speed Sensor Module LM393 and then adjusts the pwm out to the esc until it achieves the required rpm from the client.

The client consists of a uno with a dfrobot lcd button shield.

The server transmits the actual rpm to the client.

The client transmits the required rpm and the start/stop status to the server.

The radios are nrf24L01, low power versions.

The idea is the operator can set the required rpm , hit start, and the server will then make adjustments to the esc to maintain the required rpm under variable conditions. If the required rpm is unable to be maintained, a piezo buzzer will sound a warning to the operator that there may be a fault, ie a jam or mechanical failure.

I see what you mean. Thanks Paul.

 noInterrupts();
    rpm = ((60 * 1000 / pulsesperturn ) / (millis() - timeold) * pulses) / 2;
    timeold = millis();
    pulses = 0;
    Serial.print("RPM = ");
    Serial.println(rpm, DEC);
    //Restart the interrupt processing
    interrupts();

Except that you should not be doing Serial.print()s with interrupts disabled.

PaulS: Except that you should not be doing Serial.print()s with interrupts disabled.

Was looking at the placement of "interrupts()" as you replied. Thanks.

I am still unhappy with the speed of response to differences between setRpm and rpm. It currently counts its way up or down by 1 which makes for a very slow response. I tried: adjustEsc = adjustEsc * (setRpm / rpm); but with any of the values at 0, it would not start. I tried assigning values to adjustEsc, setRpm and rpm in setup but I still had no luck. The motor would stop and start at various rpm's on a 1 second cycle.

I tried: adjustEsc = adjustEsc * (setRpm / rpm); but with any of the values at 0, it would not start.

You need to cast setRpm or rpm to float, so you are not performing integer arithmetic, for one thing.

For another, you need to make sure rpm is not 0 before this call. Dividing by 0 is not a good thing.

IMHO this is the common problem of trying to fix multiple problems at the same time.

Forget about the nRF24 for a while and see if you can write a simple program to control the speed based on a value that you just build into the program (pretend it is a value that came by wireless).

Separately, write a program to receive a value by wireless and send one back. The pair of programs in this link may be sufficient for that.

When you can get both parts working separately it will be time to build a composite program. Keep all the different parts in separate functions to ease testing and debugging.

Writing to Serial or to an LCD is slow so do as little of it as possible so as to leave as many CPU cycles as possible for the important stuff.

...R

Hi,

Robin2:
IMHO this is the common problem of trying to fix multiple problems at the same time.

Forget about the nRF24 for a while and see if you can write a simple program to control the speed based on a value that you just build into the program (pretend it is a value that came by wireless).

Separately, write a program to receive a value by wireless and send one back. The pair of programs in this link may be sufficient for that.

When you can get both parts working separately it will be time to build a composite program. Keep all the different parts in separate functions to ease testing and debugging.

Writing to Serial or to an LCD is slow so do as little of it as possible so as to leave as many CPU cycles as possible for the important stuff.

…R

Ditto.
Plan and break your project to a number of smaller tasks and get each functioning first
C++ is structured text, so use its structure. also Auto-Format regularly while composing code.
Thanks… Tom… :slight_smile: