AsyncSerial非同步串列埠通訊
阿新 • • 發佈:2019-06-03
AsyncSerial.h
/* * File: AsyncSerial.h * Author: Terraneo Federico * Distributed under the Boost Software License, Version 1.0. * Created on September 7, 2009, 10:46 AM */ #ifndef ASYNCSERIAL_H #define ASYNCSERIAL_H #include <vector> #include <boost/asio.hpp> #include <boost/bind.hpp> #include <boost/thread.hpp> #include <boost/utility.hpp> #include <boost/function.hpp> #include <boost/shared_array.hpp> /** * Used internally (pimpl) */ class AsyncSerialImpl; /** * Asyncronous serial class. * Intended to be a base class. */ class AsyncSerial : private boost::noncopyable { public: AsyncSerial(); /** * Constructor. Creates and opens a serial device. * \param devname serial device name, example "/dev/ttyS0" or "COM1" * \param baud_rate serial baud rate * \param opt_parity serial parity, default none * \param opt_csize serial character size, default 8bit * \param opt_flow serial flow control, default none * \param opt_stop serial stop bits, default 1 * \throws boost::system::system_error if cannot open the * serial device */ AsyncSerial(const std::string &devname, unsigned int baud_rate, boost::asio::serial_port_base::parity opt_parity = boost::asio::serial_port_base::parity( boost::asio::serial_port_base::parity::none), boost::asio::serial_port_base::character_size opt_csize = boost::asio::serial_port_base::character_size(8), boost::asio::serial_port_base::flow_control opt_flow = boost::asio::serial_port_base::flow_control( boost::asio::serial_port_base::flow_control::none), boost::asio::serial_port_base::stop_bits opt_stop = boost::asio::serial_port_base::stop_bits( boost::asio::serial_port_base::stop_bits::one)); /** * Opens a serial device. * \param devname serial device name, example "/dev/ttyS0" or "COM1" * \param baud_rate serial baud rate * \param opt_parity serial parity, default none * \param opt_csize serial character size, default 8bit * \param opt_flow serial flow control, default none * \param opt_stop serial stop bits, default 1 * \throws boost::system::system_error if cannot open the * serial device */ void open(const std::string &devname, unsigned int baud_rate, boost::asio::serial_port_base::parity opt_parity = boost::asio::serial_port_base::parity( boost::asio::serial_port_base::parity::none), boost::asio::serial_port_base::character_size opt_csize = boost::asio::serial_port_base::character_size(8), boost::asio::serial_port_base::flow_control opt_flow = boost::asio::serial_port_base::flow_control( boost::asio::serial_port_base::flow_control::none), boost::asio::serial_port_base::stop_bits opt_stop = boost::asio::serial_port_base::stop_bits( boost::asio::serial_port_base::stop_bits::one)); /** * \return true if serial device is open */ bool isOpen() const; /** * \return true if error were found */ bool errorStatus() const; /** * Close the serial device * \throws boost::system::system_error if any error */ void close(); /** * Write data asynchronously. Returns immediately. * \param data array of char to be sent through the serial device * \param size array size */ void write(const char *data, size_t size); /** * Write data asynchronously. Returns immediately. * \param data to be sent through the serial device */ void write(const std::vector<char> &data); /** * Write a string asynchronously. Returns immediately. * Can be used to send ASCII data to the serial device. * To send binary data, use write() * \param s string to send */ void writeString(const std::string &s); virtual ~AsyncSerial() = 0; /** * Read buffer maximum size */ static const int readBufferSize = 1024; private: /** * Callback called to start an asynchronous read operation. * This callback is called by the io_service in the spawned thread. */ void doRead(); /** * Callback called at the end of the asynchronous operation. * This callback is called by the io_service in the spawned thread. */ void readEnd(const boost::system::error_code &error, size_t bytes_transferred); /** * Callback called to start an asynchronous write operation. * If it is already in progress, does nothing. * This callback is called by the io_service in the spawned thread. */ void doWrite(); /** * Callback called at the end of an asynchronuous write operation, * if there is more data to write, restarts a new write operation. * This callback is called by the io_service in the spawned thread. */ void writeEnd(const boost::system::error_code &error); /** * Callback to close serial port */ void doClose(); boost::shared_ptr<AsyncSerialImpl> pimpl; protected: /** * To allow derived classes to report errors * \param e error status */ void setErrorStatus(bool e); /** * To allow derived classes to set a read callback */ void setReadCallback(const boost::function<void(const char *, size_t)> &callback); /** * To unregister the read callback in the derived class destructor so it * does not get called after the derived class destructor but before the * base class destructor */ void clearReadCallback(); }; /** * Asynchronous serial class with read callback. User code can write data * from one thread, and read data will be reported through a callback called * from a separate thred. */ class CallbackAsyncSerial : public AsyncSerial { public: CallbackAsyncSerial(); /** * Opens a serial device. * \param devname serial device name, example "/dev/ttyS0" or "COM1" * \param baud_rate serial baud rate * \param opt_parity serial parity, default none * \param opt_csize serial character size, default 8bit * \param opt_flow serial flow control, default none * \param opt_stop serial stop bits, default 1 * \throws boost::system::system_error if cannot open the * serial device */ CallbackAsyncSerial(const std::string &devname, unsigned int baud_rate, boost::asio::serial_port_base::parity opt_parity = boost::asio::serial_port_base::parity( boost::asio::serial_port_base::parity::none), boost::asio::serial_port_base::character_size opt_csize = boost::asio::serial_port_base::character_size(8), boost::asio::serial_port_base::flow_control opt_flow = boost::asio::serial_port_base::flow_control( boost::asio::serial_port_base::flow_control::none), boost::asio::serial_port_base::stop_bits opt_stop = boost::asio::serial_port_base::stop_bits( boost::asio::serial_port_base::stop_bits::one)); /** * Set the read callback, the callback will be called from a thread * owned by the CallbackAsyncSerial class when data arrives from the * serial port. * \param callback the receive callback */ void setCallback(const boost::function<void(const char *, size_t)> &callback); /** * Removes the callback. Any data received after this function call will * be lost. */ void clearCallback(); virtual ~CallbackAsyncSerial(); }; #endif //ASYNCSERIAL_H
AsyncSerial.cpp
/*
* File: AsyncSerial.cpp
* Author: Terraneo Federico
* Distributed under the Boost Software License, Version 1.0.
* Created on September 7, 2009, 10:46 AM
*
* v1.02: Fixed a bug in BufferedAsyncSerial: Using the default constructor
* the callback was not set up and reading didn't work.
*
* v1.01: Fixed a bug that did not allow to reopen a closed serial port.
*
* v1.00: First release.
*
* IMPORTANT:
* On Mac OS X boost asio's serial ports have bugs, and the usual implementation
* of this class does not work. So a workaround class was written temporarily,
* until asio (hopefully) will fix Mac compatibility for serial ports.
*
* Please note that unlike said in the documentation on OS X until asio will
* be fixed serial port *writes* are *not* asynchronous, but at least
* asynchronous *read* works.
* In addition the serial port open ignores the following options: parity,
* character size, flow, stop bits, and defaults to 8N1 format.
* I know it is bad but at least it's better than nothing.
*
*/
#include "AsyncSerial.h"
#include <string>
#include <algorithm>
#include <iostream>
#include <boost/bind.hpp>
#include <sys/ioctl.h>
#include <linux/serial.h>
using namespace std;
using namespace boost;
//
//Class AsyncSerial
//
#ifndef __APPLE__
class AsyncSerialImpl : private boost::noncopyable
{
public:
AsyncSerialImpl() : io(), port(io), backgroundThread(), open(false),
error(false) {}
boost::asio::io_service io; ///< Io service object
boost::asio::serial_port port; ///< Serial port object
boost::thread backgroundThread; ///< Thread that runs read/write operations
bool open; ///< True if port open
bool error; ///< Error flag
mutable boost::mutex errorMutex; ///< Mutex for access to error
/// Data are queued here before they go in writeBuffer
std::vector<char> writeQueue;
boost::shared_array<char> writeBuffer; ///< Data being written
size_t writeBufferSize; ///< Size of writeBuffer
boost::mutex writeQueueMutex; ///< Mutex for access to writeQueue
char readBuffer[AsyncSerial::readBufferSize]; ///< data being read
char readBuffer1[AsyncSerial::readBufferSize];
char readBuffer2[AsyncSerial::readBufferSize];
/// Read complete callback
boost::function<void(const char *, size_t)> callback;
};
AsyncSerial::AsyncSerial() : pimpl(new AsyncSerialImpl)
{
}
AsyncSerial::AsyncSerial(const std::string &devname, unsigned int baud_rate,
asio::serial_port_base::parity opt_parity,
asio::serial_port_base::character_size opt_csize,
asio::serial_port_base::flow_control opt_flow,
asio::serial_port_base::stop_bits opt_stop)
: pimpl(new AsyncSerialImpl)
{
open(devname, baud_rate, opt_parity, opt_csize, opt_flow, opt_stop);
}
void AsyncSerial::open(const std::string &devname, unsigned int baud_rate,
asio::serial_port_base::parity opt_parity,
asio::serial_port_base::character_size opt_csize,
asio::serial_port_base::flow_control opt_flow,
asio::serial_port_base::stop_bits opt_stop)
{
if (isOpen())
close();
setErrorStatus(true); //If an exception is thrown, error_ remains true
pimpl->port.open(devname);
pimpl->port.set_option(asio::serial_port_base::baud_rate(baud_rate));
pimpl->port.set_option(opt_parity);
pimpl->port.set_option(opt_csize);
pimpl->port.set_option(opt_flow);
pimpl->port.set_option(opt_stop);
boost::asio::basic_serial_port<boost::asio::serial_port_service>::native_type native = pimpl->port.native(); // serial_port_ is the boost's serial port class.
struct serial_struct serial_tempf;
ioctl(native, TIOCGSERIAL, &serial_tempf);
serial_tempf.flags |= ASYNC_LOW_LATENCY; // (0x2000)
ioctl(native, TIOCSSERIAL, &serial_tempf);
//This gives some work to the io_service before it is started
pimpl->io.post(boost::bind(&AsyncSerial::doRead, this));
thread t(boost::bind(&asio::io_service::run, &pimpl->io));
pimpl->backgroundThread.swap(t);
setErrorStatus(false); //If we get here, no error
pimpl->open = true; //Port is now open
}
bool AsyncSerial::isOpen() const
{
return pimpl->open;
}
bool AsyncSerial::errorStatus() const
{
lock_guard<mutex> l(pimpl->errorMutex);
return pimpl->error;
}
void AsyncSerial::close()
{
if (!isOpen())
return;
pimpl->open = false;
pimpl->io.post(boost::bind(&AsyncSerial::doClose, this));
pimpl->backgroundThread.join();
pimpl->io.reset();
if (errorStatus())
{
throw(boost::system::system_error(boost::system::error_code(),
"Error while closing the device"));
}
}
void AsyncSerial::write(const char *data, size_t size)
{
{
lock_guard<mutex> l(pimpl->writeQueueMutex);
pimpl->writeQueue.insert(pimpl->writeQueue.end(), data, data + size);
}
pimpl->io.post(boost::bind(&AsyncSerial::doWrite, this));
}
void AsyncSerial::write(const std::vector<char> &data)
{
{
lock_guard<mutex> l(pimpl->writeQueueMutex);
pimpl->writeQueue.insert(pimpl->writeQueue.end(), data.begin(),
data.end());
}
pimpl->io.post(boost::bind(&AsyncSerial::doWrite, this));
}
void AsyncSerial::writeString(const std::string &s)
{
{
lock_guard<mutex> l(pimpl->writeQueueMutex);
pimpl->writeQueue.insert(pimpl->writeQueue.end(), s.begin(), s.end());
}
pimpl->io.post(boost::bind(&AsyncSerial::doWrite, this));
}
AsyncSerial::~AsyncSerial()
{
if (isOpen())
{
try
{
close();
}
catch (...)
{
//Don't throw from a destructor
}
}
}
void AsyncSerial::doRead()
{
pimpl->port.async_read_some(asio::buffer(pimpl->readBuffer, readBufferSize),
boost::bind(&AsyncSerial::readEnd,
this,
asio::placeholders::error,
asio::placeholders::bytes_transferred));
// asio::async_read(pimpl->port,asio::buffer(pimpl->readBuffer,readBufferSize),asio::transfer_exactly(54),
// boost::bind(&AsyncSerial::readEnd,
// this,
// asio::placeholders::error,
// asio::placeholders::bytes_transferred));
}
void AsyncSerial::readEnd(const boost::system::error_code &error,
size_t bytes_transferred)
{
if (error)
{
#ifdef __APPLE__
if (error.value() == 45)
{
//Bug on OS X, it might be necessary to repeat the setup
//http://osdir.com/ml/lib.boost.asio.user/2008-08/msg00004.html
doRead();
return;
}
#endif //__APPLE__
//error can be true even because the serial port was closed.
//In this case it is not a real error, so ignore
if (isOpen())
{
doClose();
setErrorStatus(true);
}
}
else
{
if (pimpl->callback)
{
pimpl->callback(pimpl->readBuffer, bytes_transferred);
}
doRead();
}
}
void AsyncSerial::doWrite()
{
//If a write operation is already in progress, do nothing
if (pimpl->writeBuffer == 0)
{
lock_guard<mutex> l(pimpl->writeQueueMutex);
pimpl->writeBufferSize = pimpl->writeQueue.size();
pimpl->writeBuffer.reset(new char[pimpl->writeQueue.size()]);
copy(pimpl->writeQueue.begin(), pimpl->writeQueue.end(),
pimpl->writeBuffer.get());
pimpl->writeQueue.clear();
async_write(pimpl->port, asio::buffer(pimpl->writeBuffer.get(), pimpl->writeBufferSize),
boost::bind(&AsyncSerial::writeEnd, this, asio::placeholders::error));
}
}
void AsyncSerial::writeEnd(const boost::system::error_code &error)
{
if (!error)
{
lock_guard<mutex> l(pimpl->writeQueueMutex);
if (pimpl->writeQueue.empty())
{
pimpl->writeBuffer.reset();
pimpl->writeBufferSize = 0;
return;
}
pimpl->writeBufferSize = pimpl->writeQueue.size();
pimpl->writeBuffer.reset(new char[pimpl->writeQueue.size()]);
copy(pimpl->writeQueue.begin(), pimpl->writeQueue.end(),
pimpl->writeBuffer.get());
pimpl->writeQueue.clear();
async_write(pimpl->port, asio::buffer(pimpl->writeBuffer.get(), pimpl->writeBufferSize),
boost::bind(&AsyncSerial::writeEnd, this, asio::placeholders::error));
}
else
{
setErrorStatus(true);
doClose();
}
}
void AsyncSerial::doClose()
{
boost::system::error_code ec;
pimpl->port.cancel(ec);
if (ec)
setErrorStatus(true);
pimpl->port.close(ec);
if (ec)
setErrorStatus(true);
}
void AsyncSerial::setErrorStatus(bool e)
{
lock_guard<mutex> l(pimpl->errorMutex);
pimpl->error = e;
}
void AsyncSerial::setReadCallback(const boost::function<void(const char *, size_t)> &callback)
{
pimpl->callback = callback;
}
void AsyncSerial::clearReadCallback()
{
pimpl->callback.clear();
}
#else //__APPLE__
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <termios.h>
#include <unistd.h>
class AsyncSerialImpl : private boost::noncopyable
{
public:
AsyncSerialImpl() : backgroundThread(), open(false), error(false) {}
boost::thread backgroundThread; ///< Thread that runs read operations
bool open; ///< True if port open
bool error; ///< Error flag
mutable boost::mutex errorMutex; ///< Mutex for access to error
int fd; ///< File descriptor for serial port
char readBuffer[AsyncSerial::readBufferSize]; ///< data being read
/// Read complete callback
boost::function<void(const char *, size_t)> callback;
};
AsyncSerial::AsyncSerial() : pimpl(new AsyncSerialImpl)
{
}
AsyncSerial::AsyncSerial(const std::string &devname, unsigned int baud_rate,
asio::serial_port_base::parity opt_parity,
asio::serial_port_base::character_size opt_csize,
asio::serial_port_base::flow_control opt_flow,
asio::serial_port_base::stop_bits opt_stop)
: pimpl(new AsyncSerialImpl)
{
open(devname, baud_rate, opt_parity, opt_csize, opt_flow, opt_stop);
}
void AsyncSerial::open(const std::string &devname, unsigned int baud_rate,
asio::serial_port_base::parity opt_parity,
asio::serial_port_base::character_size opt_csize,
asio::serial_port_base::flow_control opt_flow,
asio::serial_port_base::stop_bits opt_stop)
{
if (isOpen())
close();
setErrorStatus(true); //If an exception is thrown, error remains true
struct termios new_attributes;
speed_t speed;
int status;
// Open port
pimpl->fd = ::open(devname.c_str(), O_RDWR | O_NOCTTY | O_NONBLOCK);
if (pimpl->fd < 0)
throw(boost::system::system_error(
boost::system::error_code(), "Failed to open port"));
// Set Port parameters.
status = tcgetattr(pimpl->fd, &new_attributes);
if (status < 0 || !isatty(pimpl->fd))
{
::close(pimpl->fd);
throw(boost::system::system_error(
boost::system::error_code(), "Device is not a tty"));
}
new_attributes.c_iflag = IGNBRK;
new_attributes.c_oflag = 0;
new_attributes.c_lflag = 0;
new_attributes.c_cflag = (CS8 | CREAD | CLOCAL); //8 data bit,Enable receiver,Ignore modem
/* In non canonical mode (Ctrl-C and other disabled, no echo,...) VMIN and VTIME work this way:
if the function read() has'nt read at least VMIN chars it waits until has read at least VMIN
chars (even if VTIME timeout expires); once it has read at least vmin chars, if subsequent
chars do not arrive before VTIME expires, it returns error; if a char arrives, it resets the
timeout, so the internal timer will again start from zero (for the nex char,if any)*/
new_attributes.c_cc[VMIN] = 1; // Minimum number of characters to read before returning error
new_attributes.c_cc[VTIME] = 1; // Set timeouts in tenths of second
// Set baud rate
switch (baud_rate)
{
case 50:
speed = B50;
break;
case 75:
speed = B75;
break;
case 110:
speed = B110;
break;
case 134:
speed = B134;
break;
case 150:
speed = B150;
break;
case 200:
speed = B200;
break;
case 300:
speed = B300;
break;
case 600:
speed = B600;
break;
case 1200:
speed = B1200;
break;
case 1800:
speed = B1800;
break;
case 2400:
speed = B2400;
break;
case 4800:
speed = B4800;
break;
case 9600:
speed = B9600;
break;
case 19200:
speed = B19200;
break;
case 38400:
speed = B38400;
break;
case 57600:
speed = B57600;
break;
case 115200:
speed = B115200;
break;
case 230400:
speed = B230400;
break;
default:
{
::close(pimpl->fd);
throw(boost::system::system_error(
boost::system::error_code(), "Unsupported baud rate"));
}
}
cfsetospeed(&new_attributes, speed);
cfsetispeed(&new_attributes, speed);
//Make changes effective
status = tcsetattr(pimpl->fd, TCSANOW, &new_attributes);
if (status < 0)
{
::close(pimpl->fd);
throw(boost::system::system_error(
boost::system::error_code(), "Can't set port attributes"));
}
//These 3 lines clear the O_NONBLOCK flag
status = fcntl(pimpl->fd, F_GETFL, 0);
if (status != -1)
fcntl(pimpl->fd, F_SETFL, status & ~O_NONBLOCK);
setErrorStatus(false); //If we get here, no error
pimpl->open = true; //Port is now open
thread t(bind(&AsyncSerial::doRead, this));
pimpl->backgroundThread.swap(t);
}
bool AsyncSerial::isOpen() const
{
return pimpl->open;
}
bool AsyncSerial::errorStatus() const
{
lock_guard<mutex> l(pimpl->errorMutex);
return pimpl->error;
}
void AsyncSerial::close()
{
if (!isOpen())
return;
pimpl->open = false;
::close(pimpl->fd); //The thread waiting on I/O should return
pimpl->backgroundThread.join();
if (errorStatus())
{
throw(boost::system::system_error(boost::system::error_code(),
"Error while closing the device"));
}
}
void AsyncSerial::write(const char *data, size_t size)
{
if (::write(pimpl->fd, data, size) != size)
setErrorStatus(true);
}
void AsyncSerial::write(const std::vector<char> &data)
{
if (::write(pimpl->fd, &data[0], data.size()) != data.size())
setErrorStatus(true);
}
void AsyncSerial::writeString(const std::string &s)
{
if (::write(pimpl->fd, &s[0], s.size()) != s.size())
setErrorStatus(true);
}
AsyncSerial::~AsyncSerial()
{
if (isOpen())
{
try
{
close();
}
catch (...)
{
//Don't throw from a destructor
}
}
}
void AsyncSerial::doRead()
{
//Read loop in spawned thread
for (;;)
{
int received = ::read(pimpl->fd, pimpl->readBuffer, readBufferSize);
if (received < 0)
{
if (isOpen() == false)
return; //Thread interrupted because port closed
else
{
setErrorStatus(true);
continue;
}
}
if (pimpl->callback)
pimpl->callback(pimpl->readBuffer, received);
}
}
void AsyncSerial::readEnd(const boost::system::error_code &error,
size_t bytes_transferred)
{
//Not used
}
void AsyncSerial::doWrite()
{
//Not used
}
void AsyncSerial::writeEnd(const boost::system::error_code &error)
{
//Not used
}
void AsyncSerial::doClose()
{
//Not used
}
void AsyncSerial::setErrorStatus(bool e)
{
lock_guard<mutex> l(pimpl->errorMutex);
pimpl->error = e;
}
void AsyncSerial::setReadCallback(const function<void(const char *, size_t)> &callback)
{
pimpl->callback = callback;
}
void AsyncSerial::clearReadCallback()
{
pimpl->callback.clear();
}
#endif //__APPLE__
//
//Class CallbackAsyncSerial
//
CallbackAsyncSerial::CallbackAsyncSerial() : AsyncSerial()
{
}
CallbackAsyncSerial::CallbackAsyncSerial(const std::string &devname,
unsigned int baud_rate,
asio::serial_port_base::parity opt_parity,
asio::serial_port_base::character_size opt_csize,
asio::serial_port_base::flow_control opt_flow,
asio::serial_port_base::stop_bits opt_stop)
: AsyncSerial(devname, baud_rate, opt_parity, opt_csize, opt_flow, opt_stop)
{
}
void CallbackAsyncSerial::setCallback(const boost::function<void(const char *, size_t)> &callback)
{
setReadCallback(callback);
}
void CallbackAsyncSerial::clearCallback()
{
clearReadCallback();
}
CallbackAsyncSerial::~CallbackAsyncSerial()
{
clearReadCal