Go Down

Topic: Radio code appears to be interfering with other functions. (Read 3654 times) previous topic - next topic

zhomeslice

Here you go Ping timer without any waiting for the sound to return
NOTE: I had to swap 2 pins to use the interrupt
Code: [Select]
/*
  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();
  }
}
HC

moose4621

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

moose4621

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


Wow, with decimal precision too!
Awesome.


zhomeslice

Testing the first tacho mod now. Using the serial debug you have enabled, what values should I be aiming for Kp, Ki, and Kd?
If you are watching the resulting calculations.
I'm assuming you are at setpoint and the PID is tuned.
Kp should fluctuate around zero. Ki is additive and will produce an offset so that setpoint can be reached. this moves slower than Kp. Kp + Ki will produce the output PWM input 0~255 to keep the motor at the desired speed. Kd is detailed oriented. It cleans up messes. Sudden change in setpoint/reading will cause Kd value to spike. But it's its influence is short lived.It just speeds up the process of reaching setpoint. (Tune Kd last)
HC

moose4621

you could just put it in manual and force the output to zero.
Code: [Select]

//  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



Worked a treat.
Had to do the kick start thing again though.
Code: [Select]

void switchOnOff()
{
  if (isStarted == false)
  {
    myPID.SetMode(MANUAL);
    Output = 0;
    analogWrite(PWMpin, Output); // Stop NOW
  }
  else if (myPID.GetMode() == MANUAL)
  {
    myPID.SetMode(AUTOMATIC);
    analogWrite(PWMpin, 60);
    myPID.Compute();
    delay(11);
    myPID.Compute();
  }
}


Thank you for this idea. I would never have come up with this. I was heading down the track of putting a relay in to switch the esc off. This is a much better solution.

zhomeslice

Quote
Worked a treat.
Had to do the kick start thing again though.
Excellent :)
I'm glad the ping code works well for you. 
How does the Ping sensor interact with the setup?
Are you using it to adjust the setpoint of the motor when an object gets close? 
or is it just a status reading?

HC

moose4621

The ping sensor is used to switch the motor on/off as the 3 point linkage on the tractor is raised and lowered. I will have to give you a better idea of what I want the final outcome to be. Need a video and/or cad drawing to show you. I'll take some photo's tomarrow.

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 :)

I see where you are going with this but the problem is that I am not receiving data from the hand held remote (client) fast enough.
The remote has the start stop button of which I need to keep the lag to as little as possible.

The client, if I understand correctly, is continuously transmitting but the master only receives at txIntervalMillis.

Master
Code: [Select]

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();
  }

}


Client, (hand held remote).

Code: [Select]

void exchangeData()  {

radio.writeAckPayload(1, radioTxArray, sizeof(radioTxArray));

  if ( radio.available() ) {
    radio.read( radioRxArray, sizeof(radioRxArray) );
    /*
    Serial.print("Data received Number0 ");
    Serial.print(radioRxArray[0]);
    Serial.print(" Number1 ");
    Serial.println(radioRxArray[1]);
    */
  }
}

moose4621

I wonder if I am better off reversing the radios?

They are the way they are since adaptation from Robin2's excellent examples.

zhomeslice

Quote
I wonder if I am better off reversing the radios?

They are the way they are since adaptation from Robin2's excellent examples.
I see how you are working this. each time the new rpm reading is sent your master unit is expecting an update even if nothing has changed. you need the sensor reading to be responsive. With LCD displays 100 milliseconds is usually enough (10 times a second)
so if you were to reverse the roles
1) you need a ping from the remote (Now Master) every x milliseconds to confirm communications is still alive
2) you only need to get the RPM status when you need to update the display
3) you will only need to send updates when you make a change (Push a button etc.)
4) if ping fails have the settings default to a safe condition and send a request to the when communication is restored remote (Now Master) for complete update

