4
votes

Good afternoon,

I have a peripheral device which communicates over usb over virtual serial port. Everything works well under Windows with generic ACM serial driver, for example with: https://www.kernel.org/doc/Documentation/usb/linux-cdc-acm.inf

Under Linux, it uses CDC ACM drivers. Everything in sys logs seems to work ok, but communication is behaving strangely. When I connect the device, about 10 bytes at the begining of communication are lost. Next, just each second command is received ok.

My questions are: 1) Communication protocol of this device doesn't use ASCII, it is binary (it can randomly contain control characters etc...). Should I use stty for configuring just speed, data bits, stop bits and parity, or something more is necessary to be set up for binary communication? (To ignore control bits in kernel and transmit every byte - raw data.)

2) Any idea, how to test if linux ACM drivers works properly, or which another drivers should i try for my CDC ACM device?

Thanks for any idea!

1
You just need to be sure that termios (a) is properly configured for non-canonical (aka raw) mode, and (b) has software flow-control disabled. You're not detailed at all; what software are you using to perform this data transfer? Why does it not configure the port properly?sawdust
stty -F /dev/ttyACM0 raw might work (if the transfer program isn't also configuring these attributes). Note that there's no hyphen preceding the raw parameter. It's a parameter setting not a switch. If you do enter -raw, then you'll negate the purpose of this command.sawdust
I haven't any software, I use echo -ne "\xXY" to /dev/ttyACMx for write a byte and cat /dev/ttyACM to log file for read from the port. For see received data, I open my log file via xxd. Settings are done by stty. I think that raw parameter could be the solution, thanks!JirkaRCK
"I haven't any software" -- You're using a digital computer, so there has to be software. echo and cat are shell commands, so the program that you're using is the shell.sawdust
Try to disable ModemManager service, e.g. sudo systemctl stop ModemManager && sudo systemctl disable ModemManager. It might interfere in your communication by trying to initialize the device as modem. If it helps, but you need the service to be running, there is a way to configure udev to ignore just your device in ModemManager.Alexandr Zarubkin

1 Answers

1
votes

Linux will often mangle things like line-ending characters (0x0A and 0x0D) when you try to send them over a serial port, which can cause issues if they are actually binary data and not intended as line-ending characters.

Here is a snippet from Pololu that shows how to configure your serial port correctly and then send and receive a few bytes. Pay attention to the part that calls tcsetattr in particular.

#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)
  {
    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()
{
  const char * device = "/dev/ttyACM0";  // Linux
  int fd = open(device, O_RDWR | O_NOCTTY);
  if (fd == -1)
  {
    perror(device);
    return 1;
  }

#ifdef _WIN32
  _setmode(fd, _O_BINARY);
#else
  struct termios options;
  tcgetattr(fd, &options);
  options.c_iflag &= ~(INLCR | IGNCR | ICRNL | IXON | IXOFF);
  options.c_oflag &= ~(ONLCR | OCRNL);
  options.c_lflag &= ~(ECHO | ECHONL | ICANON | ISIG | IEXTEN);
  tcsetattr(fd, TCSANOW, &options);
#endif

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

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

  close(fd);
  return 0;
}

You might be able to do the same thing with the stty command line utility.