Portas serie em C++ ou C

ramaro007

Portugal@Home Member
Alguém conhece ou já utilizou alguma classe em C++/C que permita a interacção com as portas séries (vulgo portas COM)? O meu objectivo era criar um dado programa, multi-plataforma, que vá lendo valores recebidos por Bluetooth de uma antena GPS que utiliza a norma NMEA. Se alguém já utilizou algo do género e me puder ajudar agradecia.

PS: Já andei a ver em Qt4 e existe uma biblioteca, mas julgo não ser 100% multi-plataforma.

Obrigado
 
Alguém conhece ou já utilizou alguma classe em C++/C que permita a interacção com as portas séries (vulgo portas COM)? O meu objectivo era criar um dado programa, multi-plataforma, que vá lendo valores recebidos por Bluetooth de uma antena GPS que utiliza a norma NMEA. Se alguém já utilizou algo do género e me puder ajudar agradecia.

PS: Já andei a ver em Qt4 e existe uma biblioteca, mas julgo não ser 100% multi-plataforma.

Obrigado

Questão muito pertinente: *nix ou windows?
 
Eu escrevi no primeiro post multi-plataforma, ou seja, pretendo alguma classe/método que seja tanto compatível em *NIX como Windows, embora já saiba à partida que em *NIX são usados ficheiros do tipo .ffy.xxxx na pasta /dev/ (buffers), enquanto que em windows estou um pouco "às aranhas".

Obrigado
 
é questão é essa mesma é que tu vais usar as rotinas do sistema operativo para aceder aos aparelhos, logo vai ser muito difícil encontrar uma biblioteca que funcione em windows e linux
 
Pois, era o que eu pensava... Então para Windows e Unix terei de usar bibliotecas diferentes, correcto? Já agora, para Unix alguém conhece alguma biblioteca e/ou como interagir com as COM?

Obrigado
 
Pois, era o que eu pensava... Então para Windows e Unix terei de usar bibliotecas diferentes, correcto?

Não me parece. Ou bem que usas directamente as funções Win32 num caso e POSIX no outro, ou bem que usas uma biblioteca que te abstraia das diferenças entre as plataformas. Usar duas bibliotecas não me parece fazer sentido, que vantagem te trazem elas se não a abstracção da plataforma?

Eu diria que vais lá bem sem biblioteca nenhuma, porque quase que há um mapeamento de um-para-um entre as funções da familia tcsetattr & cia do POSIX e as funções SetCommState & CIA do Win32. Ou seja, eu faria tudo com #ifdefs, principalmente se grande parte do código do projecto fosse independente de plataforma e sem grandes bibliotecas (ou seja, não valia a pena chamar para aqui uma biblioteca só para isto).

Não digas a ninguém, deixo-te um excerto de código feito por mim (que só faz sentido no contexto do programa em que está).

