RS232 - odwrotna konwencja

Problemy dotyczące programowania.

Moderatorzy: Moderatorzy, Administratorzy

kazek3018
Użytkownik
Posty: 181
Rejestracja: 2006-12-10, 14:27

RS232 - odwrotna konwencja

Post autor: kazek3018 » 2009-02-21, 15:36

Cześć

Mam komunikacje po RS232 z zewnętrznym urządzeniem. Port obsługuję bezpośrednio za pomocą systemowych funkcji read() i write().

Kod: Zaznacz cały

int TLinuxCOM::Connect(char *DevName, TConnectionParameters ConnectionParameters)
{
    if (Connected)
    {
        DisConnect();
    }
    this->BaudRate=BaudRate;
    FileDescriptor=open(DevName, O_RDWR | O_NOCTTY);  // O_NDELAY  ->  signal DCD is not active;  O_NOCTTY  ->  program won't to be  "controlling terminal"
    if (FileDescriptor==-1)
    {
        Connected=0;
        BaudRate=0;
        return -1;    // cannot open device with given DevName;
    }
    fcntl(FileDescriptor, F_SETFL, 0);
    fcntl(FileDescriptor, F_SETFL, FNDELAY);      // if input buffer is epmty, then read function returns -1;
    // now we are set the port params;
    memset(&ComControlStructure, 0x00, sizeof(ComControlStructure));
    cfsetispeed(&ComControlStructure, ConnectionParameters.BaudRate);  // transmission baud rate;
    cfsetospeed(&ComControlStructure, ConnectionParameters.BaudRate);  // transmission baud rate;
    if (ConnectionParameters.ParityChecking==1)
    {
        ComControlStructure.c_cflag |= PARENB;               // enable parity checking;
    }
    else
    {
        ComControlStructure.c_cflag &= ~PARENB;               // disable parity checking;
    }
    if (ConnectionParameters.ParityODD==1)
    {
        ComControlStructure.c_cflag |= PARODD;          // set odd parity (ustawianie nie parzystosci);
    }
    else
    {
        ComControlStructure.c_cflag &= ~PARODD;          // set even parity (ustawianie parzystosci);
    }
    switch (ConnectionParameters.StopBits)
    {
        case 1: ComControlStructure.c_cflag &= ~CSTOP;       // 1 stop bit;
                break;
        case 2: ComControlStructure.c_cflag |= CSTOP;        // 2 stop bits;
                break;
        default: ComControlStructure.c_cflag |= CSTOP;       // 2 stop bits;
    }
    switch (ConnectionParameters.DataBits)
    {
        case 5: ComControlStructure.c_cflag |= CS5;           // 5 data bits;
                break;
        case 6: ComControlStructure.c_cflag |= CS6;           // 6 data bits;
                break;
        case 7: ComControlStructure.c_cflag |= CS7;           // 7 data bits;
                break;
        case 8: ComControlStructure.c_cflag |= CS8;           // 8 data bits;
                break;
        default: ComControlStructure.c_cflag |= CS8;          // 8 data bits;
    }
    ComControlStructure.c_cflag |= CREAD;        // enable the receiver;
    ComControlStructure.c_cflag |= CLOCAL;       // set local mode;
    ComControlStructure.c_iflag |= IGNBRK;       // ignore BREAK condition on input;
    ComControlStructure.c_cc[VMIN] = 1;          // minimum number of characters for non-canonical read;
    ComControlStructure.c_cc[VTIME] = 0;         // minimum number of characters for non-canonical read;
    tcsetattr(FileDescriptor, TCSANOW, &ComControlStructure);  // set new port params;
    ioctl(FileDescriptor, TIOCMGET, &ComStatus); // get the actual COM control settings;
    if (ConnectionParameters.DTRSignal==1)
    {
        ComStatus |= TIOCM_DTR;               // enable  DTR signal;
    }
    else
    {
        ComStatus &= ~TIOCM_DTR;              // disable  DTR signal;
    }
    if (ConnectionParameters.CTSSignal==1)
    {
        ComStatus |= TIOCM_CTS;               // enable CTS signal;
    }
    else
    {
        ComStatus &= ~TIOCM_CTS;              // disable CTS signal;
    }
    if (ConnectionParameters.RTSSignal==1)
    {
        ComStatus |= TIOCM_RTS;               // enable RTS signal;
    }
    else
    {
        ComStatus &= ~TIOCM_RTS;              // disable RTS signal;
    }
    ioctl(FileDescriptor, TIOCMSET, &ComStatus); // set the new param of COM;
    tcflush(FileDescriptor, TCIOFLUSH);
    Connected=1;
    return 0;
}
Połączenie pracuje prawidłowo dla konwencji głównej (stan wysoki określa logiczną jedynkę, dane przesyłane są od najmłodszego do najstarszego bitu).
Niestety program musi również prawidłowo współpracować z urządzeniami używającymi konwersji odwróconej (stan wysoki określa logiczne zero, dane przesyłane są od najstarszego do najmłodszego bitu).

