Radio code appears to be interfering with other functions.

Robin2:
The first line creates space for an array with 6 ints - that requires 12 bytes.
The second line define how many bytes are being received (and sent) - 4 bytes is required for 2 ints and that will easily fit in the space for 12 bytes.

I have created a bigger array than is needed - I like to be sure that there is plenty of space for the data to avoid memory corruption. It would probably be sufficient to define int ackMessg[2]

...R

This simple explanation got the radio issues sorted thanks Paul.
I had too few bytes for radioRxArrayLen.
Both radios now updating data and Tx/Rx as expected.
If only I had listened to you weeks ago :wink:

Ummmm, I do have a couple of threads going at the moment, regarding different and unrelated, (I hope), problems. Since my other problem with a countdown timer did not really relate to this thread, I started another. Did I break protocol here?

Many thanks Robin2

Unfortunately, while I have great radio comms now, it has interferred with the PID control on the master.

The txIntervalMillis creates a pulse in the motor control circuit by interrupting the PID updates.

I tried different txIntervalMillis, including 0 but no success.

You have not really provided any information about the txIntervalMillis interrupting the PID updates.

Obviously when it is time to do the wireless stuff the Arduino will take a little longer to complete a loop().

If that causes a problem for the PID system either the PID system is poorly designed or the Arduino does not have the capacity for the job.

Due to lack of information I have no idea if there might be other considerations.

...R

The server end consists of a geared motor connected to an esc which is in turn controlled by the pwm output from pin 6 on a Nano.
The output shaft on the motor has a disc with 36 slots and an LM393 slot type optocoupler connected to pin 2 of the nano.
A ping sensor is connected to pins 3 & 4.
There will be a relay and current sensor connected at some point but not yet.

Latest code.
Master:

/*
  Arduino Nano
  Pin allocation:
  D1
  D2 = speed sensor (interrupt 0)
  D3 = Ping trigger
  D4 = Ping Echo
  D5 = ESC relay
  D6 = ESC PWM out
  D7 = Radio CE
  D8 = Radio CSN
  D9 =
  D10 =
  D11 = Radio MOSI
  D12 = Radio MISO
  D13 = Radio SCK
  A0 = Current sensor
  A1 =
  A2 =
  A3 =
  A4 =
  A5 =
  A6 =
  A7 =
*/


//*********Height Sensor Stuff*********
#include <NewPing.h>
#define TRIGGER_PIN  3  // Arduino pin tied to trigger pin on the ultrasonic sensor.
#define ECHO_PIN     4  // Arduino pin tied to echo pin on the ultrasonic sensor.
#define MAX_DISTANCE 400 // Maximum distance we want to ping for (in centimeters). Maximum sensor distance is rated at 400-500cm.

NewPing sonar(TRIGGER_PIN, ECHO_PIN, MAX_DISTANCE); // NewPing setup of pins and maximum distance.

int dist;

//*****Motor Controller Stuff******
#include <PID_v2.h>

//Define Variables we'll be connecting to
double Setpoint, Input, Output;
//Specify the links and initial tuning parameters
PID myPID(&Input, &Output, &Setpoint, 0.4, 5.6, 0, DIRECT);

#define PWMpin  6

volatile unsigned long timeX = 1;
int PulsesPerRevolution = 36;
int MaxRPM = 400;
volatile int Counts = 1;
double PulsesPerMinute;
volatile unsigned long LastTime;
volatile int PulseCtr;
unsigned long Counter;
int startRamp;
unsigned long Time;
int rpm;
int printRpm; // Averaged over several readings to smooth it out.
volatile int rpmArray[5] = {0, 0, 0, 0, 0}; // For printRpm

//********Radio Stuff*******
#include <nRF24L01.h>
#include <RF24.h>
#include <SPI.h>

#include <nRF24L01.h>
#include <RF24.h>

#define CE_PIN   7
#define CSN_PIN 8

// NOTE: the "LL" at the end of the constant is "LongLong" type
// These are the IDs of each of the slaves
const uint64_t slaveID[2] = {0xE8E8F0F0E1LL, 0xE8E8F0F0E2LL} ;

RF24 radio(CE_PIN, CSN_PIN); // Create a Radio

int radioTxArray[2];

unsigned long currentMillis;
unsigned long prevMillis;
unsigned long txIntervalMillis = 1000;
int txVal = 0;
int radioRxArray[6];
//0 = motor on/off, 1 = setPoint, 2 = , 3 = Height, 4 = Cal, 5 = Dist
byte radioRxArrayLen = 12; // NB this 4 is the number of bytes in the 2 ints that will be recieved


bool isStarted = false;
bool isCal = false;
bool isDist = false;
bool go = false;

void setup() {
  // note that 1666666.67 = (60 seonds * 1000000 microseconds)microseconds in a minute / (36 / 9) pulses in 1 revolution
  PulsesPerMinute = (60 * 1000000) / (PulsesPerRevolution / Counts);

  pinMode(2, INPUT_PULLUP);
  // put your setup code here, to run once:
  Serial.begin(9600);
  Serial.println("PID controlled Seeder Master R0");
  delay(1000);
  //Digital Pin 2 Set As An Interrupt for tacho.
  attachInterrupt(0, sensorInterrupt, FALLING);

  //Motor PID control stuff.
  startRamp = 10;//map(PulsesPerRevolution , 1, MaxRPM, MaxRPM, 2);
  myPID.SetSampleTime(1);
  myPID.SetOutputLimits(40, (int) 255);
  PulseCtr = 0;
  myPID.SetMode(AUTOMATIC);
  analogWrite(PWMpin, 60);
  myPID.Compute();
  delay(11);
  myPID.Compute();

  //Radio stuff.
  radio.begin();
  radio.setDataRate( RF24_250KBPS );
  radio.enableAckPayload();
  radio.setRetries(3, 5); // delay, count

}