this would take a load off the robot (now Slave) UNO and provide you with almost instantaneous responses due to changes would be sent the instant they are detected.
HC

moose4621

I am avoiding giving you feedback on the new tacho code because I am getting some wild fluctuations from the sensor. :smiley-confuse:
I disabled the radio and ping sensor and wrote a fixed PWM output to the esc to test the results.

Code: [Select]

output from PID controlled seeder master R2 with fixed analogWrite of 80. Radio & Ping dissabled.
 In  45.42 Setpt  30.00 /\T 1284416.00 Kp -20.04 Ki  10.72 Kd         0 Out 0
 In  49.41 Setpt  30.00 /\T 1214320.00 Kp -25.23 Ki   3.56 Kd         0 Out 0
 In  45.71 Setpt  30.00 /\T 1348988.00 Kp -20.43 Ki   8.07 Kd         0 Out 0
 In  45.42 Setpt  30.00 /\T 1394444.00 Kp -20.04 Ki   7.49 Kd         0 Out 0
 In  58.50 Setpt  30.00 /\T 1111136.00 Kp -37.05 Ki -11.82 Kd         0 Out 0
 In  47.66 Setpt  30.00 /\T 1398832.00 Kp -22.96 Ki   1.41 Kd         0 Out 0
 In  42.84 Setpt  30.00 /\T 1595056.00 Kp -16.69 Ki   9.43 Kd         0 Out 0


Obviously the pid's are irrelevant but the fluctuation in input is a problem. I disassembled the sensor and rotor and cleaned them and checked wiring but I cannot find where the fluctuation is coming from.

Using this basic sensor test;
Code: [Select]



volatile float time = 0;
volatile float time_last = 0;
volatile int rpm_array[5] = {0, 0, 0, 0, 0};
int pot;

void setup()
{
  //Digital Pin 2 Set As An Interrupt
  attachInterrupt(0, fan_interrupt, FALLING);

  // pot = analogRead(0);

  Serial.begin(9600);
  analogWrite(6, 100);
}

//Main Loop To Calculate RPM and Update LCD Display
void loop()
{





  while (1) {


    // pot = analogRead(0);

    //analogWrite(6, map(pot, 0,1023, 0, 255));

    int rpm = 0;

    //Slow Down The LCD Display Updates
    delay(400);

    //Update The RPM
    /*
      noInterrupts();
      unsigned long copyTime = time;
      interrupts();
    */

    if (time > 0)
    {

      //5 Sample Moving Average To Smooth Out The Data
      rpm_array[0] = rpm_array[1];
      rpm_array[1] = rpm_array[2];
      rpm_array[2] = rpm_array[3];
      rpm_array[3] = rpm_array[4];
      rpm_array[4] = 60 * (1000000 / (time * 36));
      //Last 5 Average RPM Counts Eqauls....
      rpm = (rpm_array[0] + rpm_array[1] + rpm_array[2] + rpm_array[3] + rpm_array[4]) / 5;

      // rpm = 60*(1000000/(time * 36));
      Serial.print(rpm);
      Serial.print("  ");
      Serial.println(pot);
    }

  }
}


//Capture The IR Break-Beam Interrupt
void fan_interrupt()
{
  time = (micros() - time_last);
  time_last = micros();
}


I get;
Code: [Select]

2  0
43  0
43  0
43  0
50  0
50  0
50  0
50  0
50  0
42  0
42  0
42  0
42  0


I need to fix this first but it's after midnight. No more running motors tonight or I will get divorced. :(

zhomeslice


Quote
In 45.71 Setpt  30.00 /\T 1348988.00 Kp -20.43 Ki   8.07 Kd         0 Out 0
Setpoint (30) is below input (45.71) and power is zero so the motor should be stopped your Minimum power to the motor is powering the motor faster than what the desired setpoint is requesting.

HC

moose4621

Setpoint (30) is below input (45.71) and power is zero so the motor should be stopped your Minimum power to the motor is powering the motor faster than what the desired setpoint is requesting.


