diff --git a/.gitignore b/.gitignore index 1fc2c47..a749124 100644 --- a/.gitignore +++ b/.gitignore @@ -4,4 +4,7 @@ cmake-build-debug/ .vscode/ # Sphinx build dir -docs/_build/ \ No newline at end of file +docs/_build/ + +# Build artifacts +a.out \ No newline at end of file diff --git a/.vscode/c_cpp_properties.json b/.vscode/c_cpp_properties.json index 0ee9eae..81ce4af 100644 --- a/.vscode/c_cpp_properties.json +++ b/.vscode/c_cpp_properties.json @@ -60,7 +60,8 @@ }, "compilerPath": "/usr/bin/gcc", "cStandard": "c11", - "cppStandard": "c++17" + "cppStandard": "c++17", + "configurationProvider": "ms-vscode.cmake-tools" }, { "name": "Win32", diff --git a/CHANGELOG.md b/CHANGELOG.md index 8b85fa4..8902279 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,17 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ## [Unreleased] +## [v2.1.0] - 2020-11-08 + +### Added +- Support for custom baud rates. +- Support for all standard UNIX baud rates. +- Improved Doxygen documentation. +- Improved README.md documentation. + +### Removed +- Dependencies from the README, they weren't that useful and were not accurate anyway. + ## [v2.0.3] - 2020-10-13 ### Added diff --git a/README.md b/README.md index 75f4a19..caea473 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # CppLinuxSerial -Serial port library written in C++ +Linux serial port library written in C++. [![Build Status](https://travis-ci.org/gbmhunter/CppLinuxSerial.svg?branch=master)](https://travis-ci.org/gbmhunter/CppLinuxSerial) @@ -8,7 +8,9 @@ Serial port library written in C++ Library for communicating with COM ports on a Linux system. -Uses fstream to the file I/O. +* Simple API +* Supports custom baud rates +* cmake based build system ## Installation @@ -66,18 +68,20 @@ using namespace mn::CppLinuxSerial; int main() { // Create serial port object and open serial port - SerialPort serialPort0("/dev/ttyUSB0", BaudRate::B_57600); - serialPort0.Open(); + SerialPort serialPort("/dev/ttyUSB0", BaudRate::B_57600); + // Use SerialPort serialPort("/dev/ttyACM0", 13000); instead if you want to provide a custom baud rate + serialPort.SetTimeout(-1); // Block when reading until any data is received + serialPort.Open(); // Write some ASCII datae - serialPort0.Write("Hello"); + serialPort.Write("Hello"); - // Read some data back + // Read some data back (will block until at least 1 byte is received due to the SetTimeout(-1) call above) std::string readData; - serialPort0.Read(readData); + serialPort.Read(readData); // Close the serial port - serialPort0.Close(); + serialPort.Close(); } ``` @@ -87,34 +91,7 @@ If the above code was in a file called `main.cpp` and you had installed `CppLinu g++ main.cpp -lCppLinuxSerial ``` -For more examples, see the `.cpp` files in `test/unit/`. - -## Dependencies - -The following table lists all of the libraries dependencies. - - - - - - - - - - - - - - - - - - - - - - -
DependencyComments
C++14C++14 used for strongly typed enums, `std::chrono` and literals.
stdio.hsnprintf()
sttyUsed in unit tests to verify the serial port is configured correctly.
+For more examples, see the files in `test/`. ## Issues @@ -122,7 +99,7 @@ See GitHub Issues. ## FAQ -1. My code stalls when calling functions like `SerialPort::Read()`. This is probably because the library is set up to do a blocking read, and not enough characters have been received to allow `SerialPort::Read()` to return. Use `SerialPort::SetNumCharsToWait()` to determine how many characters to wait for before returning (set to 0 for non-blocking mode). +1. My code stalls when calling functions like `SerialPort::Read()`. This is probably because the library is set up to do a blocking read, and not enough characters have been received to allow `SerialPort::Read()` to return. Call `SerialPort::SetTimeout(0)` before the serial port is open to set a non-blocking mode. ## Changelog diff --git a/include/CppLinuxSerial/SerialPort.hpp b/include/CppLinuxSerial/SerialPort.hpp index 1ece112..6739e7a 100644 --- a/include/CppLinuxSerial/SerialPort.hpp +++ b/include/CppLinuxSerial/SerialPort.hpp @@ -15,8 +15,11 @@ #include #include // For file I/O (reading/writing to COM port) #include -#include // POSIX terminal control definitions (struct termios) +// #include // POSIX terminal control definitions (struct termios) +// #include // Terminal control definitions (struct termios) #include +#include +#include // User headers #include "Exception.hpp" @@ -24,18 +27,44 @@ namespace mn { namespace CppLinuxSerial { + /// \brief Represents the baud rate "types" that can be used with the serial port. STANDARD represents all + /// the standard baud rates as provided by UNIX, CUSTOM represents a baud rate defined by an arbitray integer. + enum class BaudRateType { + STANDARD, + CUSTOM, + }; + /// \brief Strongly-typed enumeration of baud rates for use with the SerialPort class + /// \details Specifies all the same baud rates as UNIX, as well as B_CUSTOM to specify your + /// own. See https://linux.die.net/man/3/cfsetispeed for list of supported UNIX baud rates. enum class BaudRate { + B_0, + B_50, + B_75, + B_110, + B_134, + B_150, + B_200, + B_300, + B_600, + B_1200, + B_1800, + B_2400, + B_4800, B_9600, + B_19200, B_38400, B_57600, B_115200, - CUSTOM + B_230400, + B_460800, + B_CUSTOM, // Placeholder }; + /// \brief Represents the state of the serial port. enum class State { CLOSED, - OPEN + OPEN, }; /// \brief SerialPort object is used to perform rx/tx serial communication. @@ -48,15 +77,22 @@ namespace mn { /// \brief Constructor that sets up serial port with the basic (required) parameters. SerialPort(const std::string &device, BaudRate baudRate); - //! @brief Destructor. Closes serial port if still open. + /// \brief Constructor that sets up serial port with the basic (required) parameters. + SerialPort(const std::string &device, speed_t baudRate); + + /// \brief Destructor. Closes serial port if still open. virtual ~SerialPort(); /// \brief Sets the device to use for serial port communications. /// \details Method can be called when serial port is in any state. void SetDevice(const std::string &device); + /// \brief Allows the user to set a standard baud rate. void SetBaudRate(BaudRate baudRate); + /// \brief Allows the user to set a custom baud rate. + void SetBaudRate(speed_t baudRate); + /// \brief Sets the read timeout (in milliseconds)/blocking mode. /// \details Only call when state != OPEN. This method manupulates VMIN and VTIME. /// \param timeout_ms Set to -1 to infinite timeout, 0 to return immediately with any data (non @@ -92,13 +128,19 @@ namespace mn { private: /// \brief Returns a populated termios structure for the passed in file descriptor. - termios GetTermios(); + // termios GetTermios(); /// \brief Configures the tty device as a serial port. /// \warning Device must be open (valid file descriptor) when this is called. void ConfigureTermios(); - void SetTermios(termios myTermios); + // void SetTermios(termios myTermios); + + /// \brief Returns a populated termios2 structure for the serial port pointed to by the file descriptor. + termios2 GetTermios2(); + + /// \brief Assigns the provided tty settings to the serial port pointed to by the file descriptor. + void SetTermios2(termios2 tty); /// \brief Keeps track of the serial port's state. State state_; @@ -106,8 +148,14 @@ namespace mn { /// \brief The file path to the serial port device (e.g. "/dev/ttyUSB0"). std::string device_; - /// \brief The current baud rate. - BaudRate baudRate_; + /// \brief The type of baud rate that the user has specified. + BaudRateType baudRateType_; + + /// \brief The current baud rate if baudRateType_ == STANDARD. + BaudRate baudRateStandard_; + + /// \brief The current baud rate if baudRateType_ == CUSTOM. + speed_t baudRateCustom_; /// \brief The file descriptor for the open file. This gets written to when Open() is called. int fileDesc_; diff --git a/src/SerialPort.cpp b/src/SerialPort.cpp index 9bf0aca..d3f09fa 100644 --- a/src/SerialPort.cpp +++ b/src/SerialPort.cpp @@ -15,20 +15,28 @@ #include // UNIX standard function definitions #include // File control definitions #include // Error number definitions -#include // POSIX terminal control definitions (struct termios) +// #include // POSIX terminal control definitions (struct termios) #include // For throwing std::system_error +#include // Used for TCGETS2, which is required for custom baud rates +#include +// #include // Terminal control definitions (struct termios) +#include +#include // User includes #include "CppLinuxSerial/Exception.hpp" #include "CppLinuxSerial/SerialPort.hpp" +#define BOTHER 0010000 + namespace mn { namespace CppLinuxSerial { SerialPort::SerialPort() { echo_ = false; timeout_ms_ = defaultTimeout_ms_; - baudRate_ = defaultBaudRate_; + baudRateType_ = BaudRateType::STANDARD; + baudRateStandard_ = defaultBaudRate_; readBufferSize_B_ = defaultReadBufferSize_B_; readBuffer_.reserve(readBufferSize_B_); state_ = State::CLOSED; @@ -37,7 +45,15 @@ namespace CppLinuxSerial { SerialPort::SerialPort(const std::string& device, BaudRate baudRate) : SerialPort() { device_ = device; - baudRate_ = baudRate; + baudRateType_ = BaudRateType::STANDARD; + baudRateStandard_ = baudRate; + } + + SerialPort::SerialPort(const std::string& device, speed_t baudRate) : + SerialPort() { + device_ = device; + baudRateType_ = BaudRateType::CUSTOM; + baudRateCustom_ = baudRate; } SerialPort::~SerialPort() { @@ -52,13 +68,21 @@ namespace CppLinuxSerial { void SerialPort::SetDevice(const std::string& device) { device_ = device; if(state_ == State::OPEN) - - - ConfigureTermios(); + ConfigureTermios(); } void SerialPort::SetBaudRate(BaudRate baudRate) { - baudRate_ = baudRate; + std::cout << "standard called\n"; + baudRateType_ = BaudRateType::STANDARD; + baudRateStandard_ = baudRate; + if(state_ == State::OPEN) + ConfigureTermios(); + } + + void SerialPort::SetBaudRate(speed_t baudRate) { + std::cout << " custom called\n"; + baudRateType_ = BaudRateType::CUSTOM; + baudRateCustom_ = baudRate; if(state_ == State::OPEN) ConfigureTermios(); } @@ -66,7 +90,7 @@ namespace CppLinuxSerial { void SerialPort::Open() { - std::cout << "Attempting to open COM port \"" << device_ << "\"." << std::endl; + // std::cout << "Attempting to open COM port \"" << device_ << "\"." << std::endl; if(device_.empty()) { THROW_EXCEPT("Attempted to open file when file path has not been assigned to."); @@ -86,7 +110,7 @@ namespace CppLinuxSerial { ConfigureTermios(); - std::cout << "COM port opened successfully." << std::endl; + // std::cout << "COM port opened successfully." << std::endl; state_ = State::OPEN; } @@ -97,11 +121,12 @@ namespace CppLinuxSerial { void SerialPort::ConfigureTermios() { - std::cout << "Configuring COM port \"" << device_ << "\"." << std::endl; + // std::cout << "Configuring COM port \"" << device_ << "\"." << std::endl; //================== CONFIGURE ==================// - termios tty = GetTermios(); + // termios tty = GetTermios(); + termios2 tty = GetTermios2(); //================= (.c_cflag) ===============// @@ -115,29 +140,171 @@ namespace CppLinuxSerial { //===================== BAUD RATE =================// - switch(baudRate_) { - case BaudRate::B_9600: - cfsetispeed(&tty, B9600); - cfsetospeed(&tty, B9600); - break; - case BaudRate::B_38400: - cfsetispeed(&tty, B38400); - cfsetospeed(&tty, B38400); - break; - case BaudRate::B_57600: - cfsetispeed(&tty, B57600); - cfsetospeed(&tty, B57600); - break; - case BaudRate::B_115200: - cfsetispeed(&tty, B115200); - cfsetospeed(&tty, B115200); - break; - case BaudRate::CUSTOM: - // See https://gist.github.com/kennethryerson/f7d1abcf2633b7c03cf0 - throw std::runtime_error("Custom baud rate not yet supported."); - default: - throw std::runtime_error(std::string() + "baudRate passed to " + __PRETTY_FUNCTION__ + " unrecognized."); - } + // We used to use cfsetispeed() and cfsetospeed() with the B... macros, but this didn't allow + // us to set custom baud rates. So now to support both standard and custom baud rates lets + // just make everything "custom". This giant switch statement could be replaced with a map/lookup + // in the future + if (baudRateType_ == BaudRateType::STANDARD) { + tty.c_cflag &= ~CBAUD; + tty.c_cflag |= CBAUDEX; + switch(baudRateStandard_) { + case BaudRate::B_0: + // cfsetispeed(&tty, B0); + // cfsetospeed(&tty, B0); + tty.c_ispeed = 0; + tty.c_ospeed = 0; + break; + case BaudRate::B_50: + // cfsetispeed(&tty, B50); + // cfsetospeed(&tty, B50); + tty.c_ispeed = 50; + tty.c_ospeed = 50; + break; + case BaudRate::B_75: + // cfsetispeed(&tty, B75); + // cfsetospeed(&tty, B75); + tty.c_ispeed = 75; + tty.c_ospeed = 75; + break; + case BaudRate::B_110: + // cfsetispeed(&tty, B110); + // cfsetospeed(&tty, B110); + tty.c_ispeed = 110; + tty.c_ospeed = 110; + break; + case BaudRate::B_134: + // cfsetispeed(&tty, B134); + // cfsetospeed(&tty, B134); + tty.c_ispeed = 134; + tty.c_ospeed = 134; + break; + case BaudRate::B_150: + // cfsetispeed(&tty, B150); + // cfsetospeed(&tty, B150); + tty.c_ispeed = 150; + tty.c_ospeed = 150; + break; + case BaudRate::B_200: + // cfsetispeed(&tty, B200); + // cfsetospeed(&tty, B200); + tty.c_ispeed = 200; + tty.c_ospeed = 200; + break; + case BaudRate::B_300: + // cfsetispeed(&tty, B300); + // cfsetospeed(&tty, B300); + tty.c_ispeed = 300; + tty.c_ospeed = 300; + break; + case BaudRate::B_600: + // cfsetispeed(&tty, B600); + // cfsetospeed(&tty, B600); + tty.c_ispeed = 600; + tty.c_ospeed = 600; + break; + case BaudRate::B_1200: + // cfsetispeed(&tty, B1200); + // cfsetospeed(&tty, B1200); + tty.c_ispeed = 1200; + tty.c_ospeed = 1200; + break; + case BaudRate::B_1800: + // cfsetispeed(&tty, B1800); + // cfsetospeed(&tty, B1800); + tty.c_ispeed = 1800; + tty.c_ospeed = 1800; + break; + case BaudRate::B_2400: + // cfsetispeed(&tty, B2400); + // cfsetospeed(&tty, B2400); + tty.c_ispeed = 2400; + tty.c_ospeed = 2400; + break; + case BaudRate::B_4800: + // cfsetispeed(&tty, B4800); + // cfsetospeed(&tty, B4800); + tty.c_ispeed = 4800; + tty.c_ospeed = 4800; + break; + case BaudRate::B_9600: + // cfsetispeed(&tty, B9600); + // cfsetospeed(&tty, B9600); + tty.c_ispeed = 9600; + tty.c_ospeed = 9600; + break; + case BaudRate::B_19200: + // cfsetispeed(&tty, B19200); + // cfsetospeed(&tty, B19200); + tty.c_ispeed = 19200; + tty.c_ospeed = 19200; + break; + case BaudRate::B_38400: + // cfsetispeed(&tty, B38400); + // cfsetospeed(&tty, B38400); + tty.c_ispeed = 38400; + tty.c_ospeed = 38400; + break; + case BaudRate::B_57600: + // cfsetispeed(&tty, B57600); + // cfsetospeed(&tty, B57600); + tty.c_ispeed = 57600; + tty.c_ospeed = 57600; + break; + case BaudRate::B_115200: + // cfsetispeed(&tty, B115200); + // cfsetospeed(&tty, B115200); + tty.c_ispeed = 115200; + tty.c_ospeed = 115200; + break; + case BaudRate::B_230400: + // cfsetispeed(&tty, B230400); + // cfsetospeed(&tty, B230400); + tty.c_ispeed = 230400; + tty.c_ospeed = 230400; + break; + case BaudRate::B_460800: + // cfsetispeed(&tty, B460800); + // cfsetospeed(&tty, B460800); + tty.c_ispeed = 460800; + tty.c_ospeed = 460800; + break; + default: + throw std::runtime_error(std::string() + "baudRate passed to " + __PRETTY_FUNCTION__ + " unrecognized."); + } + } + // This does no different than STANDARD atm, but let's keep + // them separate for now.... + else if (baudRateType_ == BaudRateType::CUSTOM) + { + tty.c_cflag &= ~CBAUD; + tty.c_cflag |= CBAUDEX; + // tty.c_cflag |= BOTHER; + tty.c_ispeed = baudRateCustom_; + tty.c_ospeed = baudRateCustom_; + + + // #include + // // configure port to use custom speed instead of 38400 + // struct serial_struct ss; + // ioctl(fileDesc_, TIOCGSERIAL, &ss); + // ss.flags = (ss.flags & ~ASYNC_SPD_MASK) | ASYNC_SPD_CUST; + // ss.custom_divisor = (ss.baud_base + (baudRateCustom_ / 2)) / baudRateCustom_; + // int closestSpeed = ss.baud_base / ss.custom_divisor; + + // if (closestSpeed < baudRateCustom_ * 98 / 100 || closestSpeed > baudRateCustom_ * 102 / 100) { + // printf("Cannot set serial port speed to %d. Closest possible is %d\n", baudRateCustom_, closestSpeed); + // } + + // ioctl(fileDesc_, TIOCSSERIAL, &ss); + + // cfsetispeed(&tty, B38400); + // cfsetospeed(&tty, B38400); + } + else + { + // Should never get here, bug in this libraries code! + assert(false); + } //===================== (.c_oflag) =================// @@ -170,14 +337,11 @@ namespace CppLinuxSerial { tty.c_cc[VMIN] = 0; } - //======================== (.c_iflag) ====================// tty.c_iflag &= ~(IXON | IXOFF | IXANY); // Turn off s/w flow ctrl tty.c_iflag &= ~(IGNBRK|BRKINT|PARMRK|ISTRIP|INLCR|IGNCR|ICRNL); - - //=========================== LOCAL MODES (c_lflag) =======================// // Canonical input is when read waits for EOL or EOF characters before returning. In non-canonical mode, the rate at which @@ -189,11 +353,11 @@ namespace CppLinuxSerial { tty.c_lflag &= ~ISIG; // Disables recognition of INTR (interrupt), QUIT and SUSP (suspend) characters - // Try and use raw function call //cfmakeraw(&tty); - this->SetTermios(tty); + // this->SetTermios(tty); + this->SetTermios2(tty); /* // Flush port, then apply attributes @@ -262,39 +426,60 @@ namespace CppLinuxSerial { // If code reaches here, read must of been successful } - termios SerialPort::GetTermios() { - if(fileDesc_ == -1) - throw std::runtime_error("GetTermios() called but file descriptor was not valid."); + // termios SerialPort::GetTermios() { + // if(fileDesc_ == -1) + // throw std::runtime_error("GetTermios() called but file descriptor was not valid."); - struct termios tty; - memset(&tty, 0, sizeof(tty)); + // struct termios tty; + // memset(&tty, 0, sizeof(tty)); - // Get current settings (will be stored in termios structure) - if(tcgetattr(fileDesc_, &tty) != 0) - { - // Error occurred - std::cout << "Could not get terminal attributes for \"" << device_ << "\" - " << strerror(errno) << std::endl; - throw std::system_error(EFAULT, std::system_category()); - //return false; - } + // // Get current settings (will be stored in termios structure) + // if(tcgetattr(fileDesc_, &tty) != 0) + // { + // // Error occurred + // std::cout << "Could not get terminal attributes for \"" << device_ << "\" - " << strerror(errno) << std::endl; + // throw std::system_error(EFAULT, std::system_category()); + // //return false; + // } - return tty; - } + // return tty; + // } - void SerialPort::SetTermios(termios myTermios) - { - // Flush port, then apply attributes - tcflush(fileDesc_, TCIFLUSH); + // void SerialPort::SetTermios(termios myTermios) + // { + // // Flush port, then apply attributes + // tcflush(fileDesc_, TCIFLUSH); - if(tcsetattr(fileDesc_, TCSANOW, &myTermios) != 0) - { - // Error occurred - std::cout << "Could not apply terminal attributes for \"" << device_ << "\" - " << strerror(errno) << std::endl; - throw std::system_error(EFAULT, std::system_category()); + // if(tcsetattr(fileDesc_, TCSANOW, &myTermios) != 0) + // { + // // Error occurred + // std::cout << "Could not apply terminal attributes for \"" << device_ << "\" - " << strerror(errno) << std::endl; + // throw std::system_error(EFAULT, std::system_category()); - } + // } + + // // Successful! + // } - // Successful! + termios2 SerialPort::GetTermios2() + { + struct termios2 term2; + + ioctl(fileDesc_, TCGETS2, &term2); + + return term2; + + // term2.c_cflag &= ~CBAUD; /* Remove current BAUD rate */ + // term2.c_cflag |= BOTHER; /* Allow custom BAUD rate using int input */ + // term2.c_ispeed = speed; /* Set the input BAUD rate */ + // term2.c_ospeed = speed; /* Set the output BAUD rate */ + + // ioctl(fd, TCSETS2, &term2); + } + + void SerialPort::SetTermios2(termios2 tty) + { + ioctl(fileDesc_, TCSETS2, &tty); } void SerialPort::Close() { diff --git a/test/arduino/Basic/Basic.ino b/test/arduino/Basic/Basic.ino new file mode 100644 index 0000000..10fa637 --- /dev/null +++ b/test/arduino/Basic/Basic.ino @@ -0,0 +1,24 @@ +/* + AnalogReadSerial + + Reads an analog input on pin 0, prints the result to the Serial Monitor. + Graphical representation is available using Serial Plotter (Tools > Serial Plotter menu). + Attach the center pin of a potentiometer to pin A0, and the outside pins to +5V and ground. + + This example code is in the public domain. + + http://www.arduino.cc/en/Tutorial/AnalogReadSerial +*/ + +// the setup routine runs once when you press reset: +void setup() { + // initialize serial communication at 9600 bits per second: + Serial.begin(9600); +// Serial.begin(81234); // Used to test custom baud rates +} + +// the loop routine runs over and over again forever: +void loop() { + Serial.println("Hello"); + delay(100); // delay in between reads for stability +} diff --git a/test/arduino/main.cpp b/test/arduino/main.cpp new file mode 100644 index 0000000..0cf1248 --- /dev/null +++ b/test/arduino/main.cpp @@ -0,0 +1,24 @@ +#include + +using namespace mn::CppLinuxSerial; + +int main() { + // Create serial port object and open serial port + SerialPort serialPort("/dev/ttyACM0", BaudRate::B_9600); + // SerialPort serialPort("/dev/ttyACM0", 13000); + serialPort.SetTimeout(-1); // Block when reading until any data is received + serialPort.Open(); + + // Write some ASCII datae + // serialPort0.Write("Hello"); + + // Read some data back + while(1) { + std::string readData; + serialPort.Read(readData); + std::cout << "Received data: " << readData; + } + + // Close the serial port + serialPort.Close(); +} \ No newline at end of file