C Cannot set PARENB flag on usb-serial adapter (ucom driver)

Hi.

I am writing program to communicate with my power meter - via RS485-USB adapter (ucom driver), using libmodbus.
After investigating libmodbus sources i found that there cannot be set PARENB flag in termios structure (passed to tcsetattr).

That same code (only difference is /dev/ttyUSB0 instead of /dev/cuaU0) works perfectly under Linux - so this is FreeBSD related problem.

Under FreeBSD libmodbus cannot set PARENB flag on serial device. Under Linux it works.

Any sysctl or other solution?
 
Strange. Should work. I run Modbus in Python on both Linux and FreeBSD machines (using a library called minimalmodbus) and have no problem, both with ASCII and RTU protocols. And I also use other serial gadgets (modems, data acquisition) that may use parity and has no problem. Obviously, using python puts another layer into it.

I know it's a lot of work: But can you show the source code, and show what call (to tcsetattr?) fails, and how the data structure was prepared? Maybe something trigger my memory.
 
minimal program which reads something from modbus device (without extensive error checking for simplicity)


C:
#include <errno.h>
#include <modbus.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>


int main() {
    modbus_t *ctx = modbus_new_rtu("/dev/cuaU0", 9600, 'E', 8, 1);
    uint16_t reg[1];
    int rc;
    modbus_set_slave(ctx, 1);
    if (modbus_connect(ctx) == -1) {
        fprintf(stderr, "Connection failed: %s\n", modbus_strerror(errno));
        return -1;
    }

    // read something from device
    rc = modbus_read_registers(ctx, 2, 1, reg);
    if (rc == -1) {
        fprintf(stderr, "Failed to read register: %s\n", modbus_strerror(errno));
        return -1;
    }

    printf("Register value: %d\n", reg[0]);

    modbus_close(ctx);
    modbus_free(ctx);

}

fragment of libmodbus (modbus-rtu.c) where error occurs. I've marked it with comment: // FAILS HERE !!!

tios.c_cflag |= PARENB fails.

C:
    ctx->s = open(ctx_rtu->device, flags);
    if (ctx->s == -1) {
        if (ctx->debug) {
            fprintf(stderr, "ERROR Can't open the device %s (%s)\n",
                    ctx_rtu->device, strerror(errno));
        }
        return -1;
    }

    /* Save */
    tcgetattr(ctx->s, &ctx_rtu->old_tios);

    memset(&tios, 0, sizeof(struct termios));

    /* C_ISPEED     Input baud (new interface)
       C_OSPEED     Output baud (new interface)
    */
    switch (ctx_rtu->baud) {
    case 110:
        speed = B110;
        break;
    case 300:
        speed = B300;
        break;
    case 600:
        speed = B600;
        break;
    case 1200:
        speed = B1200;
        break;
    case 2400:
        speed = B2400;
        break;
    case 4800:
        speed = B4800;
        break;
    case 9600:
        speed = B9600;
        break;
    case 19200:
        speed = B19200;
        break;
    default:
        speed = B9600;
        if (ctx->debug) {
            fprintf(stderr,
                    "WARNING Unknown baud rate %d for %s (B9600 used)\n",
                    ctx_rtu->baud, ctx_rtu->device);
        }
    }

    /* Set the baud rate */
    if ((cfsetispeed(&tios, speed) < 0) ||
        (cfsetospeed(&tios, speed) < 0)) {
        close(ctx->s);
        ctx->s = -1;
        fprintf(stderr, "ERROR: Set baud rate failed (cfsetospeed)");
        return -1;
    }

    tios.c_cflag |= (CREAD | CLOCAL);
    tios.c_cflag &= ~CSIZE;
    switch (ctx_rtu->data_bit) {
    case 5:
        tios.c_cflag |= CS5;
        break;
    case 6:
        tios.c_cflag |= CS6;
        break;
    case 7:
        tios.c_cflag |= CS7;
        break;
    case 8:
    default:
        tios.c_cflag |= CS8;
        break;
    }

    /* Stop bit (1 or 2) */
    if (ctx_rtu->stop_bit == 1)
        tios.c_cflag &=~ CSTOPB;
    else /* 2 */
        tios.c_cflag |= CSTOPB;

    /* PARENB       Enable parity bit
       PARODD       Use odd parity instead of even */
    if (ctx_rtu->parity == 'N') {
        /* None */
        tios.c_cflag &=~ PARENB;
    } else if (ctx_rtu->parity == 'E') {
        /* Even */
        tios.c_cflag |= PARENB;            // FAILS HERE !!!
        tios.c_cflag &=~ PARODD;
    } else {
        /* Odd */
        tios.c_cflag |= PARENB;
        tios.c_cflag |= PARODD;
    }


    /* Raw input */
    tios.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);

    if (ctx_rtu->parity == 'N') {
        /* None */
        tios.c_iflag &= ~INPCK;
    } else {
        tios.c_iflag |= INPCK;
    }

    /* Software flow control is disabled */
    tios.c_iflag &= ~(IXON | IXOFF | IXANY);


    /* Raw output */
    tios.c_oflag &=~ OPOST;

    tios.c_cc[VMIN] = 0;
    tios.c_cc[VTIME] = 0;
    if (tcsetattr(ctx->s, TCSANOW, &tios) < 0) {
        close(ctx->s);
        ctx->s = -1;
        fprintf(stderr, "ERROR: tcsetattr failed: %s\n", strerror(errno));        
        return -1;
    }

