0

I am trying to read serial data from my Duet2 board using the following code

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <termios.h>

int main(int argc, char ** argv) 
{
  int fd;
  struct termios SerialPortSettings;

  // Open the Port. We want read/write, no "controlling tty" status, and open it no matter what state DCD is in
  fd = open("/dev/ttyACM0", O_RDWR | O_NOCTTY | O_NDELAY);
  
  // setting baud rates 

  cfsetispeed(&SerialPortSettings,B115200); 
  cfsetospeed(&SerialPortSettings,B115200);
        
  if (fd == -1) 
  {
    perror("open_port: Unable to open /dev/ttyACM0 - ");
    return(-1);
  }

  fcntl(fd, F_SETFL, 0);

 
  // Read up to 255 characters from the port if they are there

  char buf[256];
  
  while (1)
  {
    int k = read(fd, (void*)buf, 255);
    if (k < 0) 
    {
      perror("Read failed - ");
      return -1;
    } 
    else if (k == 0) 
    {
      printf("No data on port\n");
    }
    else 
    {
      printf("Read line is : %s \n",buf);
      
    }
  }
  
  close(fd);
  return 0;
}

The output that I expect looks something like :

N 0.500000 169.649002 108.239998 0.300000 0.000000 0.000000 0.000000 41926.273438 0.000000 41926.273438 83852 0.0000e+0 1.1926e-5 0.0000e+0 0 -0.500000

N 0.500000 169.649002 108.239998 0.300000 0.000000 0.000000 0.000000 41926.273438 0.000000 41926.273438 83852 0.0000e+0 1.1926e-5 0.0000e+0 0 -0.500000

N 6.769390 169.649002 108.239998 0.300000 5.300995 4.209999 0.000000 96530.593750 0.000000 96530.593750 193061 0.0000e+0 7.0127e-5 0.0000e+0 1 0.000000

N 0.500000 174.949997 112.449997 0.300000 0.000000 0.000000 0.000000 41926.273438 0.000000 41926.273438 83852 0.0000e+0 1.1926e-5 0.0000e+0 1 0.500000

N 15.100006 174.949997 112.449997 0.300000 15.100006 0.000000 0.000000 53037.558594 917231.625000 970269.187500 970269 0.0000e+0 1.6000e-5 1.6000e-5 1 1.265568

Warning: Tool 0 was not driven because its heater temperatures were not high enough or it has a heater fault

N 15.100006 190.050003 112.449997 0.300000 0.000000 15.100006 0.000000 0.000000 943750.375000 943750.375000 943750 1.6000e-5 1.6000e-5 1.6000e-5 1 1.265663

N 6.769390 169.649002 108.239998 0.300000 5.300995 4.209999 0.000000 96530.593750 0.000000 96530.593750 193061 0.0000e+0 7.0127e-5 0.0000e+0 1 0.000000
    
N 0.500000 174.949997 112.449997 0.300000 0.000000 0.000000 0.000000 41926.273438 0.000000 41926.273438 83852 0.0000e+0 1.1926e-5 0.0000e+0 1 0.500000
    
N 15.100006 174.949997 112.449997 0.300000 15.100006 0.000000 0.000000 53037.558594 917231.625000 970269.187500 970269 0.0000e+0 1.6000e-5 1.6000e-5 1 1.265568

Warning: Tool 0 was not driven because its heater temperatures were not high enough or it has a heater fault
    
N 15.100006 190.050003 112.449997 0.300000 0.000000 15.100006 0.000000 0.000000 943750.375000 943750.375000 943750 1.6000e-5 1.6000e-5 1.6000e-5 1 1.265663

i.e. a line starting with N and followed by some numerical values and occasionally get some warnings about the heater.

However, the output I get is :

Read line is Warning: Tool 0 was not driven because its heater temperatures were not high enough or it has a heater fault
 
Read line is Warning: Tool 0 was not driven because its heater temperatures were not high enough or it has a heater fault
 
Read line is Warning: Tool 0 was not driven because its heater temperatures were not high enough or it has a heater fault
 
Read line is Warning: Tool 0 was not driven because its heater temperatures were not high enough or it has a heater fault
 
Read line is Warning: Tool 0 was not driven because its heater temperatures were not high enough or it has a heater fault
 
Read line is Warning: Tool 0 was not driven because its heater temperatures were not high enough or it has a heater fault
 
Read line is Warning: Tool 0 was not driven because its heater temperatures were not high enough or it has a heater fault

Why does my code choose to ignore the values I am interested in? I know the Duet board gives me the expected result on the serial port since I am able to read it using python however I would like to do it using C.

EDIT : The python code

import serial
import time
import subprocess