void loop() {
  // put your main code here, to run repeatedly:
  exchangeData();
  getSetPoint();
  getStartStop();
  getHeight();
  readMotorCurrent();
  switchOnOff();
  readRpm();
  debug();
  static unsigned long SpamTimer;

}

void exchangeData()//Send and receice radioTxArray and radioRxArray.
{

  currentMillis = millis();
  if (currentMillis - prevMillis >= txIntervalMillis) {

    radio.openWritingPipe(slaveID[0]); // calls the first slave
    // there could be a FOR loop to call several slaves in turn
    bool rslt;
    rslt = radio.write( radioTxArray, sizeof(radioTxArray) );
    Serial.print("\nRSLT (1 = success) ");
    Serial.println(rslt);
    Serial.print("Data Sent ");
    Serial.print(radioTxArray[0]);
    Serial.print("  ");
    Serial.println(radioTxArray[1]);
    if ( radio.isAckPayloadAvailable() ) {
      radio.read(radioRxArray, radioRxArrayLen);
      Serial.print("Acknowledge received: ");
      Serial.print(radioRxArray[0]); Serial.print("  ");
      Serial.print(radioRxArray[1]); Serial.print("  ");
      Serial.print(radioRxArray[2]); Serial.print("  ");
      Serial.print(radioRxArray[3]); Serial.print("  ");
      Serial.print(radioRxArray[4]); Serial.print("  ");
      Serial.print(radioRxArray[5]); Serial.print("  isStarted = ");
      Serial.println(isStarted);

    }
    prevMillis = millis();
  }

}

void getSetPoint()//Required rpm from hand controller
{
  if (isStarted == true) {
    Setpoint = radioRxArray[1];
    // Setpoint = 30;
  }
  else
  {
    Setpoint = 0;
    radioTxArray[0] = 0;
  }
}

void getStartStop()// Determine if motor should start on not using on/off and/or height switching from hand controller.
{
  if (radioRxArray[0] == 1) //On/off = on.
  {
    if (radioRxArray[3] == 0)//Height switching = off.
    {
      isStarted = true;
    }
    if (dist <= radioRxArray[5] == 1 && radioRxArray[3] == 1)// dist is less than switch point & Height switching = on.
    {
      isStarted = true;
    }
  }

  else
  {
    isStarted = false;
  }

}

void getHeight() //read ping sensor.
{

  unsigned int uS = sonar.ping(); // Send ping, get ping time in microseconds (uS).
  dist = (uS / US_ROUNDTRIP_CM); //raw distance in cm.



}

void readMotorCurrent()
{

}

void switchOnOff() //activate relay on pin 5 to turn esc on and off.
{
  if (isStarted == true)
  {
    digitalWrite(5, HIGH);

  }
}

void sensorInterrupt() // for tacho.
{
  static int Ctr;
  unsigned long Time;
  Ctr++;
  if (Ctr >= Counts) { // 36 / 4 = 9 so we are taking an average of 9 readings to use in our calculations
    Time = micros();
    timeX += (Time - LastTime); // this time is accumulative ovrer those 9 readings
    LastTime = Time;
    PulseCtr ++;
    Ctr = 0;
  }
}

void readRpm()
{

  cli ();         // clear interrupts flag
  Time = timeX;   // Make a copy so if an interrupt occurs timeX can be altered and not affect the results.
  timeX = 0;
  sei ();         // set interrupts flag
  if (PulseCtr > 0) {
    Input =  (double) (PulsesPerMinute /  (double)(( (unsigned long)Time ) *  (unsigned long)PulseCtr)); // double has more percision
    //   PulseCtr = 0; // set pulse Ctr to zero
    // debug();
    if (!myPID.Compute()); //Serial.println();

    analogWrite(PWMpin, Output);
    //
    Time = 0; // set time to zero to wait for the next rpm trigger.
    Counter += PulseCtr;
    PulseCtr = 0; // set pulse Ctr to zero
    // we are automatically adjusting the diviser to preserve processor time after calculating last rpm
    // starting at 0 RPM ~ 400+RPM we adjust the division of the pulses per revolution

    // Counts = 100; constrain(map((int)Input, startRamp, MaxRPM, 1, PulsesPerRevolution), 1, PulsesPerRevolution);
    PulsesPerMinute = (60.0 * 1000000.0) / (double)((double)PulsesPerRevolution / (double)Counts);
    //Fill rpm array with rpm readings and average them for display.
    rpmArray[0] = rpmArray[1];
    rpmArray[1] = rpmArray[2];
    rpmArray[2] = rpmArray[3];
    rpmArray[3] = rpmArray[4];
    rpmArray[4] = Input;
    //Last 5 Average RPM Counts Eqauls....
    printRpm = (rpmArray[0] + rpmArray[1] + rpmArray[2] + rpmArray[3] + rpmArray[4]) / 5;

    radioTxArray[0] = printRpm;//average sent to hand controller

  }
}