Program używa Qt i działa z Windows i Linux, o ile na win32API wszystko pracuje gładko, to pod Linuksem nie mam już za bardzo pomysłów gdzie szukać błędów. Wszystkie ustawienia specyficzne dla Linuksa to te poniżej:

Kod: Zaznacz cały

    ComControlStructure.c_cflag |= CREAD;        // enable the receiver;
    ComControlStructure.c_cflag |= CLOCAL;       // set local mode;
    ComControlStructure.c_iflag |= IGNBRK;       // ignore BREAK condition on input;
    ComControlStructure.c_cc[VMIN] = 1;          // minimum number of characters for non-canonical read;
    ComControlStructure.c_cc[VTIME] = 0;         // minimum number of characters for non-canonical read;
A cała reszta jest identyczna w obydwu przypadkach i cała różnica polega na zmianie "even parity" na "odd parity" w przypadku konversji odwróconej.
Port RS to przejściówka USB<->RS232 na pl2303.
Ma ktoś może jakiś pomysł dlaczego to nie współpracuje akurat z Linuksem?

acek
Użytkownik
Posty: 47
Rejestracja: 2006-09-26, 21:27
Kontakt:

Re: RS232 - odwrotna konwencja

Post autor: acek » 2009-03-07, 02:04

Co oznacza "odwrotna konwencja"? Zmienioną reprezentację bitów danych, bitów parzystości, czy całej transmisji?

Poza tym myślę, że Prolific wypuścił wadliwą serię układów PL2303HX (wersja 3.0, jeśli dobrze pamiętam). Te układy sprawiają takie same problemy w różnych systemach operacyjnych: Linux, OpenBSD, nawet w Windows XP, gdy stosowany jest sterownik dostarczony przez producenta układu.

kazek3018
Użytkownik
Posty: 181
Rejestracja: 2006-12-10, 14:27

Re: RS232 - odwrotna konwencja

Post autor: kazek3018 » 2009-03-07, 17:25

Przejściówka jest ok. Program działa na niej pod Win, problem mam z oprogramowaniem portu RS232 pod Linuksem.

Konwencja odwrotna to: stan wysoki określa logiczne zero, dane przesyłane są od najstarszego do najmłodszego bitu. Ustawienia portu to PARODD, CSTOP (2 bity), CS8.

Wiem może ktoś, gdzie można znaleźć kod źródłowy funkcji cfsetispeed po Linkusa?

Awatar użytkownika
dienet
Moderator
Posty: 2106
Rejestracja: 2007-07-24, 18:58
Lokalizacja: Racibórz/Rybnik
Kontakt:

Re: RS232 - odwrotna konwencja

Post autor: dienet » 2009-03-07, 17:43

cała różnica polega na zmianie "even parity" na "odd parity" w przypadku konversji odwróconej.
Nie wierze w to.
Nie wiem czy Linuks na to pozwala, ale zawsze mozesz zeswapowac bajty przed wyslaniem.
Pozdr0
dienet
[img]http://i164.photobucket.com/albums/u19/slawek15/kotekeo0lq3.jpg[/img]

