-
-
Save yoggy/3323808 to your computer and use it in GitHub Desktop.
#include "stdafx.h" | |
#include "SerialPort.h" | |
int main(int argc, char* argv[]) | |
{ | |
bool rv; | |
SerialPort::print_devices(); | |
std::string name = SerialPort::get_port_name(0); | |
SerialPort serial; | |
rv = serial.start(name.c_str(), 115200); | |
if (rv == false) { | |
return -1; | |
} | |
// initialize | |
serial.end_of_line_char(0x0d); | |
serial.write_some("BI010\r\n"); | |
serial.write_some("PE011\r\n"); | |
serial.write_some("RA01000\r\n"); | |
// wait | |
Sleep(5 * 1000); | |
serial.stop(); | |
return 0; | |
} |
#include "StdAfx.h" | |
#include <Setupapi.h> | |
#pragma comment(lib, "Setupapi.lib") | |
#include "SerialPort.h" | |
SerialPort::SerialPort(void) : end_of_line_char_('\n') | |
{ | |
} | |
SerialPort::~SerialPort(void) | |
{ | |
stop(); | |
} | |
char SerialPort::end_of_line_char() const | |
{ | |
return this->end_of_line_char_; | |
} | |
void SerialPort::end_of_line_char(const char &c) | |
{ | |
this->end_of_line_char_ = c; | |
} | |
std::vector<std::string> SerialPort::get_port_names() | |
{ | |
std::vector<std::string> names; | |
BOOL rv; | |
DWORD size; | |
GUID guid[1]; | |
HDEVINFO hdevinfo; | |
DWORD idx = 0; | |
SP_DEVINFO_DATA devinfo_data; | |
devinfo_data.cbSize = sizeof(SP_DEVINFO_DATA); | |
int count = 0; | |
rv = SetupDiClassGuidsFromName("Ports", (LPGUID)&guid, 1, &size) ; | |
if (!rv) { | |
std::cout << "error : SetupDiClassGuidsFromName() failed..." << std::endl; | |
return names; | |
} | |
hdevinfo = SetupDiGetClassDevs(&guid[0], NULL, NULL, DIGCF_PRESENT | DIGCF_PROFILE); | |
if (hdevinfo == INVALID_HANDLE_VALUE) { | |
std::cout << "error : SetupDiGetClassDevs() failed..." << std::endl; | |
return names; | |
} | |
while(SetupDiEnumDeviceInfo(hdevinfo, idx++, &devinfo_data)) { | |
char friendly_name[MAX_PATH]; | |
char port_name[MAX_PATH]; | |
DWORD prop_type; | |
DWORD type = REG_SZ; | |
HKEY hKey = NULL; | |
rv = ::SetupDiGetDeviceRegistryProperty(hdevinfo, &devinfo_data, SPDRP_FRIENDLYNAME, &prop_type, | |
(LPBYTE)friendly_name, sizeof(friendly_name), &size); | |
if (!rv) { | |
std::cout << "error : SetupDiGetDeviceRegistryProperty() failed..." << std::endl; | |
continue; | |
} | |
hKey = ::SetupDiOpenDevRegKey(hdevinfo, &devinfo_data, DICS_FLAG_GLOBAL, 0, DIREG_DEV, KEY_READ); | |
if (!hKey) continue; | |
size = sizeof(port_name); | |
rv = ::RegQueryValueEx(hKey, "PortName", 0, &type, (LPBYTE)&port_name, &size); | |
::RegCloseKey(hKey); | |
names.push_back(port_name); | |
} | |
SetupDiDestroyDeviceInfoList(hdevinfo); | |
return names; | |
} | |
int SerialPort::get_port_number() | |
{ | |
std::vector<std::string> names = get_port_names(); | |
return names.size(); | |
} | |
std::string SerialPort::get_port_name(const unsigned int &idx) | |
{ | |
std::vector<std::string> names = get_port_names(); | |
if (idx >= names.size()) return std::string(); | |
return names[idx]; | |
} | |
void SerialPort::print_devices() | |
{ | |
std::cout << "SerialPort::print_devices()" << std::endl; | |
int n = SerialPort::get_port_number(); | |
for (int i = 0; i < n; ++i) { | |
std::string name = SerialPort::get_port_name(i); | |
std::cout << "\t" << name.c_str() << std::endl; | |
} | |
} | |
bool SerialPort::start(const char *com_port_name, int baud_rate) | |
{ | |
boost::system::error_code ec; | |
if (port_) { | |
std::cout << "error : port is already opened..." << std::endl; | |
return false; | |
} | |
port_ = serial_port_ptr(new boost::asio::serial_port(io_service_)); | |
port_->open(com_port_name, ec); | |
if (ec) { | |
std::cout << "error : port_->open() failed...com_port_name=" | |
<< com_port_name << ", e=" << ec.message().c_str() << std::endl; | |
return false; | |
} | |
// option settings... | |
port_->set_option(boost::asio::serial_port_base::baud_rate(baud_rate)); | |
port_->set_option(boost::asio::serial_port_base::character_size(8)); | |
port_->set_option(boost::asio::serial_port_base::stop_bits(boost::asio::serial_port_base::stop_bits::one)); | |
port_->set_option(boost::asio::serial_port_base::parity(boost::asio::serial_port_base::parity::none)); | |
port_->set_option(boost::asio::serial_port_base::flow_control(boost::asio::serial_port_base::flow_control::none)); | |
boost::thread t(boost::bind(&boost::asio::io_service::run, &io_service_)); | |
async_read_some_(); | |
return true; | |
} | |
void SerialPort::stop() | |
{ | |
boost::mutex::scoped_lock look(mutex_); | |
if (port_) { | |
port_->cancel(); | |
port_->close(); | |
port_.reset(); | |
} | |
io_service_.stop(); | |
io_service_.reset(); | |
} | |
int SerialPort::write_some(const std::string &buf) | |
{ | |
return write_some(buf.c_str(), buf.size()); | |
} | |
int SerialPort::write_some(const char *buf, const int &size) | |
{ | |
boost::system::error_code ec; | |
if (!port_) return -1; | |
if (size == 0) return 0; | |
return port_->write_some(boost::asio::buffer(buf, size), ec); | |
} | |
void SerialPort::async_read_some_() | |
{ | |
if (port_.get() == NULL || !port_->is_open()) return; | |
port_->async_read_some( | |
boost::asio::buffer(read_buf_raw_, SERIAL_PORT_READ_BUF_SIZE), | |
boost::bind( | |
&SerialPort::on_receive_, | |
this, boost::asio::placeholders::error, | |
boost::asio::placeholders::bytes_transferred)); | |
} | |
void SerialPort::on_receive_(const boost::system::error_code& ec, size_t bytes_transferred) | |
{ | |
boost::mutex::scoped_lock look(mutex_); | |
if (port_.get() == NULL || !port_->is_open()) return; | |
if (ec) { | |
async_read_some_(); | |
return; | |
} | |
for (unsigned int i = 0; i < bytes_transferred; ++i) { | |
char c = read_buf_raw_[i]; | |
if (c == end_of_line_char_) { | |
this->on_receive_(read_buf_str_); | |
read_buf_str_.clear(); | |
} | |
else { | |
read_buf_str_ += c; | |
} | |
} | |
async_read_some_(); | |
} | |
void SerialPort::on_receive_(const std::string &data) | |
{ | |
std::cout << "SerialPort::on_receive_() : " << data << std::endl; | |
} | |
#pragma once | |
#include <boost/asio.hpp> | |
#include <boost/asio/serial_port.hpp> | |
#include <boost/system/error_code.hpp> | |
#include <boost/system/system_error.hpp> | |
#include <boost/bind.hpp> | |
#include <boost/thread.hpp> | |
#include <string> | |
#include <vector> | |
typedef boost::shared_ptr<boost::asio::serial_port> serial_port_ptr; | |
#define SERIAL_PORT_READ_BUF_SIZE 256 | |
class SerialPort | |
{ | |
protected: | |
boost::asio::io_service io_service_; | |
serial_port_ptr port_; | |
boost::mutex mutex_; | |
char read_buf_raw_[SERIAL_PORT_READ_BUF_SIZE]; | |
std::string read_buf_str_; | |
char end_of_line_char_; | |
private: | |
SerialPort(const SerialPort &p); | |
SerialPort &operator=(const SerialPort &p); | |
public: | |
SerialPort(void); | |
virtual ~SerialPort(void); | |
char end_of_line_char() const; | |
void end_of_line_char(const char &c); | |
virtual bool start(const char *com_port_name, int baud_rate=9600); | |
virtual void stop(); | |
int write_some(const std::string &buf); | |
int write_some(const char *buf, const int &size); | |
static int get_port_number(); | |
static std::string get_port_name(const unsigned int &idx); | |
static std::vector<std::string> get_port_names(); | |
static void print_devices(); | |
protected: | |
virtual void async_read_some_(); | |
virtual void on_receive_(const boost::system::error_code& ec, size_t bytes_transferred); | |
virtual void on_receive_(const std::string &data); | |
}; |
Hi, thanks for this example. I ran into a serious bug in start() which stopped it ever receiving data because io_service_.run() was returning before it was given any work to do. The fix is to call async_read_some_(); before creating the thread and calling run():
`bool SerialPort::start(const char *com_port_name, int baud_rate)
{
boost::system::error_code ec;
if (port_) {
std::cout << "error : port is already opened..." << std::endl;
return false;
}
port_ = serial_port_ptr(new boost::asio::serial_port(io_service_));
port_->open(com_port_name, ec);
if (ec) {
std::cout << "error : port_->open() failed...com_port_name="
<< com_port_name << ", e=" << ec.message().c_str() << std::endl;
return false;
}
// option settings...
port_->set_option(boost::asio::serial_port_base::baud_rate(baud_rate));
port_->set_option(boost::asio::serial_port_base::character_size(8));
port_->set_option(boost::asio::serial_port_base::stop_bits(boost::asio::serial_port_base::stop_bits::one));
port_->set_option(boost::asio::serial_port_base::parity(boost::asio::serial_port_base::parity::none));
port_->set_option(boost::asio::serial_port_base::flow_control(boost::asio::serial_port_base::flow_control::none));
async_read_some_();
std::thread t( [this] () {this->io_service_.run(); });
thread_.swap(t);
return true;
}
void SerialPort::stop()
{
std::unique_lock lock(mutex_);
if (port_) {
port_->cancel();
port_->close();
port_.reset();
}
if (thread_.joinable())
thread_.join();
io_service_.stop();
io_service_.reset();
}
`
Quick question on your reply. In this bit:
async_read_some_();
std::thread t( [this] () {this->io_service_.run(); });
thread_.swap(t);
then
if (thread_.joinable())
thread_.join();
Am I right in thinking that thread_ is a member variable of the class?
perfect!
Thank you for this excelent example.
Just one minor suggestion: io_service_.restart() needs to be called in function SerialPort::start if there has been a previous call to SerialPort::stop, where io_service_.reset() is invoked. like this:
bool SerialPort::start(const char *com_port_name, int baud_rate)
{
boost::system::error_code ec;
if (port_) {
std::cout << "error : port is already opened..." << std::endl;
return false;
}
io_service_.restart();
port_ = serial_port_ptr(new boost::asio::serial_port(io_service_));
port_->open(com_port_name, ec);
...
otherwise, it stops working on subsequent calls to stop() / start().
Please use comments to show us, what your code is doing. thx.