void debug()

{
  
}

Next post.

When the motor is turned on, I get this on serial.

RSLT (1 = success) 1
Data Sent 46  0
Acknowledge received: 1  34  0  0  0  80  isStarted = 1
 In   8.09 Setpt  34.00 /\T 129796.00 Kp  10.37 Ki  98.58 Kd         0 Out 108
 In  44.82 Setpt  34.00 /\T 38156.00 Kp  -4.33 Ki 101.83 Kd         0 Out 97
 In  43.30 Setpt  34.00 /\T 38100.00 Kp  -3.72 Ki  95.60 Kd         0 Out 91
 In  41.21 Setpt  34.00 /\T 39688.00 Kp  -2.89 Ki  90.89 Kd         0 Out 88
 In  36.71 Setpt  34.00 /\T 45440.00 Kp  -1.08 Ki  87.06 Kd         0 Out 85
 In  36.61 Setpt  34.00 /\T 45908.00 Kp  -1.04 Ki  84.69 Kd         0 Out 83
 In  61.19 Setpt  34.00 /\T 30780.00 Kp -10.88 Ki  69.90 Kd         0 Out 59
 In  24.81 Setpt  34.00 /\T 68108.00 Kp   3.67 Ki  72.44 Kd         0 Out 76

RSLT (1 = success) 1
Data Sent 48  0
Acknowledge received: 1  34  0  0  0  80  isStarted = 1
 In   7.08 Setpt  34.00 /\T 123784.00 Kp  10.77 Ki  91.10 Kd         0 Out 101
 In  38.23 Setpt  34.00 /\T 45604.00 Kp  -1.69 Ki  90.23 Kd         0 Out 88
 In  33.23 Setpt  34.00 /\T 46076.00 Kp   0.31 Ki  77.87 Kd         0 Out 78
 In  30.62 Setpt  34.00 /\T 61244.00 Kp   1.35 Ki  80.72 Kd         0 Out 82
 In  34.09 Setpt  34.00 /\T 54600.00 Kp  -0.04 Ki  81.29 Kd         0 Out 81
 In  33.83 Setpt  34.00 /\T 55080.00 Kp   0.07 Ki  81.20 Kd         0 Out 81
 In  33.26 Setpt  34.00 /\T 46016.00 Kp   0.29 Ki  81.26 Kd         0 Out 81

In = rpm, Out = pwm out to esc.

You can see there is a clear effect on the PID loop from the radio.

The code you posted does not seem to print the lines of data that you refer to. The problem is often where you don't expect it (otherwise you find it easily :slight_smile: )

All those print statements in exchangeData() will slow things down.

Could you ignore the first computation after reading wireless data?

...R

Robin2:
The code you posted does not seem to print the lines of data that you refer to. The problem is often where you don't expect it (otherwise you find it easily :slight_smile: )

All those print statements in exchangeData() will slow things down.

Could you ignore the first computation after reading wireless data?

...R

Thank you for your time Robin2, I appreciate it a lot.

The data is generated by the PID library and only comes through when it gets input from the rpm sensor.

You were right on the money with the print statements in exchangeData(). Got rid of them and the whole thing started to work again. PID's are way off but at least no delays. This, again, was so obvious when pointed out. I really must start thinking in simpler terms instead of assuming the problems are complex and beyond me.

Not sure which computation you mean when you refer to the one after wireless data.

moose4621:
Not sure which computation you mean when you refer to the one after wireless data.

From the examples you posted it seems that only one PID calculation is affected by reading the wireless. I just wondered if you could ignore that particular PID calcluation.

...R

At 1 sec intervals the effect on the PID is now negligable, although it may be of benefit at very low rpm. Not sure how to go about ignoring the computation after exchangeData though.

I still have a problem where the message from hand controller to master has approx 3 second lag. If I reduce

unsigned long txIntervalMillis = 1000;

the lag improves considerably but it then interferes with the PID but at least it indicates that the problem is at the server end.

Master code.

/*
  Arduino Nano
  Pin allocation:
  D1
  D2 = speed sensor (interrupt 0)
  D3 = Ping trigger
  D4 = Ping Echo
  D5 = ESC relay
  D6 = ESC PWM out
  D7 = Radio CE
  D8 = Radio CSN
  D9 =
  D10 =
  D11 = Radio
  D12 = Radio
  D13 = Radio
  A0 = Current sensor
  A1 =
  A2 =
  A3 =
  A4 =
  A5 =
  A6 =
  A7 =
*/


//*********Height Sensor Stuff*********
#include <NewPing.h>
#define TRIGGER_PIN  3  // Arduino pin tied to trigger pin on the ultrasonic sensor.
#define ECHO_PIN     4  // Arduino pin tied to echo pin on the ultrasonic sensor.
#define MAX_DISTANCE 400 // Maximum distance we want to ping for (in centimeters). Maximum sensor distance is rated at 400-500cm.

NewPing sonar(TRIGGER_PIN, ECHO_PIN, MAX_DISTANCE); // NewPing setup of pins and maximum distance.

int dist;

//*****Motor Controller Stuff******
#include <PID_v2.h>

//Define Variables we'll be connecting to
double Setpoint, Input, Output;
//Specify the links and initial tuning parameters
PID myPID(&Input, &Output, &Setpoint, 0.6, 6.2, .01, DIRECT);

#define PWMpin  6

