Help sending ASCII commands to Pan & Tilt

Sorry, I missed out a bit of code in my example - happens when i'm working on a phone screen!

My code snippet should have looked like:

void loop() {
  digitalWrite(3, LOW);       // Put RS485 in Receive mode
  while (Serial.available()) {
    digitalWrite(3,HIGH);     // Put RS485 into tx mode
    mySerial.write(Serial.read());
    mySerial.flush();
  }
  while (mySerial.available()) {
    Serial.write(mySerial.read());
  }
}

Yes. This happens a lot especially when you use a hardware serial port. The hack solution that you've seen is to add a delay after sending the bytes to the transmit queue. This gives the queue time to empty before you switch your RS485 transceiver back to receive mode. The delay depends on baud rate (mostly) which people change for their needs and then wonder why the code doesn't work.

The correct way is to monitor the transmit buffer and wait until it is empty - your option (b). However, the code from option (b) only works when using a hardware serial port. Have a look at the flush() function, which works for both hardware and software serial ports.

1 Like

Thank you that look like much a better option!

Could you explain me one thing though?:

Why do you add the digitalWrite(3, LOW); at the begining of the loop and not after mySerial.flush(); ?

I was looking at the documentation on using "while loop" but I think I might missing something... or my brain is a bit fried after to much thinking on this haha

Thanks again!

Remember, the code is a hack to get you going under specific conditions. It wouldn't be done that way in the real world.

Ok, so I didn't put the transceiver back into receive mode after the flush command as there is the possibility that 1 or more characters/bytes may arrive over the main serial link. The write and flush functions take an amount of time to execute.

Your setup code sets the baud rate to 9600 baud. Assuming your byte frame is 8 data bits, no parity and 1 stop bit + a start bit, that's 10 bits (parity bit not present) at 9600 bits/sec which takes fractionally over 1ms to write and flush.

It's possible that more characters could be received in that 1ms so the while test will be satisfied and another character will be written out over the RS485 link. By keeping the RS485 transceiver in Tx mode inside the while loop, it stops the transceiver being switched back and forth between Rx and Tx between each character.

It's personal preference, but I prefer to switch to Tx, send what you need to and then switch back to Rx.

Putting the RS485 transceiver back into Rx mode at the start of the loop makes sure that the setup is ready to receive from the remote RS485 device.

Also note that the RS485 link is half duplex so you cannot receive data from the remote device at the same time as you are transmitting to it.

1 Like

Thank you!

I will test tomorrow and let you know what happened.

Hi every one.

After lots of back and foward with the PT company and me testing I got to make it work.

A Simple code that would make the PAN and TILT work but in a jerky way is this:

#include <SoftwareSerial.h>

// Defines new serial Port not to interfeer with USB Digital Pin 4 = RX, Digital Pin 5 = TX
SoftwareSerial mySerial(4, 5);  // RX, TX

int Xpin = A0;             // Joystick X (horizontal)
int Ypin = A1;             // Joystick Y (vertical)
int Xval;
int Yval;
int restX = 512;           // Joystick X rest position value
int restY = 512;           // Joystick Y rest position value
int deadZoneX = 200;        // Dead zone for X (without movement)
int deadZoneY = 200;        // Dead zone for Y (without movement)
int dt1 = 25;              // Delay 1 time for program loop, This should be 25 (ms)
int dt2 = 5;              // Delay 2 time for Y axis, This should be 5 (ms)