kazek3018
Użytkownik
Posty: 181
Rejestracja: 2006-12-10, 14:27

Re: RS232 - odwrotna konwencja

Post autor: kazek3018 » 2009-03-07, 17:56

Tak dokładnie całą różnica na tym polega; tylko takie zmiany zachodzą podczas konfiguracji portu w Win, sama reorganizacja danych i potem ponowna ich zamiana do konwencji używanej w programie zachodzi w klasie obsługującej to urządzenie.

Niestety głębsze śledztwo wykazało, że to co próbuje ustawić i to co dostaje sterownik USB to dwie różne rzeczy.
Więc wracam do edukacji na temat portu RS232 w Linux (poprzednie przypadki działały jakimś zbiegiem okoliczności).

Co to znaczy "zeswapowac"? Bo nie wiem o co chodzi.

Awatar użytkownika
dienet
Moderator
Posty: 2106
Rejestracja: 2007-07-24, 18:58
Lokalizacja: Racibórz/Rybnik
Kontakt:

Re: RS232 - odwrotna konwencja

Post autor: dienet » 2009-03-07, 19:28

poprzednie przypadki działały jakimś zbiegiem okoliczności
Czyli typowy UB (undefined behavior)
Co to znaczy "zeswapowac"? Bo nie wiem o co chodzi.
Swap bits, czyli zamienic kolejnosc bitow np. w bajcie.
Pozdr0
dienet
[img]http://i164.photobucket.com/albums/u19/slawek15/kotekeo0lq3.jpg[/img]

kazek3018
Użytkownik
Posty: 181
Rejestracja: 2006-12-10, 14:27

Re: RS232 - odwrotna konwencja

Post autor: kazek3018 » 2009-03-09, 22:28

Kurde za chwile mnie coś trafi.

Za prawidłowe ustawienie pl2303 odpowiada tablica:

Kod: Zaznacz cały

unsigned char tab[7]
Prawidłowe ustawienie to (hex) 80 25 00 00 02 01 08. Jeżeli takie ustawienia wklepie ręcznie w sterownik to program chodzi bez żadnych problemów.

Problemem jest więc ustawienie tych parametrów z programu.
Zacznę od nagłówków jakie włącza program:

Kod: Zaznacz cały

#include <termios.h>   /* POSIX terminal control definitions */
#include <fcntl.h>     /* File control definitions           */
#include <sys/ioctl.h> /* control device params              */

    int FileDescriptor;
    termios ComControlStructure;
    int ComStatus;
Kod ustawiający mam taki:

Kod: Zaznacz cały