volatile unsigned long timeX = 1;
int PulsesPerRevolution = 36;
int MaxRPM = 400;
volatile int Counts = 1;
double PulsesPerMinute;
volatile unsigned long LastTime;
volatile int PulseCtr;
unsigned long Counter;
int startRamp;
unsigned long Time;
int rpm;
int printRpm; // Averaged over several readings to smooth it out.
volatile int rpmArray[5] = {0, 0, 0, 0, 0}; // For printRpm

//********Radio Stuff*******
#include <nRF24L01.h>
#include <RF24.h>
#include <SPI.h>

#include <nRF24L01.h>
#include <RF24.h>

#define CE_PIN   7
#define CSN_PIN 8

// NOTE: the "LL" at the end of the constant is "LongLong" type
// These are the IDs of each of the slaves
const uint64_t slaveID[2] = {0xE8E8F0F0E1LL, 0xE8E8F0F0E2LL} ;

RF24 radio(CE_PIN, CSN_PIN); // Create a Radio

int radioTxArray[2];

unsigned long currentMillis;
unsigned long prevMillis;
unsigned long txIntervalMillis = 1000;
int txVal = 0;
int radioRxArray[6];
//0 = motor on/off, 1 = setPoint, 2 = , 3 = Height, 4 = Cal, 5 = Dist
byte radioRxArrayLen = 12; // NB this 4 is the number of bytes in the 2 ints that will be recieved


bool isStarted = false;
bool isCal = false;
bool isDist = false;
bool go = false;

void setup() {
  // note that 1666666.67 = (60 seonds * 1000000 microseconds)microseconds in a minute / (36 / 9) pulses in 1 revolution
  PulsesPerMinute = (60 * 1000000) / (PulsesPerRevolution / Counts);

  pinMode(2, INPUT_PULLUP);
  // put your setup code here, to run once:
  Serial.begin(9600);
  Serial.println("PID controlled Seeder Master R0");
  delay(1000);
  //Digital Pin 2 Set As An Interrupt for tacho.
  attachInterrupt(0, sensorInterrupt, FALLING);

  startRamp = 10;//map(PulsesPerRevolution , 1, MaxRPM, MaxRPM, 2);
  myPID.SetSampleTime(1);
  myPID.SetOutputLimits(40, (int) 255);
  PulseCtr = 0;
  myPID.SetMode(AUTOMATIC);
  analogWrite(PWMpin, 60);
  myPID.Compute();
  delay(11);
  myPID.Compute();

  radio.begin();
  radio.setDataRate( RF24_250KBPS );
  radio.enableAckPayload();
  radio.setRetries(3, 5); // delay, count

}

void loop() {
  // put your main code here, to run repeatedly:
  exchangeData();
  getSetPoint();
  getStartStop();
  getHeight();
  readMotorCurrent();
  switchOnOff();
  readRpm();
  debug();
  static unsigned long SpamTimer;

}

void exchangeData()
{

  currentMillis = millis();
  if (currentMillis - prevMillis >= txIntervalMillis) {

    radio.openWritingPipe(slaveID[0]); // calls the first slave
    // there could be a FOR loop to call several slaves in turn
    bool rslt;
    rslt = radio.write( radioTxArray, sizeof(radioTxArray) );

    if ( radio.isAckPayloadAvailable() ) {
      radio.read(radioRxArray, radioRxArrayLen);

    }
    prevMillis = millis();
  }

}

void getSetPoint()
{
  if (isStarted == true) {
    Setpoint = radioRxArray[1];
    // Setpoint = 30;
  }
  else
  {
    Setpoint = 0;
    radioTxArray[0] = 0;
  }
}

void getStartStop()
{
  if (radioRxArray[0] == 1) //start/stop button on.
  {
    if (radioRxArray[3] == 0) //height switch off.
    {
      isStarted = true;
    }
    if (dist <= radioRxArray[5] && radioRxArray[3] == 1) //if ping dist is less than dist set point and height switch is on.
    {
      isStarted = true;
    }
  }

  else
  {
    isStarted = false;
  }

}

void getHeight()
{

  unsigned int uS = sonar.ping(); // Send ping, get ping time in microseconds (uS).
  dist = (uS / US_ROUNDTRIP_CM); //raw distance in cm.



}

void readMotorCurrent()
{

}

void switchOnOff()
{
  if (isStarted == true)
  {
    digitalWrite(5, HIGH);

  }
  else
  {
    digitalWrite(5, LOW);
  }
}

void sensorInterrupt()
{
  static int Ctr;
  unsigned long Time;
  Ctr++;
  if (Ctr >= Counts) { // 36 / 4 = 9 so we are taking an average of 9 readings to use in our calculations
    Time = micros();
    timeX += (Time - LastTime); // this time is accumulative ovrer those 9 readings
    LastTime = Time;
    PulseCtr ++;
    Ctr = 0;
  }
}

