diff options
| -rw-r--r-- | host/docs/CMakeLists.txt | 1 | ||||
| -rw-r--r-- | host/docs/gpsdo.rst | 66 | ||||
| -rw-r--r-- | host/docs/sync.rst | 19 | ||||
| -rw-r--r-- | host/docs/usrp2.rst | 8 | ||||
| -rw-r--r-- | host/include/uhd/usrp/gps_ctrl.hpp | 14 | ||||
| -rw-r--r-- | host/lib/usrp/gps_ctrl.cpp | 210 | ||||
| -rw-r--r-- | host/lib/usrp/usrp2/mboard_impl.cpp | 24 | 
7 files changed, 223 insertions, 119 deletions
| diff --git a/host/docs/CMakeLists.txt b/host/docs/CMakeLists.txt index 88afb9c98..be00a6655 100644 --- a/host/docs/CMakeLists.txt +++ b/host/docs/CMakeLists.txt @@ -24,6 +24,7 @@ SET(manual_sources      build.rst      coding.rst      dboards.rst +    gpsdo.rst      general.rst      images.rst      sync.rst diff --git a/host/docs/gpsdo.rst b/host/docs/gpsdo.rst new file mode 100644 index 000000000..a0f2d67da --- /dev/null +++ b/host/docs/gpsdo.rst @@ -0,0 +1,66 @@ +======================================================================== +UHD - Internal GPSDO Application Notes +======================================================================== + +.. contents:: Table of Contents + +This application note describes the use of integrated GPS-disciplined +oscillators with Ettus Research USRP devices. It pertains specifically +to the Jackson Labs Firefly-1A device unless noted otherwise. + +------------------------------------------------------------------------ +Specifications +------------------------------------------------------------------------ +Receiver type: 50 channel with WAAS, EGNOS, MSAS +10MHz ADEV: 1e-11 over >24h +1PPS RMS jitter: <50ns 1-sigma +Holdover: <11us over 3h +Phase noise: 1Hz: -80dBc/Hz, 10Hz: -110dBc/Hz, 100Hz: -135dBc/Hz, 1kHz: -145dBc/Hz, 10kHz: <-145dBc/Hz + +------------------------------------------------------------------------ +Installation +------------------------------------------------------------------------ +See the documentation for your device for specifics on installing the GPSDO. + +------------------------------------------------------------------------ +Configuring the USRP to use the GPSDO +------------------------------------------------------------------------ +This is necessary if you require absolute GPS time in your application, +or need to communicate with the GPSDO to obtain location, satellite info, etc. +If you only require 10MHz and PPS signals for reference or MIMO use, +(see the Synchronization application note), it is not necessary to perform +this step. + +To configure the USRP to communicate with the GPSDO, use the +usrp_burn_mb_eeprom utility: + +:: +   $ cd <install-path>/share/uhd/utils +   $ ./usrp_burn_mb_eeprom --key=gpsdo --val=internal + +This will configure the driver to communicate with the GPSDO on startup. + +------------------------------------------------------------------------ +Using the GPSDO in your application +------------------------------------------------------------------------ +By default, if a GPSDO is detected at startup, the USRP will be configured +to use it as a frequency and time reference. The internal VITA timestamp +will be initialized to the GPS time, and the internal oscillator will be +phase-locked to the 10MHz GPSDO reference. If the GPSDO is not locked to +satellites, the VITA time will not be initialized. + +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"); + +The returned value will be the current epoch time, in seconds since +January 1, 1970. This value is readily converted into human-readable +format using the time.h library in C, boost::posix_time in C++, etc. + +Other information can be fetched as well. You can query the lock status +with the "gps_locked" sensor, as well as obtain raw NMEA sentences using +the "gps_gpgsa", "gps_gprmc", and "gps_gpgga" sensors. Location +information can be parsed out of the "gps_gpgga" sensor by using gpsd or +another NMEA parser. 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..1b07834fb 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:** @@ -396,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/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 <boost/utility.hpp>  #include <boost/function.hpp>  #include <boost/date_time/posix_time/posix_time_types.hpp> +#include <vector> +#include <uhd/types/sensors.hpp>  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<std::string> 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..3cef1ef19 100644 --- a/host/lib/usrp/gps_ctrl.cpp +++ b/host/lib/usrp/gps_ctrl.cpp @@ -17,23 +17,26 @@  #include <uhd/usrp/gps_ctrl.hpp>  #include <uhd/utils/msg.hpp> +#include <uhd/utils/props.hpp>  #include <uhd/exception.hpp> +#include <uhd/types/sensors.hpp> +#include <boost/algorithm/string.hpp> +#include <boost/assign/list_of.hpp>  #include <boost/cstdint.hpp>  #include <boost/date_time/posix_time/posix_time.hpp>  #include <boost/thread/thread.hpp> -#include <boost/algorithm/string/trim.hpp>  #include <boost/tokenizer.hpp>  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){ @@ -42,74 +45,41 @@ 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      //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(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; -    //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;      } -    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 0\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)); - -//      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(200)); -      } -      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; @@ -118,71 +88,134 @@ public:        break;      } - -    }    ~gps_ctrl_impl(void){ +    /* NOP */ +  } +  //return a list of supported sensors +  std::vector<std::string> get_sensors(void) { +    std::vector<std::string> ret = boost::assign::list_of +        ("gps_gpgga") +        ("gps_gprmc") +        ("gps_gpgsa") +        ("gps_time") +        ("gps_locked"); +    return ret;    } -  std::string safe_gps_read() { -    return _recv(); +  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(); +    }    } -  ptime get_time(void) { +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; -    ptime now; -    boost::tokenizer<boost::escaped_list_separator<char> > tok(reply); -    std::vector<std::string> toked; +    if(not gps_detected()) { +        UHD_MSG(error) << "get_nmea(): unsupported GPS or no GPS detected"; +        return std::string(); +    }      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 = _recv(); +        if(reply.substr(0, 6) == msgtype) +          return reply; +        boost::this_thread::sleep(milliseconds(GPS_TIMEOUT_DELAY_MS)); +    } +    UHD_MSG(error) << "get_nmea(): no " << msgtype << " message found"; +    return std::string(); +  } -      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<int>(toked[9].substr(4, 2)) + 2000), //just trust me on this one -                         greg_month(boost::lexical_cast<int>(toked[9].substr(2, 2))),  -                         greg_day(boost::lexical_cast<int>(toked[9].substr(0, 2)))  -                       ), -                   hours(  boost::lexical_cast<int>(toked[1].substr(0, 2))) -                 + minutes(boost::lexical_cast<int>(toked[1].substr(2, 2))) -                 + seconds(boost::lexical_cast<int>(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; +  //helper function to retrieve a field from an NMEA sentence +  std::string get_token(std::string sentence, size_t offset) { +    boost::tokenizer<boost::escaped_list_separator<char> > tok(sentence); +    std::vector<std::string> 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 now; +    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<int>(datestr.substr(4, 2)) + 2000), +                     greg_month(boost::lexical_cast<int>(datestr.substr(2, 2))),  +                     greg_day(boost::lexical_cast<int>(datestr.substr(0, 2)))  +                   ), +                  hours(  boost::lexical_cast<int>(timestr.substr(0, 2))) +                + minutes(boost::lexical_cast<int>(timestr.substr(2, 2))) +                + seconds(boost::lexical_cast<int>(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) {      return (gps_type != GPS_TYPE_NONE);    } -private: +  bool locked(void) { +      std::string reply = get_nmea("GPGGA"); +      if(reply.size() <= 1) return false; + +      return (get_token(reply, 6) != "0"); +  } +    gps_send_fn_t _send;    gps_recv_fn_t _recv; @@ -195,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; -  };  /*********************************************************************** diff --git a/host/lib/usrp/usrp2/mboard_impl.cpp b/host/lib/usrp/usrp2/mboard_impl.cpp index 6bf412a3e..61ceb95ca 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 @@ -174,6 +180,13 @@ 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 +388,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<std::string> gs = _gps_ctrl->get_sensors(); +                names.insert(names.end(), gs.begin(), gs.end()); +            }              val = names;          }          return; @@ -389,8 +405,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(); | 