void setup() {

  // open serial comunication
  Serial.begin(9600);

  // Set the baud rate for the SoftwareSerial object
  mySerial.begin(9600);

  // Define pin modes for Xpin and Ypin
  pinMode(Xpin, INPUT);
  pinMode(Ypin, INPUT);

  // Define pin modes for TX and RX
  pinMode(4, INPUT);
  pinMode(5, OUTPUT);

}
void loop() {
  Xval = analogRead(Xpin);
  Yval = analogRead(Ypin);
  delay(dt1);

  // if Joystick is moved up or down, move Vertical stepper (Tilt)
  if (Yval > (restY + deadZoneY)) {            // Yval < 512 + 200 = 712
    Serial.println("TILT Up");
    Serial.println(Yval);
    mySerial.print("$AMMF0000W\r\n");          // Tilt Up
    delay(dt2);
  }
  else if (Yval < (restY - deadZoneY)) {        // Yval < 512 - 200 = 312
    Serial.println("TILT Up");
    Serial.println(Yval);
    mySerial.print("$AMMB0000W\r\n");           // Tilt Down
    delay(dt2);
  }
  else {
    Serial.println("TILT Stop");                 // 472 < Yval < 552
    Serial.println(Yval);
    mySerial.print("$AMST0000W\r\n");           // Tilt Motor Stop
    delay(dt2);
  }
  // if Joystick is moved left or right, move Horizontal stepper (Pan)
  if (Xval > (restX + deadZoneX)) {            // Xval < 512 + 40 = 552
    Serial.println("Pan Clockwise");
    Serial.println(Xval);
    mySerial.print("#AMMF0000W\r\n");          // Pan clockwise
    delay(dt2);
  }
  else if (Xval < (restX - deadZoneX)) {        // Xval < 512 - 40 = 472
    Serial.println("Pan Anti-Clockwise");
    Serial.println(Xval);
    mySerial.print("#AMMB0000W\r\n");           // Pan anti-clockwise
    delay(dt2);
  }
  else {
    Serial.println("Pan Stop");                 // 472 < Xval < 552
    Serial.println(Xval);
    mySerial.print("#AMST0000W\r\n");           // Pan Motor Stop
    delay(dt2);
  }
}

Those delays you can see on the code is the ones I found be trial an error.

But there is a problem:
The code keep sending the command over and over again. At first I though it was not going to be a problem. But in reality it is as the PAN and TILT reacts every time a code is received and it makes the movment jerky.

Can you guys suggest a way to just send the commands once when the joystick is a certain position and then wait until the joystick changes position?

Something like:

previousPosition  = currentPosition;
currentPosition = analogRead(joystickPin);
if ( abs( currentPosition - previousPosition > threshold)) {

Or any other suggestions?

Thank you

That's almost the common pattern for not doing until it needs to be.

 if   (     abs( currentPosition - previousPosition )      >    threshold   ) {

Remove as many spaces as you want, just don't refigure the parantheses!

Also, you probably want to update previousPosition after. After you react to the current position being far enough away from the previous position.

Otherwise, the constant updating would mean (I think - sand and sun in my eyes yet) that you could ever so slowly move the position along, never triggering because it is never seen to be moving anything like a threshold distance.

HTH

a7

Thanks but I finally changed the strategy based on another example I found

Here is the code.
I'm sure is a very "dirty" code and it could be lot simpler, cleaner and elegant but at list it works as expected.

If Any one has a suggestion on how to make it cleaner, please feel free to comment.

#include <SoftwareSerial.h>

// Defines new serial Port not to interfeer with USB Digital Pin 4 = RX, Digital Pin 5 = TX
SoftwareSerial mySerial(4, 5);  // RX, TX

int Xpin = A0;             // Joystick X (horizontal)
int Ypin = A1;             // Joystick Y (vertical)
int Xval;
int Yval;
int restX = 512;           // Joystick X rest position value
int restY = 512;           // Joystick Y rest position value
int deadZoneX = 200;        // Dead zone for X (without movement)
int deadZoneY = 200;        // Dead zone for Y (without movement)
int dt1 = 100;              // Delay 1 time for program loop, This works fine with 100 (ms)
int dt2 = 40;              // Delay 2 time for Y axis, This works fine with 40 (ms)
int currentJoystickY;
int currentJoystickX;
int currentYPosition;
int currentXPosition;
int previousYPosition;
int previousXPosition;

void setup() {

  // open serial comunication
  Serial.begin(9600);

  // Set the baud rate for the SoftwareSerial object
  mySerial.begin(9600);

  // Define pin modes for Xpin and Ypin
  pinMode(Xpin, INPUT);
  pinMode(Ypin, INPUT);

  // Define pin modes for TX and RX
  pinMode(4, INPUT);
  pinMode(5, OUTPUT);

}
void loop() {
  Yval = analogRead(Ypin);
  Xval = analogRead(Xpin);
  currentYPosition = Yval;
  currentXPosition = Xval;
  delay(dt1);

  if (getYJoystickUpdateReady() == true) {              // Check if joystick Y axis has change
    int currentJoystickY = Yval;
// if joystick Y axis has change command acording to the Yval value
    if (currentJoystickY > (restY + deadZoneY)) {       // Yval > 512 + 200 = 712
      Serial.println("TILT Up");                        // Show Command on the Serial Monitor
      Serial.println(Yval);                             // Show Value of Y axis on the Serial Monitor
      mySerial.print("$AMMF0000W\r\n");                 // TILT Up
      mySerial.flush();                                 // Wait to entire message to be sent
    }
    else if (currentJoystickY < (restY - deadZoneY)) {  // Yval < 512 - 200 = 312
      Serial.println("TILT Up");
      Serial.println(Yval);
      mySerial.print("$AMMB0000W\r\n");                 // TILT Down
      mySerial.flush();
    }
    else if ( (restY - deadZoneY) < currentJoystickY < (restY + deadZoneY) ) {
      Serial.println("TILT Stop");                      // 312 < Yval < 712
      Serial.println(Yval);
      mySerial.print("$AMST0000W\r\n");                 // TILT Motor Stop
      mySerial.flush();
    }
    delay(dt2);                                         // This delay has proved to be needed to avoid X axis miss a command
  }
  if (getXJoystickUpdateReady() == true) {              // Check if joystick Y axis has change
    int currentJoystickX = Xval;
// if joystick X axis has change command acording to the Xval value
    if (currentJoystickX > (restX + deadZoneX)) {       // Xval > 512 + 200 = 712
      Serial.println("PAN Right");
      Serial.println(Xval);
      mySerial.print("#AMMF0000W\r\n");                 // PAN Up
      mySerial.flush();
    }
    else if (currentJoystickX < (restX - deadZoneX)) {  // Xval < 512 - 200 = 312
      Serial.println("PAN Left");
      Serial.println(Xval);
      mySerial.print("#AMMB0000W\r\n");                  // PAN Down
      mySerial.flush();
    }
    else if ( (restX - deadZoneX) < currentJoystickX < (restX + deadZoneX) ) {
      Serial.println("PAN Stop");                       // 312 < Xval < 712
      Serial.println(Xval);
      mySerial.print("#AMST0000W\r\n");                 // PAN Motor Stop
      mySerial.flush();
      delay(dt2);
      // This delay and the repeted "stop" command had to be added as some times the X axis will not receive this command.
      // This seams to insure the X axis do stop when needed
      mySerial.print("#AMST0000W\r\n");                 // PAN Motor Stop
      mySerial.flush();
    }
  }
}

bool getYJoystickUpdateReady() {
  // Determines if we are ready for another joystick update on the Y axis

  if (abs( currentYPosition - previousYPosition ) > 300 ) {
    previousYPosition = currentYPosition;
    return true;
  }
  return false;
}

bool getXJoystickUpdateReady() {
  // Determines if we are ready for another joystick update on the X axis

  if (abs( currentXPosition - previousXPosition ) > 300 ) {
    previousXPosition = currentXPosition;
    return true;
  }

  return false;
}

I am curious about how this is supposed to work, could you say how the joystick movement and/or absolute position controls the issuing of the commands that move the axis servo?

Does the servo move one step per

mySerial.print("$AMMB0000W\r\n");                 // TILT Down

or begin moving with that command?

The joystick does not control the speed of tilting?

And this

else if ( (restY - deadZoneY) < currentJoystickY < (restY + deadZoneY) )

is not doing what it looks like you think it is doing. Yes, in mathematics, no so much in programming.

This

  else if ( (restY - deadZoneY) < currentJoystickY && currentJoystick < (restY + deadZoneY) )  

is the way to do two conditions. Or you could use and instead of '&&'.

Either way, that's just good old

  else if (abs(currentJoystickY - restY) < deadZoneY)

I am also curious about the asymmetric delay() calls. Does it not work if the two code sections are more or less identical, but for the axis variables?

a7

Hey alto777,

I will try to answer as I'm a very novice coder (as you could see) and I've trying to learn from other codes I find and try to adapt.
I have to say that I needed this to work for a particular camera pan and tilt project and I was hitting the dead line, so I was tryin hard to work even though I was not 100% sure of what I was doing.

This PAN and TILT servo works on RS-485 coms and to either configure some features or to tell it to move or change speed you need to send the unit a particular ASCII message.

You can see the documentation on the tread but to make it easier here is the link.

I Wanted to change the speed depending on the Joystick position I would have to make a math expresion to then send the unit a code to change the speed. For this project is was not needed, so I made it simple.


The joystick is like and ON/OFF comand.
So when the joystick move down, it send the comand "Tilt down" and the tilt moves until the joystick is centered and then send the comand "Tilt stop".


Thank you for that I will update the code and try.


"
I'm not sure I understand you question.
I just know I was battling to most with this part.
I just wanted the code to verify the position of the joystick and if this position changed then update it and check the value in other to send a different command if needed. If the position of the joystick had not change enough then do nothing.

With out this condition the code was sending constantly a command.
For example if the joystick was centered, it kept sending the "stop" command and that was making the PAN&TILT make a constant clicking noise (pretty annoying) and When moving, for example "tilte down" for every command received the servo will have micro stops for every command received even though it was the same command, resulting on a jerky movement instead of a steady one.

How that helps a bit.

THX for the additional information.

I meant that the code sections handling the two axes are similar, but the placement of the delay() calls that make them work is different one to the other.

I would have thought that the two axes are identical, that is to say that the code to handle them would be identical but for the variables used and the axis being handled.

That you arrived at two different and (so far) odd fixes interested me. Is there, in fact, any real difference between the two axes that would require different handling?

I ran your code and it does seem to work, aside from issuing multiple STOP commands, STOP when it is already. Stopped. No large deal.

I do not argue with success, this odd line

   else if ( (restX - deadZoneX) < currentJoystickX < (restX + deadZoneX) ) {

somehow seems to be working - whatever it does do is passing for what you think it is doing. I haven't just now what it takes to see why that is. Perhaps I will figure that out.

The code below would replace your loop() function. It only handles one axis. It uses hysteresis and a simple finite state machine to track the joystick movement and issue commands to the pan/tilt as necessary. Your code does the same things! I only offer this as a simple demonstration of a more explicit FSM approach:

void loop() {
  doXJoyStick();
}

void doXJoyStick()
{
  enum {IDLE, LEFT, RIGHT, STOP};
  static unsigned char state = IDLE;

  int joyX = analogRead(A0) - 512; // normalize to -511..511 range

  switch (state) {
  case IDLE :
    if (joyX < -200) {
      Serial.println("PAN Right");
      Serial.println(joyX);
      mySerial.print("#AMMF0000W\r\n");                 // PAN Up
      mySerial.flush();

      state = LEFT;
    }
    else if (joyX > 200) {
      Serial.println("PAN Left");
      Serial.println(joyX);
      mySerial.print("#AMMB0000W\r\n");                  // PAN Down
      mySerial.flush();

      state = RIGHT;
    }
    break;

  case LEFT :
    if (joyX > -100)
      state = STOP;

    break;

  case RIGHT :
    if (joyX < 100)
      state = STOP;

    break;

  case STOP :
    Serial.println("PAN Stop");
    Serial.println(Xval);
    mySerial.print("#AMST0000W\r\n");                  // PAN Stop
    mySerial.flush();
    
    state = IDLE;

    break;
  }
}

The other axis could be handled with a cut/paste/edit copy of the same code, or a real programmer could figure out a way for the two code sections, nearly identical, could be generalized to serve both axes.

Try it here (X-axis only).

a7

1 Like

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.