0

so I am using an ARM9 processor (i.mx28) running a Linux Kernel to communicate with a BLDC controller via RS232 (High level protocol is based on CANOpen protocol). Messages sent to the controller are generally followed by a response.

The controller-side connector features RX, TX as well as GND, so no flow control lines or such. The controller manual states that connection must be set up using no software flow control. Thus, I configured the port to ignore DCD (using CLOCAL) as well as RTS/CTS (using ~CRTSCTS) and software flow control (using ~IXON, ~IXOFF and ~IXANY).

My problem is as follows: after setting up the serial port, writing works just fine, commands like for example a soft reset are executed instantly. Trying to read mentioned responses, however, results in "Errno 11: Resource momentarily unavailable". When I tried monitoring the messages on my computer running a terminal, read() returns 0 instead of -1, errno is hence not set. Vice versa, I tried hooking up the BLDC controller to said terminal and manually rebooting it to get to see the boot-up-message which is sent at start up. This worked fine as well.

I've been abusing Google for the last 3 days looking for similar problems, but to no avail. Among several different combinations of setting for nonblocking IO, I also tried several ways of blocking behavior, this however resulted in blocking forever and still not reading anything at all.

I suspect that the serial port is, although the termios configuration states otherwise, still expecting handshake/flow control, as hooking up the system to a computer providing RTS/CTS lines works fine.

int configureSerialPort() {
//Open Serial Port--------------------------------------------
  int fileDescr = open("/dev/ttyS3", O_RDWR | O_NDELAY);
  if (fileDescr < 0){
    std::cout << "Unable to open Serial Port." << std::endl;
    exit(1);
  }
  else std::cout << "Serial Port opened." << std::endl;
  fcntl(fdSerial, F_SETFL, O_NONBLOCK);
//Configure Serial Port for B115200, 8N1---------
  struct termios PortParams;
  if (tcgetattr(fileDescr, &PortParams) < 0) {
    std::cout << "Could not get Attributes." << std::endl;
    exit(1);
  }
  else std::cout << "Got Attributes." << std::endl;

  cfsetispeed(&PortParams, 115200);
  cfsetospeed(&PortParams, 115200);

  PortParams.c_cflag |= (CLOCAL | CREAD);
  PortParams.c_cflag &= ~PARENB;
  PortParams.c_cflag &= ~CSTOPB;
  PortParams.c_cflag &= ~CSIZE;
  PortParams.c_cflag |= CS8;
  PortParams.c_cflag &= ~CRTSCTS;

  PortParams.c_lflag &= ~(ICANON | ECHO | ECHONL | IEXTEN | ISIG);

  PortParams.c_iflag &= ~(INPCK | ISTRIP | IGNPAR | PARMRK | IXON | IXOFF | IXANY);

  PortParams.c_oflag &= ~OPOST;

  PortParams.c_cc[VMIN] = 0;
  PortParams.c_cc[VTIME] = 0;

  if (tcsetattr(fileDescr, TCSANOW, &PortParams) != 0) {
    std::cout << "Could not set Attributes." << std::endl;
    exit(1);
  }
  else std::cout << "Port configured." << std::endl;
  usleep(1000000);
  tcflush(fdSerial, TCIFLUSH);
  return fileDescr;
}

This is my configuration as by now, which should be fine to my understanding.

Writing and reading is done by their respective syscalls:

wrLenSerial = write(fdSerial, &resetNode, sizeof(resetNode));
  if (wrLenSerial < sizeof(resetNode)) {
    std::cout << "Could not send Write Command." << std::endl;
  }
  else std::cout << "Reset Command sent." << std::endl;
  usleep(60000);
  memset(inBufSerial, '\0', sizeof(inBufSerial));
  rdlenSerial = read(fdSerial, inBufSerial, (sizeof(inBufSerial) - 1));
  if (rdlenSerial < 0) {
    std::cout << "Read Error. " << errno << std::strerror(errno) << std::endl;
  }
  else if (rdlenSerial == 0) {
    std::cout << "Nothing read." << std::endl;
  }
  else {
    std::cout << "Node Response: ";
    for (int i = 0; i < rdlenSerial; i++) {
      int r = inBufSerial[i];
      std::cout << std::hex << r << std::dec << " " << std::endl;
    }
  }

I really hope this post is not too long to read, any thoughts are appreciated!

BaconPing
  • 3
  • 1
  • You seem to be mixing up very different things. Are you sure your ARM computer is running their serial ports on RS232 and not on TTL levels? You can very easily verify if your problem is related to flow control just disconnecting the RTS and CTS lines on the computer that works. – Marcos G. Aug 13 '19 at 17:33
  • 1
    The **errno** 11 is completely expected given your configuration. You have over-done everything possible to configure the serial terminal into nonblocking mode. Study https://stackoverflow.com/questions/25996171/linux-blocking-vs-non-blocking-serial-read/26006680#26006680. Determine if you want blocking or nonblocking mode. IMO nonblocking mode with serial terminals is overused, rarely warranted/needed, and often poorly implemented. – sawdust Aug 13 '19 at 19:23
  • The ARM is mounted on a carrier board featuring an Exar SP3222EEA-L as RS232 driver, so no worries there (Note also that transmitting works fine). But nice thought regarding disabling RTS/CTS, didn't even think about that! – BaconPing Aug 13 '19 at 19:24
  • @sawdust: I stumbled upon your post several times during the last few days and tried implementing your thoughts several times, however nothing seemed to solve the problem. It seems like my code can't recognize incoming data although it must clearly be there. I also tried `cat`ing the serial port directly on my ARM board, which also worked. (And yes, I absolutely over-did, that's the result of the last few days haha) – BaconPing Aug 13 '19 at 19:31
  • The main program needs to simultaneously read potential input from given controller and an 8 bit value from an eeprom connected via i2c. As I‘m not that experienced, my approach is to alternately read both sources in order to react accordingly. So to my understanding a nonblocking read is in order, but I‘m sure that there are better solutions. – BaconPing Aug 13 '19 at 20:04

