Boost.ASIOのserial_portでPOSIX標準にないボーレートを設定する
Unix/Linux環境を前提。
#include <boost/asio.hpp>
namespace asio = boost::asio;
int main() {
asio::io_context ioc{};
asio::serial_port serial{ioc, "/dev/cu.usbserial-xxxxx"};
boost::system::error_code ec{};
// これが失敗する
serial.set_option(asio::serial_port_base::baud_rate(460800), ec);
if (ec) {
std::cout << ec.message() << std::endl; // Invalid argument
}
}
デバイス側は対応しているものとして、なぜかASIO内部で失敗する。エラーメッセージはInvalid argument
としか言わないので何が悪いのかわからない・・・
serial.set_option
をデバッガで追いかけると、非常に入り組んでいるが結果としてserial_port_base::baud_rate::store()
を呼び出している
定義をコピペすると
ASIO_SYNC_OP_VOID serial_port_base::baud_rate::store(
ASIO_OPTION_STORAGE& storage, asio::error_code& ec) const
{
#if defined(ASIO_WINDOWS) || defined(__CYGWIN__)
storage.BaudRate = value_;
#else
speed_t baud;
switch (value_)
{
// Do POSIX-specified rates first.
case 0: baud = B0; break;
case 50: baud = B50; break;
case 75: baud = B75; break;
case 110: baud = B110; break;
case 134: baud = B134; break;
case 150: baud = B150; break;
case 200: baud = B200; break;
case 300: baud = B300; break;
case 600: baud = B600; break;
case 1200: baud = B1200; break;
case 1800: baud = B1800; break;
case 2400: baud = B2400; break;
case 4800: baud = B4800; break;
case 9600: baud = B9600; break;
case 19200: baud = B19200; break;
case 38400: baud = B38400; break;
// And now the extended ones conditionally.
# ifdef B7200
case 7200: baud = B7200; break;
# endif
# ifdef B14400
case 14400: baud = B14400; break;
# endif
# ifdef B57600
case 57600: baud = B57600; break;
# endif
# ifdef B115200
case 115200: baud = B115200; break;
# endif
# ifdef B230400
case 230400: baud = B230400; break;
# endif
# ifdef B460800
case 460800: baud = B460800; break;
# endif
# ifdef B500000
case 500000: baud = B500000; break;
# endif
# ifdef B576000
case 576000: baud = B576000; break;
# endif
# ifdef B921600
case 921600: baud = B921600; break;
# endif
# ifdef B1000000
case 1000000: baud = B1000000; break;
# endif
# ifdef B1152000
case 1152000: baud = B1152000; break;
# endif
# ifdef B2000000
case 2000000: baud = B2000000; break;
# endif
# ifdef B3000000
case 3000000: baud = B3000000; break;
# endif
# ifdef B3500000
case 3500000: baud = B3500000; break;
# endif
# ifdef B4000000
case 4000000: baud = B4000000; break;
# endif
default:
ec = asio::error::invalid_argument;
ASIO_SYNC_OP_VOID_RETURN(ec);
}
# if defined(_BSD_SOURCE) || defined(_DEFAULT_SOURCE)
::cfsetspeed(&storage, baud);
# else
::cfsetispeed(&storage, baud);
::cfsetospeed(&storage, baud);
# endif
#endif
ec = asio::error_code();
ASIO_SYNC_OP_VOID_RETURN(ec);
}
最初の#if defined(ASIO_WINDOWS) || defined(__CYGWIN__)
でWindowsの場合は何も気にせずボーレート設定をしている(のでWindowsは今回関係ない)。
ここでのvalue_
はserial_port_base::baud_rate
構造体のメンバ変数で、set_opiton
で私たボーレート値。ASIO_OPTION_STORAGE
はtermios
構造体(MacOSの場合)。
問題はその下、// Do POSIX-specified rates first.
とコメントにある通り、まずvalue_
がPOSIXに規定されているボーレートであるかをチェックし、それに一致していればPOSIXのマクロ(BNNN
)を使ってボーレートを設定している。
その後、POSIX規定外のボーレートについては、BNNN
の形式のマクロが定義されているときに限ってそれを使って設定する。
問題はここにあり、460800
のようなPOSIX規定外のボーレートを設定しようとしても、B460800
のようなマクロが定義されていないため、ASIOは内部でそのような指定を失敗とみなす(switch
のdefault
節)。
環境によっては、termios
関連のヘッダでそのようなマクロが定義されているので問題は起きないかもしれない。それがないと問題が起きる。
解決策は簡単で、定義してやれば良い。
// これを定義しておく
#define B460800 460800
#include <boost/asio.hpp>
namespace asio = boost::asio;
int main() {
asio::io_context ioc{};
asio::serial_port serial{ioc, "/dev/cu.usbserial-xxxxx"};
boost::system::error_code ec{};
// 失敗しなくなる
serial.set_option(asio::serial_port_base::baud_rate(460800), ec);
if (ec) {
std::cout << ec.message() << std::endl; // ここには来ない
}
}
こうすると、所望のボーレートが設定可能となる。ただし、先ほどのserial_port_base::baud_rate::store()
の実装を見てわかるように、そのswitch
文で予め調べられているボーレートしかこの方法でも有効にならず、完全に任意の値が設定できるわけではない(多分困らないと思うけど)。
もう一つ注意として、B460800
のようなマクロが定義されている環境だと多重定義とかになって別のコンパイルエラーが発生するかもしれない。