Problem running sample C code from user guide

I am still learning how to control servos using my Maestro 6 ch USB controller. I successfully installed and controlled servos with the Windows Maestro controller to start with. I was also successful with the LINUX drivers and was able to control servos using the ‘UscCmd’ command line tool and also the sample bash script in the user guide.

However, when I try to run the user guide sample C code in Linux (Debian Squeeze), it controls the servo ok, but throws errors when reading the servo position ONLY when the servo is at 5000. If I change the script to switch between 4800/7000 instead of 5000/7000, everything is ok.

Here is the code and sample output with 5000/7000 (errors), then 4800/7000 (no errors). I am compiling with ‘cc -o outfile samplecode.c’. I also tried with gcc with same results.

Any insight would be appreciated. Thanks.

Steve

#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>

#ifdef _WIN32
#define O_NOCTTY 0
#else
#include <termios.h>
#endif

// Gets the position of a Maestro channel.
// See the "Serial Servo Commands" section of the user's guide.
int maestroGetPosition(int fd, unsigned char channel)
{
  unsigned char command[] = {0x90, channel};
  if(write(fd, command, sizeof(command)) == -1)
  {
    perror("Error writing");
    return -1;
  }

  unsigned char response[2];
  if(read(fd,response,2) != 2)
  {
//    printf("response[0] = %d\n", response[0]);
//    printf("response[1] = %d\n", response[1]);
    perror("Error reading");
    return -1;
  }

  return response[0] + 256*response[1];
}

// Sets the target of a Maestro channel.
// See the "Serial Servo Commands" section of the user's guide.
// The units of 'target' are quarter-microseconds.
int maestroSetTarget(int fd, unsigned char channel, unsigned short target)
{
  unsigned char command[] = {0x84, channel, target & 0x7F, target >> 7 & 0x7F};
  if (write(fd, command, sizeof(command)) == -1)
  {
    perror("error writing");
    return -1;
  }
  return 0;
}

int main()
{
  // Open the Maestro's virtual COM port.
  const char * device = "/dev/ttyACM0";  // Linux
  int fd = open(device, O_RDWR | O_NOCTTY);
  if (fd == -1)
  {
    perror(device);
    return 1;
  }

#ifndef _WIN32
  struct termios options;
  tcgetattr(fd, &options);
  options.c_lflag &= ~(ECHO | ECHONL | ICANON | ISIG | IEXTEN);
  options.c_oflag &= ~(ONLCR | OCRNL);
  tcsetattr(fd, TCSANOW, &options);
#endif

  int position = maestroGetPosition(fd, 0);
  printf("\n***************************************\n");
  printf("Current position is %d.\n", position);

  int target = (position < 6000) ? 7000 : 5000;
  printf("Setting target to %d (%d us).\n", target, target/4);
  printf("***************************************\n\n");
  maestroSetTarget(fd, 0, target);

  close(fd);
  return 0;
}

------------------CUT HERE ----------------------------
ERRORED OUTPUT BELOW (5000/7000)
========================================
***************************************
Current position is 7000.
Setting target to 5000 (1250 us).
***************************************

# ./servo_cntl
Error reading: Success

***************************************
Current position is -1.
Setting target to 7000 (1750 us).
***************************************

# ./servo_cntl

***************************************
Current position is 7000.
Setting target to 5000 (1250 us).
***************************************

# ./servo_cntl
Error reading: Success

***************************************
Current position is -1.
Setting target to 7000 (1750 us).
***************************************

WHEN SWITCING BETWEEN 4800 and 7000, everything is ok
======================================================
./servo_cntl

***************************************
Current position is 7000.
Setting target to 4800 (1200 us).
***************************************
./servo_cntl

***************************************
Current position is 4800.
Setting target to 7000 (1750 us).
***************************************
# ./servo_cntl

***************************************
Current position is 7000.
Setting target to 4800 (1200 us).
***************************************

Hello, Steve.

The expected response from the Maestro when the position is 5000 (0x1388) is the following two bytes:

0x88 0x13