FileDescriptor=open(DevName, O_RDWR | O_NOCTTY);  // O_NDELAY  ->  signal DCD is not active;  O_NOCTTY  ->  program won't to be  "controlling terminal"
    if (FileDescriptor==-1)
    {
        Connected=0;
        BaudRate=0;
        return -1;    // cannot open device with given DevName;
    }
    fcntl(FileDescriptor, F_SETFL, 0);
    fcntl(FileDescriptor, F_SETFL, FNDELAY);      // if input buffer is epmty, then read function returns -1;
    // now we are set the port params;
    memset(&ComControlStructure, 0x00, sizeof(ComControlStructure));
    cfsetispeed(&ComControlStructure, B9600);  // transmission baud rate;
    cfsetospeed(&ComControlStructure, B9600);  // transmission baud rate;
    if (CardInterfaceConnectionParameters.ParityChecking==1)
    {
        ComControlStructure.c_cflag |= PARENB;               // enable parity checking;
    }
    else
    {
        ComControlStructure.c_cflag &= ~PARENB;               // disable parity checking;
    }
    if (CardInterfaceConnectionParameters.ParityODD==1)
    {
        ComControlStructure.c_cflag |= PARODD;          // set odd parity (ustawianie nie parzystosci);
    }
    else
    {
        ComControlStructure.c_cflag &= ~PARODD;          // set even parity (ustawianie parzystosci);
    }
    switch (CardInterfaceConnectionParameters.StopBits)
    {
        case 1: ComControlStructure.c_cflag &= ~CSTOP;       // 1 stop bit;
                break;
        case 2: ComControlStructure.c_cflag |= CSTOP;        // 2 stop bits;
                break;
        default: ComControlStructure.c_cflag |= CSTOP;       // 2 stop bits;
    }
    switch (CardInterfaceConnectionParameters.DataBits)
    {
        case 1:
        case 2:
        case 3:
        case 4:
        case 5: ComControlStructure.c_cflag |= CS5;           // 5 data bits;
                break;
        case 6: ComControlStructure.c_cflag |= CS6;           // 6 data bits;
                break;
        case 7: ComControlStructure.c_cflag |= CS7;           // 7 data bits;
                break;
        case 8: ComControlStructure.c_cflag |= CS8;           // 8 data bits;
                break;
        default: ComControlStructure.c_cflag |= CS8;          // 8 data bits;
    }
    ComControlStructure.c_cflag |= CREAD;        // enable the receiver;
    ComControlStructure.c_cflag |= CLOCAL;       // set local mode (disable modem status line check);
    ComControlStructure.c_iflag |= IGNBRK;       // ignore BREAK condition on input;
    ComControlStructure.c_cc[VMIN] = 1;          // minimum number of characters for non-canonical read;
    ComControlStructure.c_cc[VTIME] = 0;         // minimum number of characters for non-canonical read;
    int ret;
    ret=0;
    ret=tcsetattr(FileDescriptor, TCSANOW, &ComControlStructure);  // set new port params;
    ioctl(FileDescriptor, TIOCMGET, &ComStatus); // get the actual COM control settings;
    if (CardInterfaceConnectionParameters.DTRSignal==1)
    {
        ComStatus |= TIOCM_DTR;               // enable  DTR signal;
    }
    else
    {
        ComStatus &= ~TIOCM_DTR;              // disable  DTR signal;
    }
    if (CardInterfaceConnectionParameters.CTSSignal==1)
    {
        ComStatus |= TIOCM_CTS;               // enable CTS signal;
    }
    else
    {
        ComStatus &= ~TIOCM_CTS;              // disable CTS signal;
    }
    if (CardInterfaceConnectionParameters.RTSSignal==1)
    {
        ComStatus |= TIOCM_RTS;               // enable RTS signal;
    }
    else
    {
        ComStatus &= ~TIOCM_RTS;              // disable RTS signal;
    }
    ioctl(FileDescriptor, TIOCMSET, &ComStatus); // set the new param of COM;
    tcflush(FileDescriptor, TCIOFLUSH);
Jeżeli ktoś przypuszcza gdzie może być problem to proszę o pomoc.

Jeżeli ktoś pisał jakiś program z obsługa portu RS232 to może wklei kawałek kodu odpowiedzialny za konfiguracje tego ustrojstwa.

Edit -->
Znalazłem winowajce:

Kod: Zaznacz cały

switch (CardInterfaceConnectionParameters.StopBits)
    {
        case 1: ComControlStructure.c_cflag &= ~CSTOP;       // 1 stop bit;
                break;
        case 2: ComControlStructure.c_cflag |= CSTOP;        // 2 stop bits;
                break;
        default: ComControlStructure.c_cflag |= CSTOP;       // 2 stop bits;
    }
Parametr CSTOP powinien być CSTOPB.

Jeszcze pozostaje mi powalczyć z niestandardowymi prędkościami działania portu.
Ostatnio zmieniony 2009-03-09, 23:25 przez kazek3018, łącznie zmieniany 1 raz.

Awatar użytkownika
dienet
Moderator
Posty: 2106
Rejestracja: 2007-07-24, 18:58
Lokalizacja: Racibórz/Rybnik
Kontakt:

Re: RS232 - odwrotna konwencja

Post autor: dienet » 2009-03-10, 19:33

Co do custom speed ja mialem w jedym programie tak (dla out):

