From e8a7fe9e0b31f7d90f2a174744c8601739fed549 Mon Sep 17 00:00:00 2001 From: Jason Abele Date: Thu, 16 Jun 2011 18:47:37 -0700 Subject: WBX: Add support for WBX v3 daughterboards --- host/lib/usrp/dboard/db_wbx_common.cpp | 97 +++++++++++++++++++++++++++++----- host/lib/usrp/dboard/db_wbx_common.hpp | 6 +++ host/lib/usrp/dboard/db_wbx_simple.cpp | 2 + 3 files changed, 91 insertions(+), 14 deletions(-) (limited to 'host/lib/usrp') diff --git a/host/lib/usrp/dboard/db_wbx_common.cpp b/host/lib/usrp/dboard/db_wbx_common.cpp index a1b1437a2..1a016e89c 100644 --- a/host/lib/usrp/dboard/db_wbx_common.cpp +++ b/host/lib/usrp/dboard/db_wbx_common.cpp @@ -35,6 +35,14 @@ #define RX_ATTN_SHIFT 8 // lsb of RX Attenuator Control #define RX_ATTN_MASK (63 << RX_ATTN_SHIFT) // valid bits of RX Attenuator Control +// TX Attenuator Pins (v3 only) +#define TX_ATTN_16 (1 << 14) +#define TX_ATTN_8 (1 << 5) +#define TX_ATTN_4 (1 << 4) +#define TX_ATTN_2 (1 << 3) +#define TX_ATTN_1 (1 << 1) +#define TX_ATTN_MASK (TX_ATTN_16|TX_ATTN_8|TX_ATTN_4|TX_ATTN_2|TX_ATTN_1) // valid bits of TX Attenuator Control + // Mixer functions #define TX_MIXER_ENB (TXMOD_EN|ADF4350_PDBRF) #define TX_MIXER_DIS 0 @@ -43,7 +51,7 @@ #define RX_MIXER_DIS 0 // Power functions -#define TX_POWER_UP (TX_PUP_5V|TX_PUP_3V|ADF4350_CE) // high enables power supply +#define TX_POWER_UP (TX_PUP_5V|TX_PUP_3V) // high enables power supply #define TX_POWER_DOWN 0 #define RX_POWER_UP (RX_PUP_5V|RX_PUP_3V|ADF4350_CE) // high enables power supply @@ -75,6 +83,10 @@ static const uhd::dict wbx_tx_gain_ranges = map_list_ ("PGA0", gain_range_t(0, 25, 0.05)) ; +static const uhd::dict wbx_v3_tx_gain_ranges = map_list_of + ("PGA0", gain_range_t(0, 31, 1.0)) +; + static const uhd::dict wbx_rx_gain_ranges = map_list_of ("PGA0", gain_range_t(0, 31.5, 0.5)) ; @@ -88,10 +100,13 @@ wbx_base::wbx_base(ctor_args_t args) : xcvr_dboard_base(args){ this->get_iface()->set_clock_enabled(dboard_iface::UNIT_TX, true); this->get_iface()->set_clock_enabled(dboard_iface::UNIT_RX, true); + //v3 has different io bits for attenuator control + int v3_iobits = is_v3() ? TX_ATTN_MASK : ADF4350_CE; + //set the gpio directions and atr controls this->get_iface()->set_pin_ctrl(dboard_iface::UNIT_TX, TXMOD_EN|ADF4350_PDBRF); this->get_iface()->set_pin_ctrl(dboard_iface::UNIT_RX, RXBB_PDB|ADF4350_PDBRF); - this->get_iface()->set_gpio_ddr(dboard_iface::UNIT_TX, TX_PUP_5V|TX_PUP_3V|ADF4350_CE|TXMOD_EN|ADF4350_PDBRF); + this->get_iface()->set_gpio_ddr(dboard_iface::UNIT_TX, TX_PUP_5V|TX_PUP_3V|TXMOD_EN|ADF4350_PDBRF|v3_iobits); this->get_iface()->set_gpio_ddr(dboard_iface::UNIT_RX, RX_PUP_5V|RX_PUP_3V|ADF4350_CE|RXBB_PDB|ADF4350_PDBRF|RX_ATTN_MASK); //setup ATR for the mixer enables (always enabled to prevent phase slip between bursts) @@ -106,9 +121,17 @@ wbx_base::wbx_base(ctor_args_t args) : xcvr_dboard_base(args){ this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_FULL_DUPLEX, RX_MIXER_ENB, RX_MIXER_DIS | RX_MIXER_ENB); //set some default values - BOOST_FOREACH(const std::string &name, wbx_tx_gain_ranges.keys()){ - set_tx_gain(wbx_tx_gain_ranges[name].start(), name); + if (is_v3()) { + BOOST_FOREACH(const std::string &name, wbx_v3_tx_gain_ranges.keys()){ + set_tx_gain(wbx_v3_tx_gain_ranges[name].start(), name); + } + } + else { + BOOST_FOREACH(const std::string &name, wbx_tx_gain_ranges.keys()){ + set_tx_gain(wbx_tx_gain_ranges[name].start(), name); + } } + BOOST_FOREACH(const std::string &name, wbx_rx_gain_ranges.keys()){ set_rx_gain(wbx_rx_gain_ranges[name].start(), name); } @@ -131,7 +154,7 @@ void wbx_base::set_rx_enabled(bool enb){ void wbx_base::set_tx_enabled(bool enb){ this->get_iface()->set_gpio_out(dboard_iface::UNIT_TX, - (enb)? TX_POWER_UP : TX_POWER_DOWN, TX_POWER_UP | TX_POWER_DOWN + (enb)? TX_POWER_UP | ADF4350_CE : TX_POWER_DOWN, TX_POWER_UP | TX_POWER_DOWN | (is_v3() ? 0 : ADF4350_CE) ); } @@ -150,7 +173,7 @@ static int rx_pga0_gain_to_iobits(double &gain){ int iobits = ((~attn_code) << RX_ATTN_SHIFT) & RX_ATTN_MASK; UHD_LOGV(often) << boost::format( - "WBX Attenuation: %f dB, Code: %d, IO Bits %x, Mask: %x" + "WBX RX Attenuation: %f dB, Code: %d, IO Bits %x, Mask: %x" ) % attn % attn_code % (iobits & RX_ATTN_MASK) % RX_ATTN_MASK << std::endl; //the actual gain setting @@ -159,6 +182,35 @@ static int rx_pga0_gain_to_iobits(double &gain){ return iobits; } +//v3 TX gains +static int tx_pga0_gain_to_iobits(double &gain){ + //clip the input + gain = wbx_v3_tx_gain_ranges["PGA0"].clip(gain); + + //convert to attenuation + double attn = wbx_v3_tx_gain_ranges["PGA0"].stop() - gain; + + //calculate the attenuation + int attn_code = boost::math::iround(attn*2); + int iobits = ( + (attn_code & 16 ? 0 : TX_ATTN_16) | + (attn_code & 8 ? 0 : TX_ATTN_8) | + (attn_code & 4 ? 0 : TX_ATTN_4) | + (attn_code & 2 ? 0 : TX_ATTN_2) | + (attn_code & 1 ? 0 : TX_ATTN_1) + ) & TX_ATTN_MASK; + + UHD_LOGV(often) << boost::format( + "WBX TX Attenuation: %f dB, Code: %d, IO Bits %x, Mask: %x" + ) % attn % attn_code % (iobits & TX_ATTN_MASK) % TX_ATTN_MASK << std::endl; + + //the actual gain setting + gain = wbx_v3_tx_gain_ranges["PGA0"].stop() - double(attn_code)/2; + + return iobits; +} + +//Pre v3 TX gains static double tx_pga0_gain_to_dac_volts(double &gain){ //clip the input gain = wbx_tx_gain_ranges["PGA0"].clip(gain); @@ -181,15 +233,28 @@ static double tx_pga0_gain_to_dac_volts(double &gain){ } void wbx_base::set_tx_gain(double gain, const std::string &name){ - assert_has(wbx_tx_gain_ranges.keys(), name, "wbx tx gain name"); - if(name == "PGA0"){ - double dac_volts = tx_pga0_gain_to_dac_volts(gain); - _tx_gains[name] = gain; - - //write the new voltage to the aux dac - this->get_iface()->write_aux_dac(dboard_iface::UNIT_TX, dboard_iface::AUX_DAC_A, dac_volts); + if (is_v3()) { + assert_has(wbx_v3_tx_gain_ranges.keys(), name, "wbx tx gain name"); + if(name == "PGA0"){ + double dac_volts = tx_pga0_gain_to_iobits(gain); + _tx_gains[name] = gain; + + //write the new voltage to the aux dac + this->get_iface()->write_aux_dac(dboard_iface::UNIT_TX, dboard_iface::AUX_DAC_A, dac_volts); + } + else UHD_THROW_INVALID_CODE_PATH(); + } + else { + assert_has(wbx_tx_gain_ranges.keys(), name, "wbx tx gain name"); + if(name == "PGA0"){ + double dac_volts = tx_pga0_gain_to_dac_volts(gain); + _tx_gains[name] = gain; + + //write the new voltage to the aux dac + this->get_iface()->write_aux_dac(dboard_iface::UNIT_TX, dboard_iface::AUX_DAC_A, dac_volts); + } + else UHD_THROW_INVALID_CODE_PATH(); } - else UHD_THROW_INVALID_CODE_PATH(); } void wbx_base::set_rx_gain(double gain, const std::string &name){ @@ -381,6 +446,10 @@ bool wbx_base::get_locked(dboard_iface::unit_t unit){ return (this->get_iface()->read_gpio(unit) & LOCKDET_MASK) != 0; } +bool wbx_base::is_v3(void){ + return get_rx_id() == 0x057; +} + /*********************************************************************** * RX Get and Set **********************************************************************/ diff --git a/host/lib/usrp/dboard/db_wbx_common.hpp b/host/lib/usrp/dboard/db_wbx_common.hpp index 07e84a565..5d33ddce9 100644 --- a/host/lib/usrp/dboard/db_wbx_common.hpp +++ b/host/lib/usrp/dboard/db_wbx_common.hpp @@ -62,6 +62,12 @@ protected: */ virtual bool get_locked(dboard_iface::unit_t unit); + /*! + * Detect if this a v3 WBX + * \return true for locked + */ + virtual bool is_v3(void); + private: uhd::dict _tx_gains, _rx_gains; bool _rx_enabled, _tx_enabled; diff --git a/host/lib/usrp/dboard/db_wbx_simple.cpp b/host/lib/usrp/dboard/db_wbx_simple.cpp index 82405135e..602ce389d 100644 --- a/host/lib/usrp/dboard/db_wbx_simple.cpp +++ b/host/lib/usrp/dboard/db_wbx_simple.cpp @@ -76,6 +76,8 @@ static dboard_base::sptr make_wbx_simple(dboard_base::ctor_args_t args){ UHD_STATIC_BLOCK(reg_wbx_simple_dboards){ dboard_manager::register_dboard(0x0053, 0x0052, &make_wbx_simple, "WBX"); dboard_manager::register_dboard(0x0053, 0x004f, &make_wbx_simple, "WBX + Simple GDB"); + dboard_manager::register_dboard(0x0057, 0x0056, &make_wbx_simple, "WBX v3"); + dboard_manager::register_dboard(0x0057, 0x004f, &make_wbx_simple, "WBX v3 + Simple GDB"); } /*********************************************************************** -- cgit v1.2.3 From 7af605b247ee9331c29c23229252a101b7d40352 Mon Sep 17 00:00:00 2001 From: Nick Foster Date: Fri, 17 Jun 2011 13:01:07 -0700 Subject: UHD: GPS work. * Rewrote NMEA acquisition to be more general * Added GPS sensors instead of member fns for data access * GPS sensors added to mboard sensors to keep ABI compat * VITA time initialized to GPS time on init --- host/include/uhd/usrp/gps_ctrl.hpp | 14 ++-- host/lib/usrp/gps_ctrl.cpp | 125 ++++++++++++++++++++++-------------- host/lib/usrp/usrp2/mboard_impl.cpp | 15 ++++- 3 files changed, 96 insertions(+), 58 deletions(-) (limited to 'host/lib/usrp') diff --git a/host/include/uhd/usrp/gps_ctrl.hpp b/host/include/uhd/usrp/gps_ctrl.hpp index bd679b165..6ff00e03c 100644 --- a/host/include/uhd/usrp/gps_ctrl.hpp +++ b/host/include/uhd/usrp/gps_ctrl.hpp @@ -22,6 +22,8 @@ #include #include #include +#include +#include using namespace boost::posix_time; @@ -38,16 +40,14 @@ public: static sptr make(gps_send_fn_t, gps_recv_fn_t); /*! - * Get the current GPS time and date - * \return current GPS time and date as boost::posix_time::ptime object + * Retrieve the list of sensors this GPS object provides */ - virtual ptime get_time(void) = 0; - + virtual std::vector get_sensors(void) = 0; + /*! - * Get the epoch time (as time_t, which is int) - * \return current GPS time and date as time_t + * Retrieve the named sensor */ - virtual time_t get_epoch_time(void) = 0; + virtual uhd::sensor_value_t get_sensor(std::string key) = 0; /*! * Tell you if there's a supported GPS connected or not diff --git a/host/lib/usrp/gps_ctrl.cpp b/host/lib/usrp/gps_ctrl.cpp index 3bee26340..55f46ffb3 100644 --- a/host/lib/usrp/gps_ctrl.cpp +++ b/host/lib/usrp/gps_ctrl.cpp @@ -17,11 +17,13 @@ #include #include +#include #include +#include +#include #include #include #include -#include #include using namespace uhd; @@ -42,12 +44,9 @@ public: std::string reply; bool i_heard_some_nmea = false, i_heard_something_weird = false; - gps_type = GPS_TYPE_NONE; - -// set_uart_baud_rate(GPS_UART, 115200); - //first we look for a Jackson Labs Firefly (since that's what we sell with the USRP2+...) - + + //first we look for a Jackson Labs Firefly (since that's what we provide...) _recv(); //get whatever junk is in the rx buffer right now, and throw it away _send("HAAAY GUYYYYS\n"); //to elicit a response from the Firefly @@ -55,7 +54,7 @@ public: int timeout = GPS_TIMEOUT_TRIES; while(timeout--) { reply = safe_gps_read(); - if(trim_right_copy(reply) == "Command Error") { + if(boost::trim_right_copy(reply) == "Command Error") { gps_type = GPS_TYPE_JACKSON_LABS; break; } @@ -66,7 +65,6 @@ public: if((i_heard_some_nmea) && (gps_type != GPS_TYPE_JACKSON_LABS)) gps_type = GPS_TYPE_GENERIC_NMEA; - //otherwise, we can try some other common baud rates looking to see if a GPS is connected (todo, later) if((gps_type == GPS_TYPE_NONE) && i_heard_something_weird) { UHD_MSG(error) << "GPS invalid reply \"" << reply << "\", assuming none available" << std::endl; } @@ -84,13 +82,14 @@ public: boost::this_thread::sleep(boost::posix_time::milliseconds(FIREFLY_STUPID_DELAY_MS)); _send("SYST:COMM:SER:PRO OFF\n"); boost::this_thread::sleep(boost::posix_time::milliseconds(FIREFLY_STUPID_DELAY_MS)); - _send("GPS:GPGGA 0\n"); + _send("GPS:GPGGA 1\n"); boost::this_thread::sleep(boost::posix_time::milliseconds(FIREFLY_STUPID_DELAY_MS)); _send("GPS:GGAST 0\n"); boost::this_thread::sleep(boost::posix_time::milliseconds(FIREFLY_STUPID_DELAY_MS)); _send("GPS:GPRMC 1\n"); boost::this_thread::sleep(boost::posix_time::milliseconds(FIREFLY_STUPID_DELAY_MS)); - + _send("GPS:GPGSA 1\n"); + boost::this_thread::sleep(boost::posix_time::milliseconds(FIREFLY_STUPID_DELAY_MS)); // break; case GPS_TYPE_GENERIC_NMEA: @@ -104,7 +103,7 @@ public: found_gprmc = true; break; } - boost::this_thread::sleep(boost::posix_time::milliseconds(200)); + boost::this_thread::sleep(boost::posix_time::milliseconds(GPS_TIMEOUT_DELAY_MS)); } if(!found_gprmc) { if(gps_type == GPS_TYPE_JACKSON_LABS) UHD_MSG(error) << "Firefly GPS not locked or warming up." << std::endl; @@ -118,8 +117,6 @@ public: break; } - - } ~gps_ctrl_impl(void){ @@ -132,46 +129,51 @@ public: ptime get_time(void) { std::string reply; - ptime now; boost::tokenizer > tok(reply); std::vector toked; - int timeout = GPS_TIMEOUT_TRIES; - bool found_gprmc = false; - switch(gps_type) { - case GPS_TYPE_JACKSON_LABS: //deprecated in favor of a single NMEA parser - case GPS_TYPE_GENERIC_NMEA: - while(timeout--) { + reply = get_nmea("GPRMC"); + //make sure we got something + if(reply.size() <= 1) { + return ptime(); + } + + tok.assign(reply); + toked.assign(tok.begin(), tok.end()); + + if(not toked.size() == 12) { + UHD_MSG(error) << "get_time(): invalid GPRMC time sentence received."; + return ptime(); + } + + return ptime( date( + greg_year(boost::lexical_cast(toked[9].substr(4, 2)) + 2000), //just trust me on this one + greg_month(boost::lexical_cast(toked[9].substr(2, 2))), + greg_day(boost::lexical_cast(toked[9].substr(0, 2))) + ), + hours( boost::lexical_cast(toked[1].substr(0, 2))) + + minutes(boost::lexical_cast(toked[1].substr(2, 2))) + + seconds(boost::lexical_cast(toked[1].substr(4, 2))) + ); + } + + //retrieve a raw NMEA sentence for user parsing + std::string get_nmea(std::string type) { + type.insert(0, "$"); + std::string reply; + if(not gps_detected()) { + UHD_MSG(error) << "get_nmea(): unsupported GPS or no GPS detected"; + return std::string(); + } + int timeout = GPS_TIMEOUT_TRIES; + while(timeout--) { reply = safe_gps_read(); - if(reply.substr(0, 6) == "$GPRMC") { - found_gprmc = true; - break; - } - boost::this_thread::sleep(boost::posix_time::milliseconds(200)); - } - UHD_ASSERT_THROW(found_gprmc); - - tok.assign(reply); - toked.assign(tok.begin(), tok.end()); - - UHD_ASSERT_THROW(toked.size() == 12); //if it's not we got something weird in there - - now = ptime( date( - greg_year(boost::lexical_cast(toked[9].substr(4, 2)) + 2000), //just trust me on this one - greg_month(boost::lexical_cast(toked[9].substr(2, 2))), - greg_day(boost::lexical_cast(toked[9].substr(0, 2))) - ), - hours( boost::lexical_cast(toked[1].substr(0, 2))) - + minutes(boost::lexical_cast(toked[1].substr(2, 2))) - + seconds(boost::lexical_cast(toked[1].substr(4, 2))) - ); - break; - case GPS_TYPE_NONE: - default: - throw uhd::runtime_error("get_time(): Unsupported GPS or no GPS detected\n"); - break; + if(reply.substr(0, 6) == type) + return reply; + boost::this_thread::sleep(boost::posix_time::milliseconds(GPS_TIMEOUT_DELAY_MS)); } - return now; + UHD_MSG(error) << "get_nmea(): no " << type << " message found"; + return std::string(); } time_t get_epoch_time(void) { @@ -182,6 +184,33 @@ public: return (gps_type != GPS_TYPE_NONE); } + //return a list of supported sensors + std::vector get_sensors(void) { + std::vector ret; + ret.push_back("gps_gpgga"); + ret.push_back("gps_gprmc"); + ret.push_back("gps_gpgsa"); + ret.push_back("gps_time"); + return ret; + } + + uhd::sensor_value_t get_sensor(std::string key) { + if(key == "gps_gpgga" + or key == "gps_gprmc" + or key == "gps_gpgsa") { + return sensor_value_t( + boost::to_upper_copy(key), + get_nmea(boost::to_upper_copy(key.substr(4,8))), + ""); + } + else if(key == "gps_time") { + return sensor_value_t("GPS epoch time", int(get_epoch_time()), "seconds"); + } + else { + UHD_THROW_PROP_GET_ERROR(); + } + } + private: gps_send_fn_t _send; gps_recv_fn_t _recv; diff --git a/host/lib/usrp/usrp2/mboard_impl.cpp b/host/lib/usrp/usrp2/mboard_impl.cpp index 6bf412a3e..3733915a2 100644 --- a/host/lib/usrp/usrp2/mboard_impl.cpp +++ b/host/lib/usrp/usrp2/mboard_impl.cpp @@ -174,6 +174,12 @@ usrp2_mboard_impl::usrp2_mboard_impl( _iface->poke32(U2_REG_RX_CTRL_CLEAR(i), 1); //resets sequence } //------------------------------------------------------------------ + + //initialize VITA time to GPS time + if(_gps_ctrl.get() and _gps_ctrl->gps_detected()) { + UHD_MSG(status) << "Setting device time to GPS time...\n"; + set_time_spec(time_spec_t(double(_gps_ctrl->get_sensor("gps_time").to_int()+1)), false); + } } usrp2_mboard_impl::~usrp2_mboard_impl(void){UHD_SAFE_CALL( @@ -375,7 +381,10 @@ void usrp2_mboard_impl::get(const wax::obj &key_, wax::obj &val){ case SUBDEV_PROP_SENSOR_NAMES:{ prop_names_t names = boost::assign::list_of("mimo_locked")("ref_locked"); - if (_gps_ctrl.get()) names.push_back("gps_time"); + if (_gps_ctrl.get()) { + std::vector gs = _gps_ctrl->get_sensors(); + names.insert(names.end(), gs.begin(), gs.end()); + } val = names; } return; @@ -389,8 +398,8 @@ void usrp2_mboard_impl::get(const wax::obj &key_, wax::obj &val){ val = sensor_value_t("Ref", this->get_ref_locked(), "locked", "unlocked"); return; } - else if(key.name == "gps_time" and _gps_ctrl.get()) { - val = sensor_value_t("GPS time", int(_gps_ctrl->get_epoch_time()), "seconds"); + else if(uhd::has(_gps_ctrl->get_sensors(), key.name) and _gps_ctrl.get()) { + val = _gps_ctrl->get_sensor(key.name); } else { UHD_THROW_PROP_GET_ERROR(); -- cgit v1.2.3 From 6f6364f73aad1262fdbe88ad97128f7844764c99 Mon Sep 17 00:00:00 2001 From: Nick Foster Date: Fri, 17 Jun 2011 15:09:16 -0700 Subject: UHD: implemented gps_locked sensor. usrp2 mboard doesn't init VITA time if time not valid. --- host/docs/sync.rst | 19 ++++--------------- host/docs/usrp2.rst | 5 ++--- host/lib/usrp/gps_ctrl.cpp | 22 ++++++++++++++++++++++ host/lib/usrp/usrp2/mboard_impl.cpp | 20 ++++++++++++++++---- 4 files changed, 44 insertions(+), 22 deletions(-) (limited to 'host/lib/usrp') diff --git a/host/docs/sync.rst b/host/docs/sync.rst index 9284d8e33..3cb13fbf3 100644 --- a/host/docs/sync.rst +++ b/host/docs/sync.rst @@ -102,22 +102,11 @@ and the user can also parse this string to determine GPS time: usrp->set_time_next_pps(uhd::time_spec_t(gps_time+1)); ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -Method 3 - query the gps_time sensor +Method 3 - internal GPSDO ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -This is a variant of method 2 for USRPs with internal GPSDOs. -The user can query the gps_time sensor to wait for the NMEA string. - -:: - - //wait for NMEA string from internal GPSDO - usrp->get_mboard_sensor("gps_time"); - usrp->set_time_next_pps(uhd::time_spec_t(0.0)); - - -- OR -- - - //wait for the NMEA string and set GPS time - const time_t gps_time = usrp->get_mboard_sensor("gps_time").to_int(); - usrp->set_time_next_pps(uhd::time_spec_t(gps_time+1)); +USRPs with internal GPSDOs properly configured will automatically +configure themselves to set the VITA time to current UTC time. See the +GPSDO application note for more details. ------------------------------------------------------------------------ Synchronizing channel phase diff --git a/host/docs/usrp2.rst b/host/docs/usrp2.rst index 88b217f1b..fa811d0f6 100644 --- a/host/docs/usrp2.rst +++ b/host/docs/usrp2.rst @@ -349,9 +349,8 @@ Test the PPS input with the following app: ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Internal GPSDO ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -USRP-N2XX models can have an optional internal GPSDO. -To use the GPSDO with UHD, you must burn an EEPROM setting -so that UHD knows that the internal GPSDO was installed. +Please see the GPSDO application note for information on configuring and +using the internal GPSDO. **Installation instructions:** diff --git a/host/lib/usrp/gps_ctrl.cpp b/host/lib/usrp/gps_ctrl.cpp index 55f46ffb3..2c2843877 100644 --- a/host/lib/usrp/gps_ctrl.cpp +++ b/host/lib/usrp/gps_ctrl.cpp @@ -184,6 +184,24 @@ public: return (gps_type != GPS_TYPE_NONE); } + bool locked(void) { + std::string reply = get_nmea("GPGGA"); + if(reply.size() <= 1) return false; + + boost::tokenizer > tok(reply); + std::vector toked; + + tok.assign(reply); + toked.assign(tok.begin(), tok.end()); + + if(toked.size() != 15) { + UHD_MSG(error) << "gps_locked: invalid GPGGA response"; + return false; + } + + return (toked[6] != "0"); //sorry, 2d fixes don't count =D + } + //return a list of supported sensors std::vector get_sensors(void) { std::vector ret; @@ -191,6 +209,7 @@ public: ret.push_back("gps_gprmc"); ret.push_back("gps_gpgsa"); ret.push_back("gps_time"); + ret.push_back("gps_locked"); return ret; } @@ -206,6 +225,9 @@ public: else if(key == "gps_time") { return sensor_value_t("GPS epoch time", int(get_epoch_time()), "seconds"); } + else if(key == "gps_locked") { + return sensor_value_t("GPS lock status", locked(), "locked", "unlocked"); + } else { UHD_THROW_PROP_GET_ERROR(); } diff --git a/host/lib/usrp/usrp2/mboard_impl.cpp b/host/lib/usrp/usrp2/mboard_impl.cpp index 3733915a2..12fff96d0 100644 --- a/host/lib/usrp/usrp2/mboard_impl.cpp +++ b/host/lib/usrp/usrp2/mboard_impl.cpp @@ -149,7 +149,13 @@ usrp2_mboard_impl::usrp2_mboard_impl( (_mimo_clocking_mode_is_master?"master":"slave") << std::endl; //init the clock config - _clock_config = clock_config_t::internal(); + if(_iface->mb_eeprom["gpsdo"] == "internal" or + _iface->mb_eeprom["gpsdo"] == "external") { + _clock_config = clock_config_t::external(); + } + else { + _clock_config = clock_config_t::internal(); + } update_clock_config(); //init the codec before the dboard @@ -176,9 +182,15 @@ usrp2_mboard_impl::usrp2_mboard_impl( //------------------------------------------------------------------ //initialize VITA time to GPS time - if(_gps_ctrl.get() and _gps_ctrl->gps_detected()) { - UHD_MSG(status) << "Setting device time to GPS time...\n"; - set_time_spec(time_spec_t(double(_gps_ctrl->get_sensor("gps_time").to_int()+1)), false); + if( _gps_ctrl.get() + and _gps_ctrl->gps_detected()) { + if(_gps_ctrl->get_sensor("gps_locked").to_bool()) { + UHD_MSG(status) << "Setting device time to GPS time...\n"; + set_time_spec(time_spec_t(double(_gps_ctrl->get_sensor("gps_time").to_int()+1)), false); + } + else { + UHD_MSG(status) << "GPS not locked to satellites. Not initializing VITA time."; + } } } -- cgit v1.2.3 From 9101804e6b6d85b7cbdd582dba41c114358db407 Mon Sep 17 00:00:00 2001 From: Nick Foster Date: Fri, 17 Jun 2011 15:13:09 -0700 Subject: USRP2/N210: set VITA time even if GPS not locked. harmless to do so. --- host/lib/usrp/gps_ctrl.cpp | 2 +- host/lib/usrp/usrp2/mboard_impl.cpp | 9 ++------- 2 files changed, 3 insertions(+), 8 deletions(-) (limited to 'host/lib/usrp') diff --git a/host/lib/usrp/gps_ctrl.cpp b/host/lib/usrp/gps_ctrl.cpp index 2c2843877..582334cae 100644 --- a/host/lib/usrp/gps_ctrl.cpp +++ b/host/lib/usrp/gps_ctrl.cpp @@ -199,7 +199,7 @@ public: return false; } - return (toked[6] != "0"); //sorry, 2d fixes don't count =D + return (toked[6] != "0"); } //return a list of supported sensors diff --git a/host/lib/usrp/usrp2/mboard_impl.cpp b/host/lib/usrp/usrp2/mboard_impl.cpp index 12fff96d0..61ceb95ca 100644 --- a/host/lib/usrp/usrp2/mboard_impl.cpp +++ b/host/lib/usrp/usrp2/mboard_impl.cpp @@ -184,13 +184,8 @@ usrp2_mboard_impl::usrp2_mboard_impl( //initialize VITA time to GPS time if( _gps_ctrl.get() and _gps_ctrl->gps_detected()) { - if(_gps_ctrl->get_sensor("gps_locked").to_bool()) { - UHD_MSG(status) << "Setting device time to GPS time...\n"; - set_time_spec(time_spec_t(double(_gps_ctrl->get_sensor("gps_time").to_int()+1)), false); - } - else { - UHD_MSG(status) << "GPS not locked to satellites. Not initializing VITA time."; - } + UHD_MSG(status) << "Setting device time to GPS time...\n"; + set_time_spec(time_spec_t(double(_gps_ctrl->get_sensor("gps_time").to_int()+1)), false); } } -- cgit v1.2.3 From ab63a540883bf4c21980ac30b746045e00fffc0a Mon Sep 17 00:00:00 2001 From: Nick Foster Date: Fri, 17 Jun 2011 15:38:20 -0700 Subject: Refactor GPS code to duplicate way less stuff, make members private --- host/docs/gpsdo.rst | 2 +- host/docs/usrp2.rst | 3 +- host/lib/usrp/gps_ctrl.cpp | 221 +++++++++++++++++++++------------------------ 3 files changed, 104 insertions(+), 122 deletions(-) (limited to 'host/lib/usrp') diff --git a/host/docs/gpsdo.rst b/host/docs/gpsdo.rst index dc03f6ae9..a0f2d67da 100644 --- a/host/docs/gpsdo.rst +++ b/host/docs/gpsdo.rst @@ -53,7 +53,7 @@ GPS data is obtained through the mboard_sensors interface. To retrieve the current GPS time, use the "gps_time" sensor: :: -usrp->get_mboard_sensor("gps_time"); + usrp->get_mboard_sensor("gps_time"); The returned value will be the current epoch time, in seconds since January 1, 1970. This value is readily converted into human-readable diff --git a/host/docs/usrp2.rst b/host/docs/usrp2.rst index fa811d0f6..1b07834fb 100644 --- a/host/docs/usrp2.rst +++ b/host/docs/usrp2.rst @@ -395,7 +395,8 @@ they can be queried through the API. * mimo_locked - clock reference locked over the MIMO cable * ref_locked - clock reference locked (internal/external) -* gps_time - GPS seconds (available when GPSDO installed) +* gps_time - GPS epoch seconds (available when GPSDO installed) +* gps_locked - GPS lock status ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Multiple RX channels diff --git a/host/lib/usrp/gps_ctrl.cpp b/host/lib/usrp/gps_ctrl.cpp index 582334cae..3cef1ef19 100644 --- a/host/lib/usrp/gps_ctrl.cpp +++ b/host/lib/usrp/gps_ctrl.cpp @@ -21,6 +21,7 @@ #include #include #include +#include #include #include #include @@ -30,12 +31,12 @@ using namespace uhd; using namespace boost::gregorian; using namespace boost::posix_time; using namespace boost::algorithm; +using namespace boost::this_thread; /*! * A GPS control for Jackson Labs devices (and other NMEA compatible GPS's) */ -//TODO: multiple baud rate support (requires mboard_impl changes for poking UART registers) class gps_ctrl_impl : public gps_ctrl{ public: gps_ctrl_impl(gps_send_fn_t send, gps_recv_fn_t recv){ @@ -53,14 +54,14 @@ public: //then we loop until we either timeout, or until we get a response that indicates we're a JL device int timeout = GPS_TIMEOUT_TRIES; while(timeout--) { - reply = safe_gps_read(); - if(boost::trim_right_copy(reply) == "Command Error") { + reply = _recv(); + if(reply.find("Command Error") != std::string::npos) { gps_type = GPS_TYPE_JACKSON_LABS; break; } else if(reply.substr(0, 3) == "$GP") i_heard_some_nmea = true; //but keep looking for that "Command Error" response else if(reply.length() != 0) i_heard_something_weird = true; //probably wrong baud rate - boost::this_thread::sleep(boost::posix_time::milliseconds(200)); + sleep(milliseconds(200)); } if((i_heard_some_nmea) && (gps_type != GPS_TYPE_JACKSON_LABS)) gps_type = GPS_TYPE_GENERIC_NMEA; @@ -69,46 +70,16 @@ public: UHD_MSG(error) << "GPS invalid reply \"" << reply << "\", assuming none available" << std::endl; } - bool found_gprmc = false; - switch(gps_type) { case GPS_TYPE_JACKSON_LABS: UHD_MSG(status) << "Found a Jackson Labs GPS" << std::endl; - //issue some setup stuff so it spits out the appropriate data - //none of these should issue replies so we don't bother looking for them - //we have to sleep between commands because the JL device, despite not acking, takes considerable time to process each command. - boost::this_thread::sleep(boost::posix_time::milliseconds(FIREFLY_STUPID_DELAY_MS)); - _send("SYST:COMM:SER:ECHO OFF\n"); - boost::this_thread::sleep(boost::posix_time::milliseconds(FIREFLY_STUPID_DELAY_MS)); - _send("SYST:COMM:SER:PRO OFF\n"); - boost::this_thread::sleep(boost::posix_time::milliseconds(FIREFLY_STUPID_DELAY_MS)); - _send("GPS:GPGGA 1\n"); - boost::this_thread::sleep(boost::posix_time::milliseconds(FIREFLY_STUPID_DELAY_MS)); - _send("GPS:GGAST 0\n"); - boost::this_thread::sleep(boost::posix_time::milliseconds(FIREFLY_STUPID_DELAY_MS)); - _send("GPS:GPRMC 1\n"); - boost::this_thread::sleep(boost::posix_time::milliseconds(FIREFLY_STUPID_DELAY_MS)); - _send("GPS:GPGSA 1\n"); - boost::this_thread::sleep(boost::posix_time::milliseconds(FIREFLY_STUPID_DELAY_MS)); -// break; + init_firefly(); case GPS_TYPE_GENERIC_NMEA: if(gps_type == GPS_TYPE_GENERIC_NMEA) UHD_MSG(status) << "Found a generic NMEA GPS device" << std::endl; - found_gprmc = false; - //here we loop around looking for a GPRMC packet. if we don't get one, we don't have a usable GPS. - timeout = GPS_TIMEOUT_TRIES; - while(timeout--) { - reply = safe_gps_read(); - if(reply.substr(0, 6) == "$GPRMC") { - found_gprmc = true; - break; - } - boost::this_thread::sleep(boost::posix_time::milliseconds(GPS_TIMEOUT_DELAY_MS)); - } - if(!found_gprmc) { - if(gps_type == GPS_TYPE_JACKSON_LABS) UHD_MSG(error) << "Firefly GPS not locked or warming up." << std::endl; - else UHD_MSG(error) << "GPS does not output GPRMC packets. Cannot retrieve time." << std::endl; - gps_type = GPS_TYPE_NONE; + if(get_time() == ptime()) { + UHD_MSG(status) << "No valid GPRMC packet found. Ignoring discovered GPS."; + gps_type = GPS_TYPE_NONE; } break; @@ -120,46 +91,63 @@ public: } ~gps_ctrl_impl(void){ - + /* NOP */ } - std::string safe_gps_read() { - return _recv(); + //return a list of supported sensors + std::vector get_sensors(void) { + std::vector ret = boost::assign::list_of + ("gps_gpgga") + ("gps_gprmc") + ("gps_gpgsa") + ("gps_time") + ("gps_locked"); + return ret; } - ptime get_time(void) { - std::string reply; - boost::tokenizer > tok(reply); - std::vector toked; - - reply = get_nmea("GPRMC"); - //make sure we got something - if(reply.size() <= 1) { - return ptime(); + uhd::sensor_value_t get_sensor(std::string key) { + if(key == "gps_gpgga" + or key == "gps_gprmc" + or key == "gps_gpgsa") { + return sensor_value_t( + boost::to_upper_copy(key), + get_nmea(boost::to_upper_copy(key.substr(4,8))), + ""); } - - tok.assign(reply); - toked.assign(tok.begin(), tok.end()); - - if(not toked.size() == 12) { - UHD_MSG(error) << "get_time(): invalid GPRMC time sentence received."; - return ptime(); + else if(key == "gps_time") { + return sensor_value_t("GPS epoch time", int(get_epoch_time()), "seconds"); + } + else if(key == "gps_locked") { + return sensor_value_t("GPS lock status", locked(), "locked", "unlocked"); + } + else { + UHD_THROW_PROP_GET_ERROR(); } - - return ptime( date( - greg_year(boost::lexical_cast(toked[9].substr(4, 2)) + 2000), //just trust me on this one - greg_month(boost::lexical_cast(toked[9].substr(2, 2))), - greg_day(boost::lexical_cast(toked[9].substr(0, 2))) - ), - hours( boost::lexical_cast(toked[1].substr(0, 2))) - + minutes(boost::lexical_cast(toked[1].substr(2, 2))) - + seconds(boost::lexical_cast(toked[1].substr(4, 2))) - ); } - //retrieve a raw NMEA sentence for user parsing - std::string get_nmea(std::string type) { - type.insert(0, "$"); +private: + void init_firefly(void) { + //issue some setup stuff so it spits out the appropriate data + //none of these should issue replies so we don't bother looking for them + //we have to sleep between commands because the JL device, despite not acking, takes considerable time to process each command. + sleep(milliseconds(FIREFLY_STUPID_DELAY_MS)); + _send("SYST:COMM:SER:ECHO OFF\n"); + sleep(milliseconds(FIREFLY_STUPID_DELAY_MS)); + _send("SYST:COMM:SER:PRO OFF\n"); + sleep(milliseconds(FIREFLY_STUPID_DELAY_MS)); + _send("GPS:GPGGA 1\n"); + sleep(milliseconds(FIREFLY_STUPID_DELAY_MS)); + _send("GPS:GGAST 0\n"); + sleep(milliseconds(FIREFLY_STUPID_DELAY_MS)); + _send("GPS:GPRMC 1\n"); + sleep(milliseconds(FIREFLY_STUPID_DELAY_MS)); + _send("GPS:GPGSA 1\n"); + sleep(milliseconds(FIREFLY_STUPID_DELAY_MS)); + } + + //retrieve a raw NMEA sentence + std::string get_nmea(std::string msgtype) { + msgtype.insert(0, "$"); std::string reply; if(not gps_detected()) { UHD_MSG(error) << "get_nmea(): unsupported GPS or no GPS detected"; @@ -167,17 +155,54 @@ public: } int timeout = GPS_TIMEOUT_TRIES; while(timeout--) { - reply = safe_gps_read(); - if(reply.substr(0, 6) == type) + reply = _recv(); + if(reply.substr(0, 6) == msgtype) return reply; - boost::this_thread::sleep(boost::posix_time::milliseconds(GPS_TIMEOUT_DELAY_MS)); + boost::this_thread::sleep(milliseconds(GPS_TIMEOUT_DELAY_MS)); } - UHD_MSG(error) << "get_nmea(): no " << type << " message found"; + UHD_MSG(error) << "get_nmea(): no " << msgtype << " message found"; return std::string(); } + + //helper function to retrieve a field from an NMEA sentence + std::string get_token(std::string sentence, size_t offset) { + boost::tokenizer > tok(sentence); + std::vector toked; + + tok.assign(sentence); + toked.assign(tok.begin(), tok.end()); + + if(toked.size() <= offset) { + UHD_MSG(error) << "get_token: too few tokens in reply " << sentence; + return std::string(); + } + return toked[offset]; + } + + ptime get_time(void) { + std::string reply = get_nmea("GPRMC"); + + std::string datestr = get_token(reply, 9); + std::string timestr = get_token(reply, 1); + + if(datestr.size() == 0 or timestr.size() == 0) { + return ptime(); + } + + //just trust me on this one + return ptime( date( + greg_year(boost::lexical_cast(datestr.substr(4, 2)) + 2000), + greg_month(boost::lexical_cast(datestr.substr(2, 2))), + greg_day(boost::lexical_cast(datestr.substr(0, 2))) + ), + hours( boost::lexical_cast(timestr.substr(0, 2))) + + minutes(boost::lexical_cast(timestr.substr(2, 2))) + + seconds(boost::lexical_cast(timestr.substr(4, 2))) + ); + } time_t get_epoch_time(void) { - return (get_time() - boost::posix_time::from_time_t(0)).total_seconds(); + return (get_time() - from_time_t(0)).total_seconds(); } bool gps_detected(void) { @@ -188,52 +213,9 @@ public: std::string reply = get_nmea("GPGGA"); if(reply.size() <= 1) return false; - boost::tokenizer > tok(reply); - std::vector toked; - - tok.assign(reply); - toked.assign(tok.begin(), tok.end()); - - if(toked.size() != 15) { - UHD_MSG(error) << "gps_locked: invalid GPGGA response"; - return false; - } - - return (toked[6] != "0"); - } - - //return a list of supported sensors - std::vector get_sensors(void) { - std::vector ret; - ret.push_back("gps_gpgga"); - ret.push_back("gps_gprmc"); - ret.push_back("gps_gpgsa"); - ret.push_back("gps_time"); - ret.push_back("gps_locked"); - return ret; + return (get_token(reply, 6) != "0"); } - uhd::sensor_value_t get_sensor(std::string key) { - if(key == "gps_gpgga" - or key == "gps_gprmc" - or key == "gps_gpgsa") { - return sensor_value_t( - boost::to_upper_copy(key), - get_nmea(boost::to_upper_copy(key.substr(4,8))), - ""); - } - else if(key == "gps_time") { - return sensor_value_t("GPS epoch time", int(get_epoch_time()), "seconds"); - } - else if(key == "gps_locked") { - return sensor_value_t("GPS lock status", locked(), "locked", "unlocked"); - } - else { - UHD_THROW_PROP_GET_ERROR(); - } - } - -private: gps_send_fn_t _send; gps_recv_fn_t _recv; @@ -246,7 +228,6 @@ private: static const int GPS_TIMEOUT_TRIES = 10; static const int GPS_TIMEOUT_DELAY_MS = 200; static const int FIREFLY_STUPID_DELAY_MS = 200; - }; /*********************************************************************** -- cgit v1.2.3 From b7f6d905af5993f6c0b554777ab2a4559a36db15 Mon Sep 17 00:00:00 2001 From: Josh Blum Date: Mon, 20 Jun 2011 08:01:35 -0700 Subject: usrp2: fix typo w/ setting send frame size --- host/lib/usrp/usrp2/usrp2_impl.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'host/lib/usrp') diff --git a/host/lib/usrp/usrp2/usrp2_impl.cpp b/host/lib/usrp/usrp2/usrp2_impl.cpp index 9947e71e7..b5e50507c 100644 --- a/host/lib/usrp/usrp2/usrp2_impl.cpp +++ b/host/lib/usrp/usrp2/usrp2_impl.cpp @@ -249,7 +249,7 @@ usrp2_impl::usrp2_impl(const device_addr_t &_device_addr){ //extract the user's requested MTU size or default mtu_result_t user_mtu; user_mtu.recv_mtu = size_t(device_addr.cast("recv_frame_size", udp_simple::mtu)); - user_mtu.send_mtu = size_t(device_addr.cast("recv_frame_size", udp_simple::mtu)); + user_mtu.send_mtu = size_t(device_addr.cast("send_frame_size", udp_simple::mtu)); try{ //calculate the minimum send and recv mtu of all devices -- cgit v1.2.3 From 25d6e39c6af61acde0616cf50178d40741c4eace Mon Sep 17 00:00:00 2001 From: Josh Blum Date: Fri, 24 Jun 2011 16:20:33 -0700 Subject: usrp2: init the usrp2_ctrl_data_t to make valgrind happy --- host/lib/usrp/usrp2/usrp2_iface.cpp | 12 ++++++------ host/lib/usrp/usrp2/usrp2_impl.cpp | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) (limited to 'host/lib/usrp') diff --git a/host/lib/usrp/usrp2/usrp2_iface.cpp b/host/lib/usrp/usrp2/usrp2_iface.cpp index ec1a2e94c..6ba364b28 100644 --- a/host/lib/usrp/usrp2/usrp2_iface.cpp +++ b/host/lib/usrp/usrp2/usrp2_iface.cpp @@ -179,7 +179,7 @@ public: template T get_reg(boost::uint32_t addr, T data = 0){ //setup the out data - usrp2_ctrl_data_t out_data; + usrp2_ctrl_data_t out_data = usrp2_ctrl_data_t(); out_data.id = htonl(USRP2_CTRL_ID_GET_THIS_REGISTER_FOR_ME_BRO); out_data.data.reg_args.addr = htonl(addr); out_data.data.reg_args.data = htonl(boost::uint32_t(data)); @@ -207,7 +207,7 @@ public: ; //setup the out data - usrp2_ctrl_data_t out_data; + usrp2_ctrl_data_t out_data = usrp2_ctrl_data_t(); out_data.id = htonl(USRP2_CTRL_ID_TRANSACT_ME_SOME_SPI_BRO); out_data.data.spi_args.dev = htonl(which_slave); out_data.data.spi_args.miso_edge = spi_edge_to_otw[config.miso_edge]; @@ -228,7 +228,7 @@ public: **********************************************************************/ void write_i2c(boost::uint8_t addr, const byte_vector_t &buf){ //setup the out data - usrp2_ctrl_data_t out_data; + usrp2_ctrl_data_t out_data = usrp2_ctrl_data_t(); out_data.id = htonl(USRP2_CTRL_ID_WRITE_THESE_I2C_VALUES_BRO); out_data.data.i2c_args.addr = addr; out_data.data.i2c_args.bytes = buf.size(); @@ -246,7 +246,7 @@ public: byte_vector_t read_i2c(boost::uint8_t addr, size_t num_bytes){ //setup the out data - usrp2_ctrl_data_t out_data; + usrp2_ctrl_data_t out_data = usrp2_ctrl_data_t(); out_data.id = htonl(USRP2_CTRL_ID_DO_AN_I2C_READ_FOR_ME_BRO); out_data.data.i2c_args.addr = addr; out_data.data.i2c_args.bytes = num_bytes; @@ -276,7 +276,7 @@ public: BOOST_FOREACH(std::string item, queue) { //setup the out data - usrp2_ctrl_data_t out_data; + usrp2_ctrl_data_t out_data = usrp2_ctrl_data_t(); out_data.id = htonl(USRP2_CTRL_ID_HEY_WRITE_THIS_UART_FOR_ME_BRO); out_data.data.uart_args.dev = dev; out_data.data.uart_args.bytes = item.size(); @@ -298,7 +298,7 @@ public: std::string result; while(readlen == 20) { //while we keep receiving full packets //setup the out data - usrp2_ctrl_data_t out_data; + usrp2_ctrl_data_t out_data = usrp2_ctrl_data_t(); out_data.id = htonl(USRP2_CTRL_ID_SO_LIKE_CAN_YOU_READ_THIS_UART_BRO); out_data.data.uart_args.dev = dev; out_data.data.uart_args.bytes = 20; diff --git a/host/lib/usrp/usrp2/usrp2_impl.cpp b/host/lib/usrp/usrp2/usrp2_impl.cpp index b5e50507c..aa584ac8b 100644 --- a/host/lib/usrp/usrp2/usrp2_impl.cpp +++ b/host/lib/usrp/usrp2/usrp2_impl.cpp @@ -93,7 +93,7 @@ static device_addrs_t usrp2_find(const device_addr_t &hint_){ ); //send a hello control packet - usrp2_ctrl_data_t ctrl_data_out; + usrp2_ctrl_data_t ctrl_data_out = usrp2_ctrl_data_t(); ctrl_data_out.proto_ver = uhd::htonx(USRP2_FW_COMPAT_NUM); ctrl_data_out.id = uhd::htonx(USRP2_CTRL_ID_WAZZUP_BRO); udp_transport->send(boost::asio::buffer(&ctrl_data_out, sizeof(ctrl_data_out))); -- cgit v1.2.3