This program works under linux but doesn't under FreeBSD (that same hardware).

Any ideas?
 
I hate to be obnoxious, but it works fine for me.

I took the above program, put it into a file called test.c. Installed libmodbus, compiled and linked with the following commands, and ran it (the last line had to be done as root, since normal users can't touch /dev/cuaU0):
Code:
> cc -c -I /usr/local/include/modbus/ test.c -o test.o
> cc -L /usr/local/lib -lmodbus test.o -o test
# ./test
Failed to read register: Response not from requested slave

The failure is to be expected: Right now, there is no modbus device connected to my FreeBSD server, but cuaU0 has a modem (!) attached to it, so the modbus protocol is being violated. I looked around my workbench, and I have no spare RS232 modbus devices: the two Omega controllers are in use (and running a pump and a well), at $300 each I don't have a spare in stock, the spare AutomationDirect controller uses RS485 for which I have no port on my server, the Raspberry (which has an RS485 port) is running Debian, and the actual pump inverter shouldn't get messed with (since it has a 3kW motor attached to it, so I don't want to send it random commands). But there was no failure of setting parity.

If it helps:
Code:
> > freebsd-version -kru
13.3-RELEASE-p1
13.3-RELEASE-p1
13.3-RELEASE-p2
> pkg info libmodbus
libmodbus-3.1.7_2
...
Installed on   : Tue Jun 18 18:38:21 2024 PDT
Origin         : comms/libmodbus
Annotations    :
	FreeBSD_version: 1302001
	build_timestamp: 2024-04-10T08:46:08+0000
	built_by       : poudriere-git-3.4.1-1-g1e9f97d6
	cpe            : cpe:2.3:a:libmodbus:libmodbus:3.1.7:::::freebsd13:x64:2
...

Sorry I can't reproduce the bug. Honestly, looking at the source code, it makes utterly no sense why a line "tios.c_cflag |= PARENB" could fail; this is just an integer bit operation. Suggestion: Something smells rotten in Denmark, to quote Shakespeare. Put some print statements into libmodbus, recompile and run, and try to narrow down that tios.c_flag is, whether you can print it in hex, whether you can set arbitrary bits in it, whether PARENB is properly defined, and that kind of stuff.
 
I've checked this in 'print-way'. Libmodbus errors when PARENB bit is set. Checked on two machines (one with 14.0, other with 13.3). Same result. Maybe the problem is related to RS485/USB converter. Works with linux but with BSD don't. My current converter is cheap one based on CH340 chip, i've ordered more expensive converter based on FTDI chp. As soon as it arrive i will check it.
 
Ohh, we have all kinds of problems with these USB/RS485 converters. Many can't work reliable, and if $PROCURING tries to shave a few bucks by ordering the wrong kind, Techs and Support run up some serious bills. FTDI sounds better, I might run a round call later today what we have working.
 
I've checked this in 'print-way'. Libmodbus errors when PARENB bit is set. Checked on two machines (one with 14.0, other with 13.3). Same result. Maybe the problem is related to RS485/USB converter. Works with linux but with BSD don't. My current converter is cheap one based on CH340 chip, i've ordered more expensive converter based on FTDI chp. As soon as it arrive i will check it.
Ah, that might be the problem! I have thrown away several USB serial converters. Even with FTDI chips, you have to be careful, the cheap ones may use fake clone chips with strange behavior. I now buy my USB adapters from Adafruit. I general, motherboard serial ports seem to be much more reliable.
 
I have replaced CH340 adapter with FTDI based one. And now it works!
It is strange that CH340 connected to that same PC works with Linux but not with FreeBSD. Maybe Linux have some workarounds which allows buggy CH340 to work. I don't have knowledge and time to find differences between those two implementations. Nevertheless i strongly recommend adapters based on FTDI chip. Thanks for help guys:)

This device works:



1487388279_max.jpg
 
I've tired sysctl hw.usb.no_cs_fail=1 but no difference.
I will stick with FTDI dongle. It just works and costs only few bucks more than CH340.
 
Back
Top