Kod: Zaznacz cały

        if (main_config.dev_config.dev_ospeed == 0)     // custom speed
        {
                cfsetospeed(&main_config.dev_config.new_config, B38400);
                                        
                if (ioctl(main_config.dev_handle, TIOCGSERIAL, &old_serinfo) < 0)
                {
                        spliter_logerror("ioctrl(): %m"); 
                        exit(1);
                }
                new_serinfo = old_serinfo;
                                
                new_serinfo.custom_divisor = new_serinfo.baud_base/out_speed;

#ifdef DEBUG
                        printf ("custom_speed: %d\n", out_speed);
#endif
                                        
                new_serinfo.flags &= ~ASYNC_SPD_MASK;
                new_serinfo.flags |= ASYNC_SPD_CUST;
                 
                if (ioctl(main_config.dev_handle, TIOCSSERIAL, &new_serinfo) < 0)
                {
                        spliter_logerror("ioctrl(): %m");
                        exit(1);
                }
      }
Tylko cos mi sie wydaje ze kernel mi na to nie pozwalal....
Ostatnio zmieniony 2009-03-10, 19:34 przez dienet, łącznie zmieniany 1 raz.
Pozdr0
dienet
[img]http://i164.photobucket.com/albums/u19/slawek15/kotekeo0lq3.jpg[/img]

kazek3018
Użytkownik
Posty: 181
Rejestracja: 2006-12-10, 14:27

Re: RS232 - odwrotna konwencja

Post autor: kazek3018 » 2009-03-15, 19:42

Podając za opisem sterownika FTDI jest kilka sposobów ustawienia niestandardowego Baud Rate:
Non Standard Baudrates
Thanx to Kuba Ober the driver now supports non-standard baudrates using the TIOCSSERIAL interface. I haven't tested this myself so I reproduce Kuba's documentation (slightly edited) he added to the patch below:

* 1. Standard baud rates are set in tty->termios->c_cflag
* 2. If these are not enough, you can set any speed using alt_speed as follows:
* - set tty->termios->c_cflag speed to B38400
* - set your real speed in tty->alt_speed; it gets ignored when
* alt_speed==0, (or)
* - call TIOCSSERIAL ioctl with (struct serial_struct) set as follows:
* flags & ASYNC_SPD_MASK == ASYNC_SPD_[HI, VHI, SHI, WARP], this just
* sets alt_speed to (HI: 57600, VHI: 115200, SHI: 230400, WARP: 460800)
* 3. You can also set baud rate by setting custom divisor as follows
* - set tty->termios->c_cflag speed to B38400
* - call TIOCSSERIAL ioctl with (struct serial_struct) set as follows:
* o flags & ASYNC_SPD_MASK == ASYNC_SPD_CUST
* o custom_divisor set to baud_base / your_new_baudrate
Jeżeli chodzi o strukturę tty_struct to jej pobranie jest zależne od funkcji ioctl, a ta jest implementowana przez sterownik (czytaj wolna amerykanka, jak autor sterownika napisał tą funkcję to jest, jak nie to nie). @dient jeżeli używałeś przejściówki pl2303 to nie kernel a sterownik powodował błędy:

Kod: Zaznacz cały

static int pl2303_ioctl(struct tty_struct *tty, struct file *file,
                         unsigned int cmd, unsigned long arg)
 {
         struct usb_serial_port *port = tty->driver_data;
         dbg("%s (%d) cmd = 0x%04x", __func__, port->number, cmd);
 
         switch (cmd) {
         case TIOCMIWAIT:
                 dbg("%s (%d) TIOCMIWAIT", __func__,  port->number);
                 return wait_modem_info(port, arg);
         default:
                 dbg("%s not supported = 0x%04x", __func__, cmd);
                break;
        }
         return -ENOIOCTLCMD;
 }
Więc za jednym zamachem odpadają te sposoby chyba, że nakażemy użytkownikom używać tylko niektórych przejściówek (bo nie wszędzie są porty COM).
Pewną nadzieje daje funkcja:

Kod: Zaznacz cały

 420speed_t tty_get_baud_rate(struct tty_struct *tty)
 421{
 422        speed_t baud = tty_termios_baud_rate(tty->termios);
 423
 424        if (baud == 38400 && tty->alt_speed) {
 425                if (!tty->warned) {
 426                        printk(KERN_WARNING "Use of setserial/setrocket to "
 427                                            "set SPD_* flags is deprecated\n");
 428                        tty->warned = 1;
 429                }
 430                baud = tty->alt_speed;
 431        }
 432
 433        return baud;
 434}
 435EXPORT_SYMBOL(tty_get_baud_rate);
i powiązana z nią:

Kod: Zaznacz cały

 231speed_t tty_termios_baud_rate(struct ktermios *termios)
 232{
 233        unsigned int cbaud;
 234
 235        cbaud = termios->c_cflag & CBAUD;
 236
 237#ifdef BOTHER
 238        /* Magic token for arbitary speed via c_ispeed/c_ospeed */
 239        if (cbaud == BOTHER)
 240                return termios->c_ospeed;
 241#endif
 242        if (cbaud & CBAUDEX) {
 243                cbaud &= ~CBAUDEX;
 244
 245                if (cbaud < 1 || cbaud + 15 > n_baud_table)
 246                        termios->c_cflag &= ~CBAUDEX;
 247                else
 248                        cbaud += 15;
 249        }
 250        return baud_table[cbaud];
 251}
 252EXPORT_SYMBOL(tty_termios_baud_rate);
Czyli operując standardową strukturą pozwalamy kernel-owi ustawić taką prędkość jaką chcemy:

Kod: Zaznacz cały

#define    BOTHER 0010000               // non standard rate
cfsetispeed(&ComControlStructure, BOTHER);  // transmission baud rate;
cfsetospeed(&ComControlStructure, BOTHER);  // transmission baud rate;
ComControlStructure.c_ospeed=CardInterfaceConnectionParameters.BaudRate;
ComControlStructure.c_ispeed=CardInterfaceConnectionParameters.BaudRate;
Niestety w moim przypadku struktura przekazywana do sterownika ma wyczyszczone flagi c_ospeed i c_ispeed. Może to być błąd w moim programie, ale bardziej podejrzany wydaje się mi fragment:

Kod: Zaznacz cały

/* Set the state of FD to *TERMIOS_P.  */
int
tcsetattr (fd, optional_actions, termios_p)
     int fd;
     int optional_actions;
     const struct termios *termios_p;
{
  struct __kernel_termios k_termios;
  unsigned long int cmd;

  switch (optional_actions)
    {
    case TCSANOW:
      cmd = TCSETS;
      break;
    case TCSADRAIN:
      cmd = TCSETSW;
      break;
    case TCSAFLUSH:
      cmd = TCSETSF;
      break;
    default:
      __set_errno (EINVAL);
      return -1;
    }

  k_termios.c_iflag = termios_p->c_iflag & ~IBAUD0;
  k_termios.c_oflag = termios_p->c_oflag;
  k_termios.c_cflag = termios_p->c_cflag;
  k_termios.c_lflag = termios_p->c_lflag;
  k_termios.c_line = termios_p->c_line;
#if defined _HAVE_C_ISPEED && defined _HAVE_STRUCT_TERMIOS_C_ISPEED
  k_termios.c_ispeed = termios_p->c_ispeed;
#endif
#if defined _HAVE_C_OSPEED && defined _HAVE_STRUCT_TERMIOS_C_OSPEED
  k_termios.c_ospeed = termios_p->c_ospeed;
#endif
  memcpy (&k_termios.c_cc[0], &termios_p->c_cc[0],
	  __KERNEL_NCCS * sizeof (cc_t));

  return INLINE_SYSCALL (ioctl, 3, fd, cmd, &k_termios);
}
libc_hidden_def (tcsetattr)
z pliku: glibc-2.9/sysdeps/unix/sysv/linux/tcsetattr.c
Nie wiem czy to glibc jest na Slacka skompilowane bez tych flag (_HAVE_STRUCT_TERMIOS_C_(I/O)SPEED) czy problem jest jeszcze gdzieś indziej.

Nie widzę na razie sposobu ustawienia tego w sposób niezależny od sterowników. Jeżeli ktoś ma jakieś pomysły to słucham.

ODPOWIEDZ