I should be clearer, I bypassed the pid lood and wrote directly to the esc at pwm 80 to test the interval from the sensor under constant power. In & \T are valid, nothing else is.

This is my source of knowledge regarding ackPayloads.
http://forum.arduino.cc/index.php?topic=381973.0

and here;
http://maniacbug.github.io/RF24/classRF24.html

moose4621

Latest code before I  go to bed.

Code: [Select]

/*
  Arduino Nano
  Pin allocation:
  D1
  D2 = speed sensor (interrupt 0)
  D3 = Ping Echo
  D4 = Ping trigger
  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*********
#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
  }
}

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.8, 1.5, .01, DIRECT);

#define PWMpin  6//esc
int currentSensor = A0;
int amps;

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
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

//********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 = 200;
int txVal = 0;
int radioRxArray[6];
//0 = motor on/off, 1 = setPoint, 2 = , 3 = Height, 4 = Cal, 5 = Dist
byte radioRxArrayLen = 12;


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};

  pinMode(3, INPUT);
  pinMode(4, OUTPUT);
  attachInterrupt(1, PintTimer, CHANGE );

}

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

}

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 exchangeData()
{

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

    radio.openWritingPipe(slaveID[0]);
    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 (Measurements <= radioRxArray[5] && radioRxArray[3] == 1) //if ping dist is less than dist set point and height switch is on.
    {
      isStarted = true;
    }
    else if (Measurements >= radioRxArray[5] && radioRxArray[3] == 1)
    {
      isStarted = false;
    }
  }

  else
  {
    isStarted = false;
  }

}

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));
      PingTrigger(TriggerPin); // Send another ping
    }
  }

}

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


void readMotorCurrent()// shut the motor down on over current.
// send message to hand held.
{

  amps = analogRead(currentSensor);
  if (amps >= 123)//value to be determined.
  {
    isStarted = false;
    radioTxArray = 1;
  }

}

void switchOnOff()
{
  if (isStarted == false)
  {
    myPID.SetMode(MANUAL);
    Output = 0;
    analogWrite(PWMpin, Output); // Stop NOW
  }
  else if (myPID.GetMode() == MANUAL)
  {
    myPID.SetMode(AUTOMATIC);
    analogWrite(PWMpin, 60);
    myPID.Compute();
    delay(11);
    myPID.Compute();
  }
}

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;  
  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) {
      if (!myPID.Compute()) Serial.println();
      analogWrite(PWMpin, Output);
    }
    else Skip = false;

    analogWrite(PWMpin, Output);
    
    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

  }
}

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();
  }

 
}


moose4621

These flaky radio's are killing me. First the rpm sensor has gone haywire, now the radios are being stubborn.
I reversed the roles of the radios in the sketches. No joy. Hand controller, (master), reported success = 0 but would receive array from slave. On the slave end, no received data.

So, back to basics. Reloaded Robin2's test sketches, now they don't work. (Yes I changed the pin allocations):-)

Reloaded my sketches before the radio swap, everything works again!

Arghhhhh!

I really appreciate the help you have all given me but until I sort these issues out, I won't be able to give you any meaningful feedback on your suggestions.

I am changing over to a PNP NO LJ12A3-4-Z/BY Inductive Proximity Sensor Switch and a notched metal disc for rpm sensing. More durable and weatherproof and hopefully more reliable. Just needs a voltage splitter on the output.

I'll get back to you when I get it sorted.

zhomeslice

I'm sure it has to do with interrupts during the process of transmission and receiving data with SPI... I'm not familiar with SPI 

I glanced at the code and didn't see any reason for the struggles you are facing from the code you have written.

I may dig into SPI code and see if there is any code is blocking/stopping other code from working. 

The issue I see is
SPI is consuming/trapping enough process time to prevent encoder/PID from maintaining  smooth operation.

do you agree?
HC

Go Up