void readRpm()
{

  cli ();         // clear interrupts flag
  Time = timeX;   // Make a copy so if an interrupt occurs timeX can be altered and not affect the results.
  timeX = 0;
  sei ();         // set interrupts flag
  if (PulseCtr > 0) {
    Input =  (double) (PulsesPerMinute /  (double)(( (unsigned long)Time ) *  (unsigned long)PulseCtr)); // double has more percision
    //   PulseCtr = 0; // set pulse Ctr to zero
    // debug();
    if (!myPID.Compute()); //Serial.println();

    analogWrite(PWMpin, Output);
    //
    Time = 0; // set time to zero to wait for the next rpm trigger.
    Counter += PulseCtr;
    PulseCtr = 0; // set pulse Ctr to zero
    // we are automatically adjusting the diviser to preserve processor time after calculating last rpm
    // starting at 0 RPM ~ 400+RPM we adjust the division of the pulses per revolution

    // Counts = 100; constrain(map((int)Input, startRamp, MaxRPM, 1, PulsesPerRevolution), 1, PulsesPerRevolution);
    PulsesPerMinute = (60.0 * 1000000.0) / (double)((double)PulsesPerRevolution / (double)Counts);
    //Fill rpm array with rpm readings and average them for display.
    rpmArray[0] = rpmArray[1];
    rpmArray[1] = rpmArray[2];
    rpmArray[2] = rpmArray[3];
    rpmArray[3] = rpmArray[4];
    rpmArray[4] = Input;
    //Last 5 Average RPM Counts Eqauls....
    printRpm = (rpmArray[0] + rpmArray[1] + rpmArray[2] + rpmArray[3] + rpmArray[4]) / 5;

    radioTxArray[0] = printRpm;//average sent to hand controller

  }
}

void debug()

{

  /*
    Serial.print("\nRSLT (1 = success) ");
    Serial.println(rslt);
    Serial.print("Data Sent ");
    Serial.print(radioTxArray[0]);
    Serial.print("  ");
    Serial.println(radioTxArray[1]);
  */
  /*
    char S[20];
    static unsigned long SpamTimer;
    if ((unsigned long)(millis() - SpamTimer) >= 100) {
    SpamTimer = millis();
    Serial.print(" rpm: "); Serial.print(Input );
    Serial.print(" motor: "); Serial.print(radioRxArray[0]);
    Serial.print(" setpoint: "); Serial.print(radioRxArray[1]);
    Serial.print(" height "); Serial.print(radioRxArray[3]);
    Serial.print(" cal: "); Serial.print(radioRxArray[4]);
    Serial.print(" dist: "); Serial.print(radioRxArray[5]);
    Serial.print(" ping sensor "); Serial.println(dist);
    }
  */
}

Robin2:
From the examples you posted it seems that only one PID calculation is affected by reading the wireless. I just wondered if you could ignore that particular PID calcluation.

...R

The more I think about this, the better it sounds. I would like to try it but how?

Perhaps I should have explained somewhere that I only had the 1 second interval for demo purposes. I normally run that code with an interval of 100 millisecs (i.e. about 10 times per second). At that rate I am not conscious of any latency.

Post the complete program and I will see if I can figure out how you might ignore a particular calculation - or why there are glitches in the first place.

...R

I think you are on track with the skip the input after transmission since the NEW PID routine does not require a fixed time interval (delta T). It only requires an instantaneous reading to calculate the next PID output. The minimal delay will not affect the calculation in any way

Try adding This:

// up top
bool Skip = false;   
// ----------------------
// in void exchangeData() when you are successful
Skip = true;
//-----------------------
// in void readRpm()

   if(!Skip) { //PID only needs an instantaneous reading and so if we skip a few no harm done since Delta T is no longer a fixed value
      if (!myPID.Compute()) Serial.println(); 
      analogWrite(PWMpin, Output);
   }
   else Skip = false;

Glad to be back, I missed the shift to this thread and I was hoping all was going well for you. It looks like you are close to completion. let me know if this works.

Thankyou both for the help.

How is your project going zhomeslice?

I added the changes you suggested zhomeslice and it seems to stop the interference from the radio.

However, when I reduce txIntervalMillis below about 300, the Motor start is very delayed and if I reduce it to 100, the motor will not start at all. 400 is about the minimum usable interval.

/*
  Arduino Nano
  Pin allocation:
  D1
  D2 = speed sensor (interrupt 0)
  D3 = Ping trigger
  D4 = Ping Echo
  D5 = ESC relay
  D6 = ESC PWM out
  D7 = Radio CE
  D8 = Radio CSN
  D9 =
  D10 =
  D11 = Radio
  D12 = Radio
  D13 = Radio
  A0 = Current sensor
  A1 =
  A2 =
  A3 =
  A4 =
  A5 =
  A6 =
  A7 =
*/


//*********Height Sensor Stuff*********
#include <NewPing.h>
#define TRIGGER_PIN  3  // Arduino pin tied to trigger pin on the ultrasonic sensor.
#define ECHO_PIN     4  // Arduino pin tied to echo pin on the ultrasonic sensor.
#define MAX_DISTANCE 400 // Maximum distance we want to ping for (in centimeters). Maximum sensor distance is rated at 400-500cm.

NewPing sonar(TRIGGER_PIN, ECHO_PIN, MAX_DISTANCE); // NewPing setup of pins and maximum distance.

int dist;

//*****Motor Controller Stuff******
#include <PID_v2.h>

//Define Variables we'll be connecting to
double Setpoint, Input, Output;
//Specify the links and initial tuning parameters
PID myPID(&Input, &Output, &Setpoint, 0.6, 6.2, .01, DIRECT);

#define PWMpin  6

volatile unsigned long timeX = 1;
int PulsesPerRevolution = 36;
int MaxRPM = 400;
volatile int Counts = 1;
double PulsesPerMinute;
volatile unsigned long LastTime;
volatile int PulseCtr;
unsigned long Counter;
int startRamp;
unsigned long Time;
int rpm;
int printRpm; // Averaged over several readings to smooth it out.
volatile int rpmArray[5] = {0, 0, 0, 0, 0}; // For printRpm