1 Answers1

1

Trying to read mentioned responses, however, results in "Errno 11: Resource momentarily unavailable".

The errno 11 is completely expected given your configuration. You have over-done everything possible to configure the serial terminal into nonblocking mode.
First the serial terminal is opened with the O_NDELAY option.
Then a (uninitialized) file descriptor is modified to set the O_NONBLOCK option.
Finally, even though this is ineffective when configured in nonblocking mode, the termios parameters VMIN and VTIME are both configured to zero (i.e. the polling mode).

The "unavailable resource" that the errno refers to is simply data. There is no data available for the syscall to return in the user buffer.

I also tried several ways of blocking behavior, this however resulted in blocking forever and still not reading anything at all.

Without actual code, there is nothing to debug.
You need to decide if your program is going to use blocking or nonblocking mode.


The main program needs to simultaneously read potential input from given controller and an 8 bit value from an eeprom connected via i2c.

You do not need nonblocking mode to ensure "simultaneous" data capture.
The actual input of reading devices is performed by kernel drivers executing completely asynchronous to your application program.
Your application code has essentailly no control of when these input operations actually occur.
So long as the UART does not report a receiver overrun error, you can be assured that all serial data is received and initially stored in the driver's receive buffer.
That data will eventually be copied to the serial terminal's buffer, which is typically 4096 bytes in size.

Reading from the I2C device should also involve a system buffer, but I fail to see any temporal component to reading data from an EEPROM.
That's a nonvolatile storage device that responds to write and read commands.

Your application code is merely trying to fetch data from kernel buffers.
The EEPROM can be read at anytime that is convenient to the program.
The serial terminal needs to be read at a rate that is sufficient to prevent overrun of the system buffer.

Since the communication between your program and the BLDC controller is structured as a request/response dialog, the serial terminal input is solicited, i.e. the program is expecting input only after a request is sent.
Nonblocking mode is perfectly suited for such situations.


Changing

  fcntl(fdSerial, F_SETFL, O_NONBLOCK);

to

  fcntl(fileDescr, F_SETFL, 0);

will put the just-opened file descriptor in blocking mode.

Replacing

  PortParams.c_cc[VMIN] = 0;
  PortParams.c_cc[VTIME] = 0;

with something like:

  PortParams.c_cc[VMIN] = MIN(255, (sizeof(inBufSerial) - 1));
  PortParams.c_cc[VTIME] = 1;

will try to retrieve as many bytes of a response as possible per syscall.
If you prefer or need less latency, then study this answer

The usleep(60000); statement after the write should be removed, or replaced with a call to tcdrain().
In general any fixed delay/sleeping syscalls are questionable.

sawdust
  • 16,103
  • 3
  • 40
  • 50
  • Thank you, I'll implement your suggestions and report back after, hope this will do the trick! I actually already changed `usleep()`to `tcdrain()`shortly after my post as I was getting errors writing several commands in a row. The temporal aspect of reading the EEPROM comes from another uC changing the contained value in case of user input, that's why I want to keep track as close to real-time as possible. – BaconPing Aug 14 '19 at 10:43
  • So I changed the code accordingly (except for VMIN, which I just set to 3 while testing). I also changed the first command to be sent from a reset to a simple status change command to avoid the delay during reboot. Unfortunately, the following `read()`call now seems to block forever. To check again, if there's even any data incoming, I used two SSH consoles to send the same message manually and read the response, which came promptly. – BaconPing Aug 14 '19 at 15:15
  • Small update: I managed to get the call to return by manually rebooting the controller while `read()` is waiting for input. However, the boot-up message consists of 17 bytes, my program only manages to see the last 8 bytes. – BaconPing Aug 14 '19 at 16:03
  • FWIW I did test a C version of the modified code, i.e. replacing the output stuff with printf()s, and that code worked as I expected. Responses of 1 byte or 120 bytes were read as expected. So I'm confident that my version of the code works. Maybe you need to review your code for other mistakes like using uninitialized variable **fdSerial** twice in **configureSerialPort()**? Or post a minimal and complete example as you're supposed to for debugging help. – sawdust Aug 14 '19 at 21:10
  • If you have a serial port or USB adapter for the PC, then try substituting a terminal on the PC for the BLDC controller. The results you describe make no sense, so a more controlled test of the program is required. The SBC-to-BLDC_controller link would be suspect if the SBC-to-PC link tests okay. – sawdust Aug 14 '19 at 21:11
  • Sorry for not getting back earlier, but I just managed to solve the problem. Both my code and the port have been working properly, however the serial port I am using is configured as the standard serial console output. Just had to comment that out in inittab to get my communication to work. Thank you so much for your help nonetheless! – BaconPing Aug 17 '19 at 12:11