Código:
#if defined(__unix__)
#include <termios.h>
#elif defined(_WIN32)
#include <windows.h>
#endif

    class serial_sensor : public sensor {
    public:
#if defined(__unix__)
        /// Enumeration of standard baud rates.
        enum baud_rate {
            b_default = 0,
            b1200 = B1200,
            b2400 = B2400,
            b4800 = B4800,
            b9600 = B9600,
            b19200 = B19200,
            b38400 = B38400,
            b57600 = B57600,
            b115200 = B115200,
            b230400 = B230400,
            b460800 = B460800,
            b921600 = B921600,
            b1152000 = B1152000
        };

        /// Enumeration of standard character sizes.
        enum character_size {
            cs5 = CS5,
            cs6 = CS6,
            cs7 = CS7,
            cs8 = CS8
        };

        /// Enumeration of stop bit sizes.
        enum stop_bits {
            one = 0,
            two = CSTOPB
        };

        /// Enumeration of parity modes.
        enum parity {
            none = 0,
            even = PARENB,
            odd = PARENB | PARODD
        };
#elif defined(_WIN32)
        /// Enumeration of standard baud rates.
        enum baud_rate {
            b_default = 0,
            b1200 = CBR_1200,
            b2400 = CBR_2400,
            b4800 = CBR_4800,
            b9600 = CBR_9600,
            b19200 = CBR_19200,
            b38400 = CBR_38400,
            b57600 = CBR_57600,
            b115200 = CBR_115200,
            b230400 = 230400,
            b460800 = 460800,
            b921600 = 921600,
            b1152000 = 1152000
        };

        /// Enumeration of standard character sizes.
        enum character_size {
            cs5 = 5,
            cs6 = 6,
            cs7 = 7,
            cs8 = 8
        };

        /// Enumeration of stop bit sizes.
        enum stop_bits {
            one = ONESTOPBIT,
            two = TWOSTOPBITS
        };

        /// Enumeration of parity modes.
        enum parity {
            none = NOPARITY,
            even = EVENPARITY,
            odd = ODDPARITY
        };
#endif

    private:
        /// \defgroup connection
        /// Connection parameters.
        /// \{
        std::string _device;    ///< Serial port device name.
        baud_rate _baud;        ///< Baud rate.
        character_size _cs;     ///< Character size.
        stop_bits _stop_bits;   ///< Stop bits.
        parity _parity;         ///< Parity modes.
        unsigned long _rate;    ///< Numeric baud rate.
        /// \}

        /// \defgroup portstate
        /// Serial port state.
        /// \{
#if defined(__unix__)
        int _fd;                ///< Serial port file descriptor.
        termios _previous;      ///< Serial port state prior to configuration.
#elif defined(_WIN32)
        HANDLE _hfile;          ///< Serial port handle.
        DCB _dcb;               ///< Serial port previous device control block.
        COMMTIMEOUTS _cto;      ///< Serial port previous communications timeouts.
#endif
         /// \}
    };
Código:
    serial_sensor::serial_sensor(size_t size, baud_rate baud)
        : 
#if defined(__unix__)
          _device("/dev/ttyS0"),
#elif defined(_WIN32)
          _device("\\\\.\\COM1"),