//********Radio Stuff*******
#include <nRF24L01.h>
#include <RF24.h>
#include <SPI.h>

#include <nRF24L01.h>
#include <RF24.h>

#define CE_PIN   7
#define CSN_PIN 8

// NOTE: the "LL" at the end of the constant is "LongLong" type
// These are the IDs of each of the slaves
const uint64_t slaveID[2] = {0xE8E8F0F0E1LL, 0xE8E8F0F0E2LL} ;

RF24 radio(CE_PIN, CSN_PIN); // Create a Radio

int radioTxArray[2];

unsigned long currentMillis;
unsigned long prevMillis;
unsigned long txIntervalMillis = 100;
int txVal = 0;
int radioRxArray[6];
//0 = motor on/off, 1 = setPoint, 2 = , 3 = Height, 4 = Cal, 5 = Dist
byte radioRxArrayLen = 12; // NB this 4 is the number of bytes in the 2 ints that will be recieved


bool isStarted = false;
bool isCal = false;
bool isDist = false;
bool go = false;
bool Skip = false;

void setup() {
  // note that 1666666.67 = (60 seonds * 1000000 microseconds)microseconds in a minute / (36 / 9) pulses in 1 revolution
  PulsesPerMinute = (60 * 1000000) / (PulsesPerRevolution / Counts);

  pinMode(2, INPUT_PULLUP);
  // put your setup code here, to run once:
  Serial.begin(9600);
  Serial.println("PID controlled Seeder Master R0");
  delay(1000);
  //Digital Pin 2 Set As An Interrupt for tacho.
  attachInterrupt(0, sensorInterrupt, FALLING);

  startRamp = 10;//map(PulsesPerRevolution , 1, MaxRPM, MaxRPM, 2);
  myPID.SetSampleTime(1);
  myPID.SetOutputLimits(40, (int) 255);
  PulseCtr = 0;
  myPID.SetMode(AUTOMATIC);
  analogWrite(PWMpin, 60);
  myPID.Compute();
  delay(11);
  myPID.Compute();

  radio.begin();
  radio.setDataRate( RF24_250KBPS );
  radio.enableAckPayload();
  radio.setRetries(3, 5); // delay, count
  rf24_pa_dbm_e{RF24_PA_MAX};

}

void loop() {
  // put your main code here, to run repeatedly:
  exchangeData();
  getSetPoint();
  getStartStop();
  getHeight();
  readMotorCurrent();
  switchOnOff();
  readRpm();
  debug();
  static unsigned long SpamTimer;

}

void exchangeData()
{

  currentMillis = millis();
  if (currentMillis - prevMillis >= txIntervalMillis) {

    radio.openWritingPipe(slaveID[0]); // calls the first slave
    // there could be a FOR loop to call several slaves in turn
    bool rslt;
    rslt = radio.write( radioTxArray, sizeof(radioTxArray) );

    if ( radio.isAckPayloadAvailable() ) {
      radio.read(radioRxArray, radioRxArrayLen);
      Skip = true;
    }
    prevMillis = millis();
  }

}

void getSetPoint()
{
  if (isStarted == true) {
    Setpoint = radioRxArray[1];
    // Setpoint = 30;
  }
  else
  {
    Setpoint = 0;
    radioTxArray[0] = 0;
  }
}

void getStartStop()
{
  if (radioRxArray[0] == 1) //start/stop button on.
  {
    if (radioRxArray[3] == 0) //height switch off.
    {
      isStarted = true;
    }
    if (dist <= radioRxArray[5] && radioRxArray[3] == 1) //if ping dist is less than dist set point and height switch is on.
    {
      isStarted = true;
    }
  }

  else
  {
    isStarted = false;
  }

}

void getHeight()
{

  unsigned int uS = sonar.ping(); // Send ping, get ping time in microseconds (uS).
  dist = (uS / US_ROUNDTRIP_CM); //raw distance in cm.



}

void readMotorCurrent()
{

}

void switchOnOff()
{
  if (isStarted == true)
  {
    digitalWrite(5, HIGH);

  }
  else
  {
    digitalWrite(5, LOW);
  }
}

void sensorInterrupt()
{
  static int Ctr;
  unsigned long Time;
  Ctr++;
  if (Ctr >= Counts) { // 36 / 4 = 9 so we are taking an average of 9 readings to use in our calculations
    Time = micros();
    timeX += (Time - LastTime); // this time is accumulative ovrer those 9 readings
    LastTime = Time;
    PulseCtr ++;
    Ctr = 0;
  }
}