LINUX = 1

output = subprocess.check_output("python -m serial.tools.list_ports", shell=True) # sending command to terminal 
output = output.decode('utf-8') # the output is of type b'somestring' This removes that
output = output.splitlines() # so we can access the COM part

if LINUX:
    temp = output[0].split()
    port = temp[0]
else:
    port =  output[0]
    
data = open("Input.txt", "w")
ser = serial.Serial(port, 115200, timeout=30)
print("Serial Port opened on : " + ser.name)

counter = 0
while (1):
    response = ser.readline().decode('utf-8')
    print(response)
    data.write(response)
    if response == "":
        break

time.sleep(1)
data.close()
HDEV5
  • 17
  • 3
  • 1
    You can try `buf[k] = '\0';` before printing `buf` (`read` doesn't add a trailing 0) – David Ranieri Nov 30 '21 at 22:28
  • @DavidRanieri I tried that but unfortunately there is no change in the output – HDEV5 Nov 30 '21 at 22:33
  • @TedLyngmo I made that change but didn't work :/ – HDEV5 Nov 30 '21 at 22:36
  • No, that is just a more correct way to do it. Did add `return 1;` or something if `k == 0`? – Ted Lyngmo Nov 30 '21 at 22:38
  • What is `fcntl(fd, F_SETFL, 0);` supposed to do btw? – Ted Lyngmo Nov 30 '21 at 22:38
  • 1
    @TedLyngmo iirc it sets the flag to blocking mode – HDEV5 Nov 30 '21 at 22:44
  • Your code does not configure any termios settings whatsoever. Your program uses the existing termios settings, including the baudrate! – sawdust Nov 30 '21 at 22:46
  • Regarding my previous comment: Make it `ssize_t k = read(fd, buf, sizeof buf - 1);` to be able to do `buf[k] = '\0';` as @DavidRanieri suggested. – Ted Lyngmo Nov 30 '21 at 22:47
  • Is it opened with `O_NONBLOCK`? I don't see that. If you want non-blocking, I wouldn't use `0`. `int flags = fcntl(fd, F_GETFL, 0); fcntl(fd, F_SETFL, flags & ~O_NONBLOCK);` would be safer. – Ted Lyngmo Nov 30 '21 at 22:49
  • For working code that reads lines (of text) (aka canonical mode) from a serial terminal, see https://stackoverflow.com/questions/57152937/canonical-mode-linux-serial-port – sawdust Nov 30 '21 at 22:56
  • 1
    Maybe you have to send some command to your device to get it to report those lines that start with N? Can you just open the serial port using a program like `screen` or PuTTY and verify that those "N" lines are getting sent to the computer? It might be good to simplify your working Python code down to something less than 10 lines and post it here. – David Grayson Nov 30 '21 at 23:24
  • 1
    @DavidGrayson The commands are being fetched from a gcode file. I have added the python code that works – HDEV5 Nov 30 '21 at 23:55
  • @sawdust Thanks for the link to the code however it gives me the same results. The lines starting with N are not displayed – HDEV5 Nov 30 '21 at 23:57
  • @sawdust Am I not setting the baud rate with these lines ```cfsetispeed(&SerialPortSettings,B115200); cfsetospeed(&SerialPortSettings,B115200);``` – HDEV5 Dec 01 '21 at 00:10
  • *"Am I not setting the baud rate with these lines ..."* -- No, that merely makes assignments to a local structure (which is uninitialized BTW). Your code never passes that structure to the system with a **tcsetattr()**. See [Serial Programming Guide for POSIX Operating Systems](http://www.cmrr.umn.edu/~strupp/serial.html) and [Setting Terminal Modes Properly](https://www.gnu.org/software/libc/manual/html_node/Setting-Modes.html) – sawdust Dec 01 '21 at 00:22
  • *"... however it gives me the same results. The lines starting with N are not displayed"* -- Then we can reasonably assume that those lines are not being transmitted by your Duet2 board to the host PC. *"The commands are being fetched from a gcode file"* -- Please explain what this means. (I know nothing about Python.) – sawdust Dec 01 '21 at 00:30
  • @sawdust thanks for the links. I will set the attributes and revert back. I did initialize the struct, just forgot to include in the code. I have edited the code to reflect the changes.Furthermore, I am 100% sure that the duet board is sending the lines with N since I have see them being populated on the terminal when I use YAT or python. The gcodes are fed to the duet board through an UI used over a network connection in a browser. – HDEV5 Dec 01 '21 at 00:55
  • *"The gcodes are fed to the duet board through an UI used over a network connection in a browser"* -- But how are these *"gcodes"* (or mystery commands) sent to the duet board when executing your or my C program? Seems like the solution is where I have sample output in my program, you would need to insert the equivalent of those 5 Python statements for `output = ...` to the `port = ...` operation. – sawdust Dec 01 '21 at 01:15
  • @sawdust I added the `tcsetattr(fd, TCSAFLUSH, &SerialPortSettings)` but it still doesn't help – HDEV5 Dec 01 '21 at 02:29
  • *"I added the tcsetattr(fd, TCSAFLUSH, &SerialPortSettings) but it still doesn't help"* -- Minor termios fixes to your code are simply not going to solve the problem. Suggest you re-read all of my comments. As @DavidGrayson asked, you seem oblivious on how to properly initialize this device so that it responds as you expect. – sawdust Dec 01 '21 at 03:37
  • Nothing in the Python program actually writes anything to the serial port (including the `output = ` lines) so that was a dead end. In C, `printf` never prints anything after a null character, since that marks the end of a string. I suggest making a simple loop in the C program to check each byte read from the port and see if any are 0 bytes received, just as a debugging measure. – David Grayson Dec 01 '21 at 04:48
  • @DavidGrayson *"Nothing in the Python program actually writes anything to the serial port ..."* -- I'm skeptical since there's an embedded comment `# sending command to terminal`. Instead of checking for NULs, I would rather see a test of your assertion by deleting those Python statements from the first `output = ...` to the `port = output[0]` operation. Since the OP didn't implement all of the Python functionality in the C version, we need a test of the Python program with just the equivalent functionality that was implemented in the "misbehaving" C program. – sawdust Dec 01 '21 at 09:39
  • @DavidGrayson *"I suggest ... see if any are 0 bytes received, just as a debugging measure."* -- No need, this has already been checked. The OP reported that he tried my canonical mode program, and got *"lines starting with N are not displayed."* That program will display all bytes in the read buffer in hex and as printable ASCII. So I conclude that there is no data that has been received but not displayed. That implies that those *"lines starting with N"* have not been transmitted by the device, and hence cannot be received/read/displayed. – sawdust Dec 02 '21 at 06:44

1 Answers1

0
  1. Please consider changing your subject:
  • Probably misleading: Serial read on linux using C omits numerical data (?)
  • Better: Problems with Serial read on linux using C
  1. Try this code (untested). Note the error checking.

    #include <stdio.h>
    #include <unistd.h>
    #include <fcntl.h>
    #include <termios.h>
    #include <errno.h>
    
    #define BUFLEN 255
    
    int main()
    {
      char device = "/dev/ttyACM0";
      struct termios SerialPortSettings;
      char buf[BUFLEN];
      int fd, iret;
    
      // Open the Port. We want read/write, no "controlling tty" status, and open it no matter what state DCD is in
      if ((fd = open(device, O_RDWR | O_NOCTTY | O_NDELAY) < 0) {
        perror(device);
        return(-1);
      }
    
      // setting baud rates 
      tcgetattr(fd, &SerialPortSettings);
      if (cfsetispeed(&SerialPortSettings,B115200) < 0) {
        perror("cfsetispeed error: ");
      }
      if (tcsetattr(fd, TCSANOW, &SerialPortSettings) < 0) {
        perror("tcsetattr error: ");
      }
    
      // Read up to 255 characters from the port if they are there
      iret = read(fd, buf, BUFLEN-1);
      if (iret < 0){
        perror("read error: ");
      }
      else {
        buf[iret] = 0;
        printf("Read line is : %s, #/bytes= %d\n", buf, iret);
      }
    
      close(fd);
      return 0;
    }
    
  2. This link might help:

    https://tldp.org/HOWTO/Serial-Programming-HOWTO/

paulsm4
  • 114,292
  • 17
  • 138
  • 190
  • (1) OP seems unaware that the Python program initializes the target device to transmit data messages, so this untested code will not produce expected results. (2) This code has incomplete termios initialization. (3) Suggested guide is inferior as its code examples are not POSIX compliant. – sawdust Dec 01 '21 at 00:48
  • @paulsm4 I did try to tun this but it gave me a segmentation fault. After running gdb backtrace, the fault seems to lie in line 19 i the `perror(device)` – HDEV5 Dec 01 '21 at 00:59
  • @HDEV5: If I happened to have a serial device handy on Linux, I would have written and tested a complete example. I don't, so I was hoping the example code would "help". Apologies if that was a mistake. TAKEWAYS: 1) You can easily do serial I/O in either Python or C. Take your pick: but use one or the other, not both. 2) ALWAYS check for "error status" on EACH I/O call. An "error" needn't abort the entire program - but you SHOULD at least CHECK for errors. – paulsm4 Dec 01 '21 at 01:48