The byte 0x13 happens to be the ASCII encoding of carriage return. I suspect that your serial port is configured to do something special with carriage return, such as add a newline to it. Please try adding the following line right before the tcsetattr line:

options.c_iflag &= ~(INLCR | ICRNL | IGNCR | ISTRIP);

More information about this stuff is here:
pubs.opengroup.org/onlinepubs/79 … rmios.html

Let me know if that works!

–David

Hello David,

Thanks for the response. I tried your new settings, but unfortunately it did not work. I have included the modified section of the code as well as the program output below. I have also modified the program to print out the byte values returned by the program (in decimal).

I suspect you are correct with the newline, but maybe another setting is required. I will continue to look at the page you referenced but your additional insight would definately be appreciated.

Steve

modified code section below-------
#ifndef _WIN32
  struct termios options;
  tcgetattr(fd, &options);
  options.c_lflag &= ~(ECHO | ECHONL | ICANON | ISIG | IEXTEN);
  options.c_oflag &= ~(ONLCR | OCRNL);
  options.c_iflag &= ~(INLCR | ICRNL | IGNCR | ISTRIP);
  tcsetattr(fd, TCSANOW, &options);
#endif
-------
ERRORED OUTPUT REMAINS:

# ./test
response[0] = 88
response[1] = 27
response[1]*256 = 6912

***************************************
Current position is 7000.
Setting target to 5000 (1250 us).
***************************************

# ./test
response[0] = 136
response[1] = 0
response[1]*256 = 0
Error reading: Success

***************************************
Current position is -1.
Setting target to 7000 (1750 us).
***************************************

Hello, steve.

Too bad that suggestion didn’t work.

To get more information about what’s happening, could you modify the code to print the return value from read() so we know how many bytes it actually read? Also, could you duplicate the code that calls read so that it does it twice, just in case the response from the Maestro is somehow being split up?

Please run “stty -F /dev/ttyACM0 -a” and post the output from that command here. I don’t think tcsetattr has a permanent affect on it, but I might notice something strange in the output nonetheless.

–David

Hello David,

Great news-----I was working on the code just now (before I saw your post) and was able to make it work. Instead of the line of code you originally gave me, I have been expermimenting with different combinations, but eventually ended up with a single setting instead. See the code extract below and the new output.

Your original response with the termios.h link was the key to understanding some of these settings.

Thanks again for the support!

Steve

ORIGINAL SUGGESTION:
options.c_iflag &= ~(INLCR | ICRNL | IGNCR | ISTRIP);

WORKING CODE: 
options.c_iflag &=  ICRNL;

SUCCESSFUL OUTPUT:
========================================
 # ./test
response[0] = 88
response[1] = 27
response[1]*256 = 6912
***************************************
Current position is 7000.
Setting target to 5000 (1250 us).
***************************************

# ./test
response[0] = 136
response[1] = 19
response[1]*256 = 4864
***************************************
Current position is 5000.
Setting target to 7000 (1750 us).
***************************************

I’m glad that you were able to get it working, Steve. I would like to update the example code with your findings, but first I need some help from you to understand why the fix worked.

options.c_iflag &=  ICRNL;

The working code above actually clears every bit in c_iflag except ICRNL, and ICRNL probably wasn’t set so it’s basically setting c_iflag to 0. I don’t see any particular problem with this, but it seems kind of extreme to clear lots of bits that might be harmless. I would like to find out exactly which bits needed to be cleared and would appreciate your help doing that.

We already tried clearing INLCR, ICRNL, IGNCR, and ISTRIP but that was not sufficient. There must be some other bit that needs to be cleared. Could you try clearing all of the bits documented on that page and see if that works? In other words, please try:

options.c_iflag &= ~(BRKINT | ICRNL | IGNBRK | IGNCR | IGNPAR | INLCR | INPCK | ISTRIP | IUCLC |IXANY | IXOFF | IXON | PARMRK);

If that works, please try removing bits from the code above one at a time until it stops working. This will tell us which bits you actually needed to clear. If the code above doesn’t work, then there must be some undocumented bit in c_iflag on your system that causes trouble.

–David