void readRpm()
{

  cli ();         // clear interrupts flag
  Time = timeX;   // Make a copy so if an interrupt occurs timeX can be altered and not affect the results.
  timeX = 0;
  sei ();         // set interrupts flag
  if (PulseCtr > 0) {
    Input =  (double) (PulsesPerMinute /  (double)(( (unsigned long)Time ) *  (unsigned long)PulseCtr)); // double has more percision
    //   PulseCtr = 0; // set pulse Ctr to zero
    // debug();
    if (!Skip) { //PID only needs an instantaneous reading and so if we skip a few no harm done since Delta T is no longer a fixed value
      if (!myPID.Compute()) Serial.println();
      analogWrite(PWMpin, Output);
    }
    else Skip = false;

    analogWrite(PWMpin, Output);
    //
    Time = 0; // set time to zero to wait for the next rpm trigger.
    Counter += PulseCtr;
    PulseCtr = 0; // set pulse Ctr to zero
    // we are automatically adjusting the diviser to preserve processor time after calculating last rpm
    // starting at 0 RPM ~ 400+RPM we adjust the division of the pulses per revolution

    // Counts = 100; constrain(map((int)Input, startRamp, MaxRPM, 1, PulsesPerRevolution), 1, PulsesPerRevolution);
    PulsesPerMinute = (60.0 * 1000000.0) / (double)((double)PulsesPerRevolution / (double)Counts);
    //Fill rpm array with rpm readings and average them for display.
    rpmArray[0] = rpmArray[1];
    rpmArray[1] = rpmArray[2];
    rpmArray[2] = rpmArray[3];
    rpmArray[3] = rpmArray[4];
    rpmArray[4] = Input;
    //Last 5 Average RPM Counts Eqauls....
    printRpm = (rpmArray[0] + rpmArray[1] + rpmArray[2] + rpmArray[3] + rpmArray[4]) / 5;

    radioTxArray[0] = printRpm;//average sent to hand controller

  }
}

void debug()

{

  /*
    Serial.print("\nRSLT (1 = success) ");
    Serial.println(rslt);
    Serial.print("Data Sent ");
    Serial.print(radioTxArray[0]);
    Serial.print("  ");
    Serial.println(radioTxArray[1]);
  */
  /*
    char S[20];
    static unsigned long SpamTimer;
    if ((unsigned long)(millis() - SpamTimer) >= 100) {
    SpamTimer = millis();
    Serial.print(" rpm: "); Serial.print(Input );
    Serial.print(" motor: "); Serial.print(radioRxArray[0]);
    Serial.print(" setpoint: "); Serial.print(radioRxArray[1]);
    Serial.print(" height "); Serial.print(radioRxArray[3]);
    Serial.print(" cal: "); Serial.print(radioRxArray[4]);
    Serial.print(" dist: "); Serial.print(radioRxArray[5]);
    Serial.print(" ping sensor "); Serial.println(dist);
    }
  */
}

Lets slow down the need to trigger the PID loop and set a more even interval
I've tested this and it works great. After you started perusing the communications side of things I had one more itch I had to scratch on the tachometer code. and now after you made these points I think it is worth including in you code.

What it does is automatically adjust the number of triggering counts that need to take place before triggering the PID loop. you have a desired goal that you will set and the program adjusts to meet the request.

benefits are that you can decrease speed of your motor to a slow rpm and maintain a responsive PID loop

Here is the code:

// Add this near the top of the sketch
int SampleDuration = 20; // in Milliseconds I used 10 but I was testing for overall workload
// 20 I thing will work well for you bur 100 could also be good
// What this does is set the desired duration 

// updated code
void readRpm()
{

  cli ();         // clear interrupts flag
  Time = timeX;   // Make a copy so if an interrupt occurs timeX can be altered and not affect the results.
  timeX = 0;
  sei ();         // set interrupts flag
  if (PulseCtr > 0) {
    Input =  (double) (PulsesPerMinute /  (double)(( (unsigned long)Time ) *  (unsigned long)PulseCtr)); // double has more percision
    //   PulseCtr = 0; // set pulse Ctr to zero
    // debug();
    if (!Skip) { //PID only needs an instantaneous reading and so if we skip a few no harm done since Delta T is no longer a fixed value
      if (!myPID.Compute()) Serial.println();
      analogWrite(PWMpin, Output);
    }
    else Skip = false;

    analogWrite(PWMpin, Output);
    //

   // <<<<<<<<<<<<<<<<<<<<<<<<<<<< the following 3 lines perform the adjustments
    if(Time > ((SampleDuration + 1)*1000))Counts--;
    if(Time < ((SampleDuration - 1)*1000))Counts++;
    Counts = constrain(Counts, PulsesPerRevolution * .1,PulsesPerRevolution *4);
    
    Time = 0; // set time to zero to wait for the next rpm trigger.
    Counter += PulseCtr;
    PulseCtr = 0; // set pulse Ctr to zero
    
    PulsesPerMinute = (60.0 * 1000000.0) / (double)((double)PulsesPerRevolution / (double)Counts);
    //Fill rpm array with rpm readings and average them for display.
    rpmArray[0] = rpmArray[1];
    rpmArray[1] = rpmArray[2];
    rpmArray[2] = rpmArray[3];
    rpmArray[3] = rpmArray[4];
    rpmArray[4] = Input;
    //Last 5 Average RPM Counts Eqauls....
    printRpm = (rpmArray[0] + rpmArray[1] + rpmArray[2] + rpmArray[3] + rpmArray[4]) / 5;

    radioTxArray[0] = printRpm;//average sent to hand controller

  }
}

Try this first and let me know if it helps.

I believe we could breakup the exchangeData routine to transmit the RPM fast but make changes in a slower manner
guessing at the 500 interval it may be too slow... testing is necessary :slight_smile:

unsigned long prevMillisRX, prevMillisTX;
unsigned long txIntervalMillis = 100;
unsigned long rxIntervalMillis = 500;

unsigned long prevMillisRX, prevMillisTX;