#endif
          _baud(baud), _cs(cs8), _stop_bits(two), _parity(none), _rate(baud_to_rate(baud))  { }

    void serial_sensor::open(direction mode)
    {
#if defined(__unix__)
        // Open the serial port.
        if ((_fd = ::open(_device.c_str(),
                (mode == input ? O_RDONLY :
                 mode == output ? O_WRONLY :
                 O_RDWR) | O_NOCTTY)) == -1) {
            throw system_error("open()");
        }

        // Get the previous port state to restore latter.
        if (tcgetattr(_fd, &_previous) == -1) {
            ::close(_fd);
            _fd = -1;
            throw system_error("tcgetattr()");
        }
#elif defined(_WIN32)
         // Open the serial port.
        if ((_hfile = CreateFileA(_device.c_str(),
                (mode == input ? GENERIC_READ :
                 mode == output ? GENERIC_WRITE :
                 GENERIC_READ | GENERIC_WRITE),
                0, 0, OPEN_EXISTING, 0, 0)) == INVALID_HANDLE_VALUE) {
            throw win32_error("CreateFile()");
        }

        // Get the previous port state to restore latter.
        memset(&_dcb, 0, sizeof(_dcb));
        _dcb.DCBlength = sizeof(_dcb);
        if (!GetCommState(_hfile, &_dcb)) {
            CloseHandle(_hfile);
            _hfile = 0;
            throw win32_error("GetCommState()");
        }

        // Get the previous port timeouts to restore latter.
        if (!GetCommTimeouts(_hfile, &_cto)) {
            CloseHandle(_hfile);
            _hfile = 0;
            throw win32_error("GetCommTimeouts()");
        }

        // Set communications timeouts.
        COMMTIMEOUTS cto = _cto;        // Initialize to previous timeouts.

        cto.ReadIntervalTimeout = 10;
        cto.ReadTotalTimeoutConstant = 0;
        cto.ReadTotalTimeoutMultiplier = 0;
        cto.WriteTotalTimeoutConstant = 0;
        cto.WriteTotalTimeoutMultiplier = 0;

        // Set the serial port timeouts.
        if (!SetCommTimeouts(_hfile, &cto)) {
            throw win32_error("SetCommTimeouts()");
        }
#endif
     }

    void serial_sensor::set(baud_rate baud)
    {
#if defined(__unix__)
        termios settings = _previous;   // Initialize to current settings.
        cfmakeraw(&settings);           // As if stty raw.

        settings.c_cflag &= ~CSIZE;     // Clear character size mask.
        settings.c_cflag |= _cs;        // Set character size.
        settings.c_cflag &= ~CSTOPB;    // Clear stop bits mask.
        settings.c_cflag |= _stop_bits; // Set stop bit size.
        settings.c_cflag &= ~(PARENB|PARODD);   // Clear parity mask.
        settings.c_cflag |= _parity;    // Set parity.

        // Set baud rate only if not b_default.
        if (baud == b_default) baud = _baud;
        if (baud != b_default) {
            _baud = baud, _rate = baud_to_rate(baud);
            cfsetispeed(&settings, _baud);
            cfsetospeed(&settings, _baud);
        }

        // Set the serial port configuration.
        if (tcsetattr(_fd, TCSANOW, &settings) == -1) {
            throw system_error("tcsetattr()");
        }
#elif defined(_WIN32)
        DCB dcb = _dcb;                 // Initialize to previous settings.

        dcb.ByteSize = _cs;             // Set character size.
        dcb.StopBits = _stop_bits;      // Set stop bit size.
        dcb.Parity = _parity;           // Set parity.

        // Set baud rate only if not b_default.
        if (baud == b_default) baud = _baud;
        if (baud != b_default) {
            _baud = baud, _rate = baud_to_rate(baud);
            dcb.BaudRate = _baud;
        }

        // Set the serial port configuration.
        if (!SetCommState(_hfile, &dcb)) {
            throw win32_error("SetCommState()");
        }
#endif
    }

    void serial_sensor::write(const char* p, std::size_t size) const
    {
#if defined(__unix__)
        while (size > 0) {
            ssize_t n = ::write(_fd, p, size);
            if (n == -1) throw system_error("write()");

            p += n;
            size -= n;
        }
#elif defined(_WIN32)
        while (size > 0) {
            DWORD written;
            if (!WriteFile(_hfile, p, DWORD(size), &written, 0)) {
                throw win32_error("WriteFile()");
            }

            p += written;
            size -= written;
        }
#endif
    }

    std::size_t serial_sensor::read(precision_time to)
    {
#if defined(__unix__)
        // Prepare arguments for select().
        fd_set readfds;
        FD_ZERO(&readfds);
        FD_SET(_fd, &readfds);
        timeval timeout = to;

        // Wait for the serial port to be ready for reading.
        int n = select(_fd + 1, &readfds, 0, 0, &timeout);
        if (n == -1) throw system_error("select()");

        // Read data if available.
        if (n != 0) {
            n = ::read(_fd, _buffer.obeginp(), _buffer.ossize());
            if (n == -1) throw system_error("read()");

            return size_t(n);
        }
        else {
            return 0;
        }
#elif defined(_WIN32)
        // Wait for the serial port to be ready for reading.
        if (!SetCommMask(_hfile, EV_RXCHAR)) {
            throw win32_error("SetCommMask()");
        }
        DWORD EvtMask = 0;
        if (!WaitCommEvent(_hfile, &EvtMask, 0)) {
            throw win32_error("WaitCommEvent()");
        }
        if (!SetCommMask(_hfile, 0)) {
            throw win32_error("SetCommMask()");
        }

        // Read data if available.
        DWORD n;
        if (!ReadFile(_hfile, _buffer.obeginp(), DWORD(_buffer.ossize()), &n, 0)) {
            throw win32_error("ReadFile()");
        }

        return size_t(n);
#endif
    }

Também tenho imenso código NMEA...
 
Lá está, é a diversidade de opiniões.

Chamo a atenção de que em UNIX ler de uma porta série, ou ler de um ficheiro, é practicamente a mesma coisa (a única diferença é que quando eu peço 100 bytes de um ficheiro eles vêm sempre a não ser que chegue ao fim do ficheiro, na porta série ele pode devolver só 1, só 16...). Só difere naquele momento inicial em que tens que configurar a porta para 4800 baud, etc, etc. No Win32 também é a mesma coisa, apenas estamos menos acostumados a usar as funções de leitura de ficheiros directamente (CreateFile, ReadFile, etc).
 
Back
Topo