void exchangeData()
{

  currentMillis = millis();
  if (currentMillis - prevMillisTX >= txIntervalMillis) {

    radio.openWritingPipe(slaveID[0]); // calls the first slave
    // there could be a FOR loop to call several slaves in turn
    bool rslt;
    rslt = radio.write( radioTxArray, sizeof(radioTxArray) );
    prevMillisTX = currentMillis;
  }
   
  if (currentMillis - prevMillis >= rxIntervalMillis) {

    if ( radio.isAckPayloadAvailable() ) {
      radio.read(radioRxArray, radioRxArrayLen);
      Skip = true;
    }
    prevMillisTX = currentMillis;
  }

}

I'm not a big fan of #include <NewPing.h>
due to a while() loop that traps any further execution of code till it is freed when the ping returns.
Your code is locked from the start of the trigger pin rise to the end of the echo pin drop and can't do anything else while it is holding hostage your arduino

I have a working PING code that will remedy this multiple while loop issue NewPing has
Are you using more than one ping sensor... would you like to use more than one?

I think if we have more time for the radio its abuse of time will be less noticeable :slight_smile:

You have some great ideas there. I am at work for another few hours so no testing yet but I am keen to try the revised tacho.

I can only see the use for one ping sensor so I think we can lock it in at that.

I also need to stop the motor on is started = false but ATM the output 40 - 255 prevents the motor from stopping. I need to give this some more thought.

moose4621:
I can only see the use for one ping sensor so I think we can lock it in at that.

I'll adjust my code for 1 ping sensor and simplify it to fit. this should be easy :slight_smile:

I also need to stop the motor on is started = false but ATM the output 40 - 255 prevents the motor from stopping. I need to give this some more thought.

you could just put it in manual and force the output to zero.

//  you can do something like

 if (Stop = ture){
    myPID.SetMode(MANUAL);
    Output = 0;
    analogWrite(PWMpin, Output); // Stop NOW
 }
 else if(myPID.GetMode() == MANUAL) myPID.SetMode(AUTOMATIC); // only change to automatic if it has been set to manual to save processor time

I am grinning from ear to ear.
Your ideas and suggestions are great. I am looking forward to getting home to try these out.

I got the ping code completed and tested with only 1 sensor, I'm just cleaning up unused variables.

I'll help out with integration if needed :slight_smile:

Here you go Ping timer without any waiting for the sound to return
NOTE: I had to swap 2 pins to use the interrupt

/*
  D3 = Ping trigger
  D4 = Ping Echo

  We need to change it to

  D3 = Ping Echo *** The arduino has an easy interrupt on pin 3 that will allow us to simplify my code
  D4 = Ping trigger
*/
#define TriggerPin  4
// echo pin will be interrupt 1 on pin 3
#define DelayBetweenPings 50 // it works to about 5 milliseconds between pings

volatile  unsigned long PingTime;
volatile  unsigned long edgeTime;
volatile  uint8_t PCintLast;
int PinMask = B1000; // pin 3
float Measurements = 0;

void PintTimer( )
{
  static uint8_t pin;
  static unsigned long cTime;
  cTime = micros();         // micros() return a uint32_t
  pin = PIND; //  get the state of all pins 0-8  quick snap shot
  sei();                    // re enable other interrupts
  uint16_t dTime;
  PCintLast = pin;          // we memorize the current state of all PINs
  int CheckPin = 3;
  if ((pin >> CheckPin & 1))edgeTime = cTime; //Pulse went HIGH store the start time
  else { // Pulse Went low calculate the duratoin
    dTime = cTime - edgeTime; // Calculate the change in time
    PingTime = dTime; // Lets Store any duration up to 65535 micro seconds
  }
}

void setup() {
  Serial.begin(115200);
  Serial.println("Ping Test");
  pinMode(3, INPUT);
  pinMode(4, OUTPUT);
  attachInterrupt(1, PintTimer, CHANGE );
}

void loop() {
  PingIt(); // Manage ping data
  debug();
}

void PingTrigger(int Pin) {
  digitalWrite(Pin, LOW);
  delayMicroseconds(1);
  digitalWrite(Pin, HIGH); // Trigger another pulse
  delayMicroseconds(10);
  digitalWrite(Pin, LOW);
}

bool AllClear() {
  return (!(PinMask & PIND)); //  all the input pins are LOW
}
void PingIt() {
  unsigned long PT;
  static unsigned long PingTimer;
  if ( AllClear()) { // Wait
    if ((unsigned long)(millis() - PingTimer) >= DelayBetweenPings) {
      PingTimer = millis();
      cli ();         // clear interrupts flag
      PT = PingTime;
      sei ();         // set interrupts flag
      Measurements = (float) (microsecondsToCentimeters(PT));
//      Measurements = (float) (microsecondsToInches(PT));
      PingTrigger(TriggerPin); // Send another ping
    }
  }
}

float microsecondsToInches(long microseconds){
  return (float) microseconds / 74 / 2;
}

float microsecondsToCentimeters(long microseconds){
  return (float)microseconds / 29 / 2;
}

void debug() {
  char S[20];
  static unsigned long PingTimer;
  if ((unsigned long)(millis() - PingTimer) >= 100) {
    PingTimer = millis();
    Serial.print(dtostrf(Measurements, 6, 1, S));
    Serial.println();
  }
}

Testing the first tacho mod now. Using the serial debug you have enabled, what values should I be aiming for Kp, Ki, and Kd?